redis + jwt实现token认证

15 篇文章 2 订阅
12 篇文章 0 订阅

业务场景

在前后端分离的场景下,越来越多的项目使用token作为接口的安全机制,APP或者web端使用token与后端接口交互,以达到安全的目的

拦截器

  • 拦截器

    • 获取前端请求的token
    • 判断token是否为空
    • token不为空,则查询redis缓存是否有相关token
    • 有相关token则通过拦截,请求资源
@Configuration
@Slf4j
public class JwtInterceptor implements HandlerInterceptor {

    @Autowired
    private TbGuestMapper tbGuestMapper;

    @Autowired
    private LoginMapper loginMapper;

    @Autowired
    private RedisUtils redisUtils;

    /**
     * 预处理方法
     *
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 允许跨域
        response.setHeader("Access-Control-Allow-Origin", "*");
        String token = request.getHeader("authorization");
        //服务器内置的token,用于测试接口
        if ("testToken".equals(token)) {
            return true;
        }

        // 1.----------- 解析jwt信息

        //判断token是否为null,使用Optional防止出现空指针异常(optional可以接受null值)
        Optional.ofNullable(token)
                .map(n -> {
                    //判断token是否过期,过期则抛出异常
                    try {
                        log.info("----->" + n);
                        log.info("----->" + JwtUtils.parseJWT(n));
                        return JwtUtils.parseJWT(n);
                    } catch (Exception e) {
                        log.warn(">>>>>>>>>>>>>>>>>>token不存在!,重新授权登录!>>>>>>>>>>>>>>>>>>");
                        throw new RuntimeException("token不存在!,重新授权登录!");
                    }
                });

//        // 2.------------- 数据库交互
//        //解密出该用户的unionid,并去数据库教验是否存在,不存在则让其重新授权登录
//        Claims claims = JwtUtils.parseJWT(token);
//        String base64UnionId = (String) claims.get("key");
//        if("admin".equals(claims.getSubject())){
//            //后台登录情况
//        }
//        byte[] decode = Base64.getDecoder().decode(base64UnionId);
//        String unionId = new String(decode);
//        TbGuestExample tbGuestExample = new TbGuestExample();
//        tbGuestExample.createCriteria().andUnionidEqualTo(unionId);
//        long i = tbGuestMapper.countByExample(tbGuestExample);
//        if(i < 1){
//            //不存在该用户
//            log.warn("用户unionid:" + unionId + "不存在于数据库中,重新授权登陆");
//            return false;
//        }

        //解析token
        Claims claims = JwtUtils.parseJWT(token);
        //获取登录token标识
        String base64UnionId = (String)claims.get("key");
        //分情况登录
        //后台登录
        if("admin".equals(claims.getSubject())){

        }
        // 小程序登录
        // 取出token中的unionid
        byte[] decode = Base64.getDecoder().decode(base64UnionId);
        String unionId = new String(decode);
        //查redis中有无该token
        String tokenKey = "token:"+unionId;
        log.info("redis中查到相关token");
        if (redisUtils.get(tokenKey) == null){
            //不存在该key
            log.warn("用户unionid:" + unionId + "不存在于数据库中,重新授权登陆");
            return false;
        }
        return true;
    }
}

1.登录授权获取token

第一种业务场景就是最普遍的登录授权获取到token后,前端带上token去请求资源接口,token具有一定有效期限

流程图

在这里插入图片描述

流程解释

  • 流程:
    • 1.前端传unionid给后端
    • 2.后端生成jwt后,以token:unionid为key将token存入redis
    • 3.调用拦截路径外的接口需要提供token。
    • 4.后端拿到token后查询redis是否有相关token,有则放行
    @Autowired
    private RedisUtils redisUtils;

    /**
     * jwt + redis 实现登录授权
     * 流程:
     *      1.前端传unionid给后端
     *      2.后端生成jwt后,以token:unionid为key将token存入redis
     *      3.调用拦截路径外的接口需要提供token。
     *      4.后端拿到token后查询redis是否有相关token,有则放行
     * @param unionId
     * @return
     */
    @RequestMapping("/login")
    public AjaxResult login(@RequestParam("unionId") String unionId){
        // ----- 验证用户名密码 ------
        //JWT token
        String token = null;
        //生成token
        if(unionId != null || "".equals(unionId)) {
            try {
                Map claims = new HashMap<String, Integer>();
                String key = Base64.getEncoder().encodeToString(unionId.getBytes());
                claims.put("key", key);
                token = JwtUtils.createJwt(claims, "WxUser", 1000 * 60 * 60 * 72);
                log.info("生成的toen:" + token);
                //生成的token保存到redis中
                redisUtils.set("token:"+unionId,token,1000 * 60 * 60 * 72);
            }catch (Exception e){
                log.error("生成token失败: "+e.getMessage());
                throw new RuntimeException("生成token失败");
            }
        }
        return new AjaxResult().ok(token);
    }

2.登出使token失效

登出的时候需要删除相关token,使token失效

    /**
     * 用户登出并且使jwt token失效
     * 流程:
     *      1.前端传入unionid
     *      2.后端删除redis中key为token:unionid的token
     * @Param unionId
     * @return
     */
    @RequestMapping("/loginOut")
    public AjaxResult loginOut(@RequestParam("unionId")String unionId){
        String key = "token:"+unionId;
        redisUtils.del(key);
        return new AjaxResult().ok("登出成功");
    }

测试

token为空情况下请求资源接口

在这里插入图片描述

请求结果:

在这里插入图片描述

使用已经登出的token去请求资源接口

在这里插入图片描述

3.续期token

  • 流程:

    • 1.前端传入unionid和续期时间

    • 2.后端重新设置token失效时间为:剩余时间 + 指定失效时间

    /**
     * 续期token
     * 流程:
     *      1.前端传入unionid和续期时间
     *      2.后端重新设置token失效时间为:剩余时间 + 指定失效时间
     * @param unionId
     * @return
     */
    @RequestMapping("/renewalToken")
    public AjaxResult renewalToken(@RequestParam("unionId")String unionId,
                                   @RequestParam("time")long time){
        String key = "token:"+unionId;
        if(redisUtils.get(key) == null){
            return new AjaxResult().ok("token不存在");
        }
        long oldTime = redisUtils.getExpire(key);
        log.info("oldTime:"+oldTime);
        boolean b = redisUtils.expire(key,oldTime+time);
        return b == true ? new AjaxResult().ok("续期成功") : new AjaxResult().error("续期失败");
    }

申请新的token

在这里插入图片描述

剩余过期时间

在这里插入图片描述

续期

在这里插入图片描述

续期后的token过期时间

在这里插入图片描述

4.获取新的token

当token失效,前端还可以请求这个restful接口获取有效token,流程和登录授权接口差不多

    /**
     * 获取新的token
     * 流程:
     *      1.前端传入unionid后端刷新token
     *      2.后端刷新redis缓存
     * @param unionId
     * @return
     */
    @GetMapping(value = "/token/{unionId}")
    public AjaxResult getNewToken(@PathVariable(name = "unionId") String unionId) {
        String token = null;
        if (StringUtils.isBlank(unionId)) {
            return new AjaxResult(502, "unionId为空");
        }
        //获取token
        Map claims = new HashMap<String, Integer>();
        //在claims中添加通过base64加密过的unionId
        String encodeUnionid = Base64.getEncoder().encodeToString(unionId.getBytes());
        claims.put("key", encodeUnionid);
        //72h过期
        try {
            token = JwtUtils.createJwt(claims, "wxuser", 1000 * 60 * 60 * 72);
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (!StringUtils.isBlank(token)) {
            redisUtils.set("token:"+unionId,token,1000 * 60 * 60 * 72);
            return new AjaxResult().ok(token);
        }
        return new AjaxResult().error("无法获取token");
    }
  • 6
    点赞
  • 67
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
JWTRedis 都可以用于实现 Token 校验,但具体的实现方式有所不同,下面分别介绍一下它们的优劣。 JWT 实现 Token 校验的优点: 1. 无状态:JWT 实现 Token 校验时不需要在服务器端存储任何信息,这样可以减轻服务器的负担,提高系统的性能。 2. 轻量级:JWT Token 的体积较小,可以减少网络传输的开销。 3. 可扩展性:JWT 可以很容易地与其他技术集成,如 OAuth、SAML 等,可以实现单点登录和跨域身份验证。 JWT 实现 Token 校验的缺点: 1. 无法注销:一旦颁发了 JWT,就无法撤回或注销,除非等到 JWT 过期或更改密钥。 2. 密钥管理困难:JWT 的安全性依赖于密钥的保护,如果密钥泄露或被攻击者获取,就可能会导致系统的安全问题。 3. 数据量较大:由于 JWT 包含了用户信息和签名等信息,因此数据量较大,可能会影响网络传输的性能。 Redis 实现 Token 校验的优点: 1. 数据存储在内存中:Redis 的数据存储在内存中,可以快速地对 Token 进行校验。 2. 高可用性:Redis 支持数据复制和故障转移,可以保证系统的高可用性。 3. 可扩展性:Redis 支持分布式部署和数据复制,可以实现数据的高可用和扩展性。 Redis 实现 Token 校验的缺点: 1. 数据存储成本高:Redis 的数据存储在内存中,如果数据量过大,可能会导致内存不足的问题。 2. 数据持久化成本高:Redis 的数据持久化可能会影响系统的性能,尤其是在大数据量或高并发的情况下。 3. 高可用性需要额外配置:Redis 的高可用性需要额外的配置和维护,可能会增加运维的成本。 综上所述,JWTRedis 都有其优点和缺点,具体的选择应根据具体的场景和需求进行考虑。如果需要实现无状态的 Token 校验并且数据量较小,可以考虑使用 JWT;如果需要实现高可用的 Token 校验并且数据量较大,可以考虑使用 Redis
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值