Token在前后端分离项目中的使用

1,458 阅读6分钟

认识token

一、token是计算机术语:

令牌,令牌是一种能够控制站点占有媒体的特殊帧,以区别数据帧及其他控制帧。token其实说的更通俗点可以叫暗号,在一些数据传输之前,要先进行暗号的核对,不同的暗号被授权不同的数据操作。基于 Token 的身份验证方法

二、在前后端完全分离的情况下,

Vue项目中实现token验证大致思路如下:

1、第一次登录的时候,前端调后端的登陆接口,发送用户名和密码

2、后端收到请求,验证用户名和密码,验证成功,就给前端返回一个token

3、前端拿到token,将token存储到localStorage和vuex中,并跳转路由页面

4、前端每次跳转路由,就判断 localStroage 中有无 token ,没有就跳转到登录页面,有则跳转到对应路由页面

5、每次调后端接口,都要在请求头中加token

6、后端判断请求头中有无token,有token,就拿到token并验证token,验证成功就返回数据,验证失败(例如:token过期)就返回401,请求头中没有token也返回401

7、如果前端拿到状态码为401,就清除token信息并跳转到登录页面

三、具体问题

  1. 如何生成token?
  2. 如何验证token?

引出JWT

什么是jwt:

JsonWebToken,通俗的说就是通过JSON的形式作为web应用的令牌,用于在各方之间安全的将信息作为json对象传输,在数据传输过程可以完成数据加密和解密。

jwt可以做什么?

授权:使用jwt最常见的方案,一旦用户登陆,每个后续请求将包括jwt从而允许用户访问该令牌的路由,服务和资源;

信息交换:用的较少;

为什么选择JWT?
传统的认证方式:

​ http是一种无状态的协议,如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要在一次进行用户认证才行,因为根据http协议,不知道是哪个用户发送的请求,所以为了让我们的应用能识别是哪个用户发送的请求,只能在服务器储存一份用户登陆的信息,这份登陆信息会在登陆后响应给浏览器,浏览器保存为cookie,以便下次发送请求能够被服务器识别是哪个用户,这就是传统的基于session的认证。

传统认证的问题:

  • 每个用户认证后,都要在服务器进行记录,以便下一次鉴别,seesion通常都是保存在内存中,随着用户增多,服务器的开销会明显变大;
  • 用户认证后,服务器端做认证记录,如果记录都保存在内存中,意味着下次用户还必须访问该服务器,才能拿到授权资源,这样在分布式应用中,限制了负载均衡的能力,也限制了应用的扩展能力;
  • 基于cooke进行用户识别,可能会被解惑,用户容易受到跨站请求伪造攻击。
  • 在前后端分离的系统中:增加了部署的复杂性,通常用户的一次请求要被转发多次,如果用session,每次携带sessionID到服务器,增加了服务器的压力。
基于JWT的认证过程:

1、前端通过web表单,将自己的用户名和密码发送到后端的接口,这一过程一般是个http post请求,建议的方式是通过ssl加密的传输(https协议),从而避免信息被嗅探;

2、后端核对用户名后,将用户的id等其他信息作为JWT Payload(负载),将其他头部分别进行base64编码拼接后签名,形成一个jwt字符串;响应给前端存放在localStorge或者SessionStorge中,退出登陆删除即可;

3、前端在每次请求时将JWT放在http header中的Authorization位。(解决XSS和SCRF的问题)

4、后端通过JWT自带的方法检测JWT的有效性,

5、也可获取JWT中包含的信息,进行其他逻辑操作。

jwt的优势

简洁:通过url或者post参数或者在http的header中发送,因为数据量小,传输速度也快

自包含:负载中包含了用户所需信息,避免了多次查询数据库

因为token是以json加密存储在客户端的,所以jwt是跨语言的,任何web都支持

不需要再服务端保存会话信息,特别适用于分布式微服务

JWT的结构:

token String ===> header.paylod.singnature

​ 头部 负载 签名

header:由两部分组成:令牌类型和所使用签名的算法,如HMAC、SHA256,他会使用Base64编码组成JWT结构的第一部分。

payload:负载,可以放入信息,也会通过Base64编码组成JWT

signature:前两部分都是Base64编码的,可以直接被前端解开知道里面的东西。Signature需要使用编码的Header和payload以及我们提供的一个密钥,然后使用指定的算法进行签名,以保证jwt没有被修改过

三部分组成即jwt

jwt的使用:

1、引入依赖 java-jwt

2、生成token

Calendar instance = Calendar。getInstande();
instance.add(Calendar.SECOND,90);
String token = JWT.create()

.withClaim("username","张三")  //添加负载信息

.withExpiresAT(instance)   // 设置过期时间

.sing(Algorithm.HMAC256("!#%sdf*"))   //设置签名 

3、根据令牌和签名解析数据

JWTVerifier   jwtVerifier = JWT.require(Algorithm.HMAC256("!#%sdf*")).build;
DecodedJWT decodeJWT =  jwtVerifier.verify(token)

4、封装成工具类使用:

package com.tnt.util;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;

import java.util.Calendar;
import java.util.Map;

/**
 * @author huangrw
 * @program: 后端-netsecurity_exam
 * @Description  JWT工具类
 * @createTime 2021年02月20日 21:31:00
 */
public class JWTUtils {

    private static final String SING = "!2323eW@y";

    /**
     * @Author huangrw
     * @Description  生成token
     * @Date 21:44 2021/2/20
     * @Param [map]
     * @return java.lang.String
    **/
    public static String getToken(Map<String,String> map){

        Calendar instance = Calendar.getInstance();
        // 默认三天过期
        instance.add(Calendar.DATE,3);

        // 创建jwt builder
        JWTCreator.Builder builder = JWT.create();

        map.forEach((k,v)->{
            builder.withClaim(k,v);
        });

        String token = builder.withExpiresAt(instance.getTime())  //指定令牌过期时间
                .sign(Algorithm.HMAC256(SING));   //签名
        return token;
    }


    /**
     * @Author huangrw
     * @Description  验证token
     * @Date 22:07 2021/2/20
     * @Param [token]
     * @return com.auth0.jwt.interfaces.DecodedJWT
    **/
    public static DecodedJWT verify(String token){
      return   JWT.require(Algorithm.HMAC256(SING)).build().verify(token);
    }

}

5、使用拦截器进行校验,

package com.tnt.interceptor;

import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sun.javafx.geom.transform.SingularMatrixException;
import com.tnt.util.JWTUtils;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;

/**
 * @author huangrw
 * @program: 后端-netsecurity_exam
 * @Description  JWT拦截器
 * @createTime 2021年02月20日 21:50:00
 */
public class JWTInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Map<String,Object> map = new HashMap<>();

        // 解决跨域问题,所有options请求统统放行
        if("OPTIONS".equals(request.getMethod().toUpperCase())){
            return true;
        }

        // 获取请求头中的token
        String token = request.getHeader("token");
        System.out.println(token);
        try {
            JWTUtils.verify(token);
            return true;
        } catch (SingularMatrixException e){
            e.printStackTrace();
            map.put("msg","无效签名");
        }catch (TokenExpiredException e){
            e.printStackTrace();
            map.put("msg","token过期");
        }catch ( AlgorithmMismatchException e ){
            e.printStackTrace();
            map.put("msg","token算法不一致");
        }catch (Exception e){
            e.printStackTrace();
            map.put("msg","token无效");

        }
        map.put("state",false); // 设置状态
        // 将map转换成json  jackson
        String json = new ObjectMapper().writeValueAsString(map);
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().println(json);
        return false;
    }
}

6、配置拦截器

package com.tnt.config;

import com.tnt.interceptor.JWTInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author huangrw
 * @program: 后端-netsecurity_exam
 * @Description  JWT拦截器配置
 * @createTime 2021年02月20日 22:03:00
 */
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new JWTInterceptor())
                .addPathPatterns("/**")    //拦截所有请求
                //.excludePathPatterns("/**")
                .excludePathPatterns("/api/user/regist")   //放行注册请求
                .excludePathPatterns("/api/admin/login")  //放行管理员登陆请求
                .excludePathPatterns("/api/user/login")   //放行用户登陆请求
                .excludePathPatterns("/doc.html","/swagger-resources");  //放行Knife4j
    }


}