基于上次做的前后端项目的sso是shiro整合的jwt,jwt这块还不太熟悉,所以去回顾了下知识点,简单记录一下。主要是端午节三天天气太反常了,坐着都出汗,啥也没干,所以节前做的都忘了也不知道记啥了。

session和JWT

之前shrio自己都是集成session的,整合JWT需要自己去写。先说说session,session在服务器端存储,客户端在一次登录成功后以后再次登录会向服务端发送带有session返回的cookie去服务端查对应session来实现自动登录。例:

package com.jin.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;


/**
 * @author jinyunlong
 * @date 2021/6/11 10:39
 * @profession ICBC锅炉房保安
 */

@RestController
public class SessionController {
    @GetMapping(value = "test")
    public String session(String username, HttpServletRequest httpServletRequest){
        httpServletRequest.getSession().setAttribute("username",username);
        return "login ok";
    }
}

5.PNG

sesssion存在的问题:

1、session存在服务端,大部分是在内存中,要是用户多了,服务器端压力会变大

2、基于cookie的话,容易被跨站伪造

3、以后多节点部署的话,还要做session共享,不方便做集群

4、session只是一段特征值,实际没啥意义,我们到服务器端还是要根据这个session值去数据库该查查数据,数据库压力也是一样的大。

JWT认证流程:用户在第一次登录成功后,后端会根据jwt的格式(header+payload(不要放敏感信息)+sign)生成一个token返回给前端存在本地localstorge中。然后前端以后再去请求后端接口时会把token放在http header中传入后端,后端再根据自己定义的规则(过期时间,有效性等)去校验从而实现自动登录,通过的话去调用对应接口业务逻辑,失败则抛出相应异常。

JWT token的好处:

1、简单:可以通过post参数或者放在http header中请求,数据量小

2、在本地存储,减少服务端压力

3、自包含:token的payload段中可以包含想要的相关用户信息,拿来即用,可以避免多次查询数据库

4、token是json格式,所以跨语言

JWT结构

token组成:header(标头)+payload(有效载荷)+signature(签名) header.payload.signature

header:声明jwt中使用的签名算法:如HMAC、RSA、SHA256等

payload:在这里头就能放想要的数据了

signature:签名秘钥

一个springboot实例

首先,生成token util类:

package com.jin.utils;

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 jinyunlong
 * @date 2021/6/11 17:12
 * @profession ICBC锅炉房保安
 */
public class JWTUtils {

    private static final String SIGN = "123456";
    //生成token   header.payload.sign
    public static String getToken(Map<String,String> map){

        Calendar instance =Calendar.getInstance();
        instance.add(Calendar.DATE,7);  //令牌有效时间,7天过期
        //创建jwt builder
        JWTCreator.Builder builder = JWT.create();
        //payload
        map.forEach((k,v)->{
            builder.withClaim(k,v);
        });
        String token = builder.withExpiresAt(instance.getTime())    //指定令牌过期时间
        .sign(Algorithm.HMAC256(SIGN));

        System.out.println(token);
        return token;
    }

    //验证token合法性,不合法就直接抛异常了,合法就当我们无事发生过 - -
    public static void verify(String token){
         JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token);
    }

    //获取token信息方法
    public static DecodedJWT getTokenInfo(String token){
        DecodedJWT verify = JWT.require(Algorithm.HMAC256(SIGN)).build().verify(token);
        return verify;
    }
}

controller登录接口去查库并用postman测试,成功的话生成token:

package com.jin.controller;

import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.jin.entity.User;
import com.jin.service.UserService;
import com.jin.utils.JWTUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

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

/**
 * @author jinyunlong
 * @date 2021/6/11 18:06
 * @profession ICBC锅炉房保安
 */
@RestController
@Slf4j
public class UserController {
    @Autowired
    private UserService userService;

    @GetMapping("/user")
    public Map<String,Object> login(User user){
        log.info("用户名:[{}]",user.getName());
        log.info("密码:[{}]",user.getPassword());
        Map<String,Object> map = new HashMap<>();

        try {
            User userDB = userService.login(user);
            Map<String,String> payload = new HashMap<>();
            payload.put("id",userDB.getId());
            payload.put("name",userDB.getName());

            //生成jwt令牌
            String token = JWTUtils.getToken(payload);

            map.put("state",true);
            map.put("msg","认证成功");
            map.put("token",token);  //响应token
        } catch (Exception e) {
            map.put("state",false);
            map.put("msg",e.getMessage());
        }
        return map;
    }

    @GetMapping("/user1")
    public Map<String,Object> test(HttpServletRequest request){
        Map<String,Object> map = new HashMap<>();
        String token = request.getHeader("token");
        DecodedJWT decodedJWT = JWTUtils.getTokenInfo(token);
        log.info("用户id:[{}]",decodedJWT.getClaim("id").asString());
        log.info("用户名:[{}]",decodedJWT.getClaim("name").asString());
        //处理自己的业务逻辑
        map.put("state",true);
        map.put("msg","请求成功");
        return map;
//        log.info("当前token为:[{}]",token);
//        try {
//              JWTUtils.verify(token);   //验证令牌
//              map.put("state",true);
//              map.put("msg","请求成功");
//              return map;
//        } catch (SignatureVerificationException 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);
//        return map;
        //有了拦截器就不用了
    }
}

2.PNG

然后写个拦截器并注入到容器中

package com.jin.interceptor;

import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jin.utils.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 jinyunlong
 * @date 2021/6/15 10:39
 * @profession ICBC锅炉房保安
 */
public class JWTInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //获取请求头中的令牌
        String token = request.getHeader("token");
        Map<String,Object> map = new HashMap<>();

        try {
            JWTUtils.verify(token);   //验证令牌
            return true;  //放行请求
        } catch (SignatureVerificationException 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;
    }
}

package com.jin.config;

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

/**
 * @author jinyunlong
 * @date 2021/6/15 10:48
 * @profession ICBC锅炉房保安
 */
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new JWTInterceptor())             //拦他
//                .addPathPatterns("/**")                        //全拦
//                .excludePathPatterns("/user/**");             //排除
                .addPathPatterns("/user1")                        //全拦
                .excludePathPatterns("/user");   //或者这样一拦一放
    }
}

那根据上面两段代码写的,/user接口我是放行了,但是调用user1接口时会先去走拦截器,拦截器会去调用http header存入的刚才生成的token的调util 类中校验方法去校验,没抛异常就放行到接口中去实现业务逻辑,抛异常就进异常。用postman测试一下:
第一次我没加token请求user1接口,进入拦截器被拦,抛出token无效的异常:
3.PNG

然后我在请求头中带上token传:

1.PNG
可以看见返回了user1接口的成功内容

 map.put("state",true);
 map.put("msg","请求成功");

带上一点业务处理逻辑,看下log:

4.PNG

可见token没问题的话会通过拦截器成功到接口下,获取到了token中的payload内容。

总结下JWT的逻辑,总体不难:

1、先通过util类写生成token、获取token、校验token的方法

2、登录成功时一定要调用生成token方法

3、写拦截器,里面调校验token的方法,通过就放行,不通过就直接抛异常,所以校验写void就可以

4、拦截器注入容器,在这个config类中设置那些接口放行,哪些需要拦截

5、前端传参可以把token通过参数post,或者放进header请求至后端,拦截器校验通过的话,到对应接口下,接口可以通过上面两种方式接参(我上面用的httprequest接header)获取token值后,再去获取token中的payload值。

6、hashMap(我也不知道要说啥。。。)

哎,端午节不该回家的,闷热的不行,吃刨冰去了。


标题:JWT要点
作者:jyl
地址:http://www.jinyunlong.xyz/articles/2021/06/15/1623734766217.html