全局过滤器实现Jwt校验

从Session到Jwt

之前我写过一篇 什么是 httpsession :
理解HttpSession

在经典的那个登录场景中:

客户端第一次访问的时候 需要登录 登录成功之后

后面再次访问的时候 为了让服务器认识 这是已经登录成功的我

在session中存储的用户的信息。

现在我们进化了! 使用Jwt
(jwt是一段token 它是登录的时候根据用户id生成的 也就说id一样 token就一样)

全局过滤器实现Jwt校验

在这里插入图片描述
为什么要进化 因为在分布式微服务中 会有很多个系统和服务相互配合 我们一般会用一个独立的网关服务来控制所有的请求入口

这时候jwt会比session更好用


我们一步一步来看 首先前端发的请求 先到nginx
nginx再把请求转发到后端服务器 比如到这个微服务 51601

upstream  heima-app-gateway{
    server localhost:51601;
}

那么我们在 微服务 51601里面 创建一个过滤器把所有发过来的请求都拦截下来:
创建一个过滤器实现 Ordered, GlobalFilter 接口

@Component
@Slf4j
public class AuthorizeFilter implements Ordered, GlobalFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        return null;
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

解释一下 这里面有2个方法重写 第一个是过滤用的 第二个是设置优先级用的(int值越小 代表优先级越高)

拦截下所有的请求之后 我们可以从ServerWebExchange 获取每个请求的 req和resp对象
我们就可以自己想怎么搞怎么搞

比如我们/login的请求就直接放行

   //2.判断是否是登录
        if(request.getURI().getPath().contains("/login")){
            //放行
            return chain.filter(exchange);
        }

其他请求 我们要验证一下 有没有token token是否有效 是否过期
注意这个token 当用户通过 /login 登录成功之后 我们就用jwt生成一个token 传给前端:

    public static String getToken(Long id){
        Map<String, Object> claimMaps = new HashMap<>();
        claimMaps.put("id",id);
        long currentTime = System.currentTimeMillis();
        return Jwts.builder()
                .setId(UUID.randomUUID().toString())
                .setIssuedAt(new Date(currentTime))  //签发时间
                .setSubject("system")  //说明
                .setIssuer("heima") //签发者信息
                .setAudience("app")  //接收用户
                .compressWith(CompressionCodecs.GZIP)  //数据压缩方式
                .signWith(SignatureAlgorithm.HS512, generalKey()) //加密方式
                .setExpiration(new Date(currentTime + TOKEN_TIME_OUT * 1000))  //过期时间戳
                .addClaims(claimMaps) //cla信息
                .compact();
    }

它生成的时候 就会把: 用户id 有效期 接收方 等等这些信息都加密成一个很长的串
这样这个用户再次访问服务的时候 就会一直带着这个token直到它过期,过期之后就需要重新登录

   @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //获取每个请求的 req和resp对象
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        //2.判断是否是登录
        if(request.getURI().getPath().contains("/login")){
            //放行
            return chain.filter(exchange);
        }

        //3.获取token
        String token = request.getHeaders().getFirst("token");

        //4.判断token是否存在
        if(StringUtils.isBlank(token)){
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }

        //5.判断token是否有效
        try {
            Claims claimsBody = AppJwtUtil.getClaimsBody(token);
            //是否是过期
            int result = AppJwtUtil.verifyToken(claimsBody);
            if(result == 1 || result  == 2){
                response.setStatusCode(HttpStatus.UNAUTHORIZED);
                return response.setComplete();
            }
        }catch (Exception e){
            e.printStackTrace();
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            return response.setComplete();
        }

        //6.放行
        return chain.filter(exchange);


    }

想象一下你进公园的时候 进门发给你一张电子卡 这个卡里面 把你的身份证 可以玩哪些项目 游玩时间等数据加密
然后你没到一个点 验证一下 就可以知道你的信息 看你是不是买票了 或者你还有多久可以玩


对比一下jwt和session

本质上都是 客户端访问服务器 登录成功之后 创建一个东西 给前端 让他下次来的时候认识我

jwt是自己的代码创建一个token

session是服务器自己创建一个session id给前端

但是问题是如果后台有3个机器 那么你用jwt不影响 因为第一台机器登录成功 给你token 你拿着token如果第二次访问 遇到其他机器还是可以通过

但是session就有问题了如果你第二次遇到其他机器 其他几次不认识这个session id 所以还需要其他的手段来补足 比如拷贝session


AppJwtUtil

public class AppJwtUtil {

    // TOKEN的有效期一天(S)
    private static final int TOKEN_TIME_OUT = 3_600;
    // 加密KEY
    private static final String TOKEN_ENCRY_KEY = "MDk4ZjZiY2Q0NjIxZDM3M2NhZGU0ZTgzMjYyN2I0ZjY";
    // 最小刷新间隔(S)
    private static final int REFRESH_TIME = 300;

    // 生产ID
    public static String getToken(Long id){
        Map<String, Object> claimMaps = new HashMap<>();
        claimMaps.put("id",id);
        long currentTime = System.currentTimeMillis();
        return Jwts.builder()
                .setId(UUID.randomUUID().toString())
                .setIssuedAt(new Date(currentTime))  //签发时间
                .setSubject("system")  //说明
                .setIssuer("heima") //签发者信息
                .setAudience("app")  //接收用户
                .compressWith(CompressionCodecs.GZIP)  //数据压缩方式
                .signWith(SignatureAlgorithm.HS512, generalKey()) //加密方式
                .setExpiration(new Date(currentTime + TOKEN_TIME_OUT * 1000))  //过期时间戳
                .addClaims(claimMaps) //cla信息
                .compact();
    }

    /**
     * 获取token中的claims信息
     *
     * @param token
     * @return
     */
    private static Jws<Claims> getJws(String token) {
            return Jwts.parser()
                    .setSigningKey(generalKey())
                    .parseClaimsJws(token);
    }

    /**
     * 获取payload body信息
     *
     * @param token
     * @return
     */
    public static Claims getClaimsBody(String token) {
        try {
            return getJws(token).getBody();
        }catch (ExpiredJwtException e){
            return null;
        }
    }

    /**
     * 获取hearder body信息
     *
     * @param token
     * @return
     */
    public static JwsHeader getHeaderBody(String token) {
        return getJws(token).getHeader();
    }

    /**
     * 是否过期
     *
     * @param claims
     * @return -1:有效,0:有效,1:过期,2:过期
     */
    public static int verifyToken(Claims claims) {
        if(claims==null){
            return 1;
        }
        try {
            claims.getExpiration()
                    .before(new Date());
            // 需要自动刷新TOKEN
            if((claims.getExpiration().getTime()-System.currentTimeMillis())>REFRESH_TIME*1000){
                return -1;
            }else {
                return 0;
            }
        } catch (ExpiredJwtException ex) {
            return 1;
        }catch (Exception e){
            return 2;
        }
    }

    /**
     * 由字符串生成加密key
     *
     * @return
     */
    public static SecretKey generalKey() {
        byte[] encodedKey = Base64.getEncoder().encode(TOKEN_ENCRY_KEY.getBytes());
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    }

}

然后我们启动测试项目 postman 请求一个带 /login的 或者不带的 试一下

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值