JWT实现登陆认证及Token自动续期

JWT 使用入门(二)token有效期 生成token,设置有效时间1分钟,其中密钥设置为"itcast"注意签发时间需要小于过期时间。 阅读详情

过去这段时间主要负责了项目中的用户管理模块,用户管理模块会涉及到加密及认证流程,加密已经在前面的文章中介绍了,可以阅读用户管理模块:如何保证用户数据安全。今天就来讲讲认证功能的技术选型及实现。技术上没啥难度当然也没啥挑战,但是对一个原先没写过认证功能的菜鸡甜来说也是一种锻炼吧

技术选型

要实现认证功能,很容易就会想到JWT或者session,但是两者有啥区别?各自的优缺点?应该Pick谁?夺命三连

区别

基于session和基于JWT的方式的主要区别就是用户的状态保存的位置,session是保存在服务端的,而JWT是保存在客户端的

认证流程
基于session的认证流程
  • 用户在浏览器中输入用户名和密码,服务器通过密码校验后生成一个session并保存到数据库
  • 服务器为用户生成一个sessionId,并将具有sesssionId的cookie放置在用户浏览器中,在后续的请求中都将带有这个cookie信息进行访问
  • 服务器获取cookie,通过获取cookie中的sessionId查找数据库判断当前请求是否有效
基于JWT的认证流程
  • 用户在浏览器中输入用户名和密码,服务器通过密码校验后生成一个token并保存到数据库
  • 前端获取到token,存储到cookie或者local storage中,在后续的请求中都将带有这个token信息进行访问
  • 服务器获取token值,通过查找数据库判断当前token是否有效
优缺点
  • JWT保存在客户端,在分布式环境下不需要做额外工作。而session因为保存在服务端,分布式环境下需要实现多机数据共享
  • session一般需要结合Cookie实现认证,所以需要浏览器支持cookie,因此移动端无法使用session认证方案
安全性
  • JWT的payload使用的是base64编码的,因此在JWT中不能存储敏感数据。而session的信息是存在服务端的,相对来说更安全

image.png

如果在JWT中存储了敏感信息,可以解码出来非常的不安全

性能
  • 经过编码之后JWT将非常长,cookie的限制大小一般是4k,cookie很可能放不下,所以JWT一般放在local storage里面。并且用户在系统中的每一次http请求都会把JWT携带在Header里面,HTTP请求的Header可能比Body还要大。而sessionId只是很短的一个字符串,因此使用JWT的HTTP请求比使用session的开销大得多
一次性

无状态是JWT的特点,但也导致了这个问题,JWT是一次性的。想修改里面的内容,就必须签发一个新的JWT

  • 无法废弃
    一旦签发一个JWT,在到期之前就会始终有效,无法中途废弃。若想废弃,一种常用的处理手段是结合redis
  • 续签
    如果使用JWT做会话管理,传统的cookie续签方案一般都是框架自带的,session有效期30分钟,30分钟内如果有访问,有效期被刷新至30分钟。一样的道理,要改变JWT的有效时间,就要签发新的JWT。最简单的一种方式是每次请求刷新JWT,即每个HTTP请求都返回一个新的JWT。这个方法不仅暴力不优雅,而且每次请求都要做JWT的加密解密,会带来性能问题。另一种方法是在redis中单独为每个JWT设置过期时间,每次访问时刷新JWT的过期时间
选择JWT或session

我投JWT一票,JWT有很多缺点,但是在分布式环境下不需要像session一样额外实现多机数据共享,虽然seesion的多机数据共享可以通过粘性session、session共享、session复制、持久化session、terracoa实现seesion复制等多种成熟的方案来解决这个问题。但是JWT不需要额外的工作,使用JWT不香吗?且JWT一次性的缺点可以结合redis进行弥补。扬长补短,因此在实际项目中选择的是使用JWT来进行认证

功能实现

JWT所需依赖
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>3.10.3</version>
</dependency>
复制代码
JWT工具类
public class JWTUtil {
    private static final Logger logger = LoggerFactory.getLogger(JWTUtil.class);

    //私钥
    private static final String TOKEN_SECRET = "123456";

    /**
     * 生成token,自定义过期时间 毫秒
     *
     * @param userTokenDTO
     * @return
     */
    public static String generateToken(UserTokenDTO userTokenDTO) {
        try {
            // 私钥和加密算法
            Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
            // 设置头部信息
            Map<String, Object> header = new HashMap<>(2);
            header.put("Type", "Jwt");
            header.put("alg", "HS256");

            return JWT.create()
                    .withHeader(header)
                    .withClaim("token", JSONObject.toJSONString(userTokenDTO))
                    //.withExpiresAt(date)
                    .sign(algorithm);
        } catch (Exception e) {
            logger.error("generate token occur error, error is:{}", e);
            return null;
        }
    }

    /**
     * 检验token是否正确
     *
     * @param token
     * @return
     */
    public static UserTokenDTO parseToken(String token) {
        Algorithm algorithm = Algorithm.HMAC256(TOKEN_SECRET);
        JWTVerifier verifier = JWT.require(algorithm).build();
        DecodedJWT jwt = verifier.verify(token);
        String tokenInfo = jwt.getClaim("token").asString();
        return JSON.parseObject(tokenInfo, UserTokenDTO.class);
    }
}
复制代码

说明:

  • 生成的token中不带有过期时间,token的过期时间由redis进行管理
  • UserTokenDTO中不带有敏感信息,如password字段不会出现在token中
Redis工具类
public final class RedisServiceImpl implements RedisService {
    /**
     * 过期时长
     */
    private final Long DURATION = 1 * 24 * 60 * 60 * 1000L;

    @Resource
    private RedisTemplate redisTemplate;

    private ValueOperations<String, String> valueOperations;

    @PostConstruct
    public void init() {
        RedisSerializer redisSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(redisSerializer);
        redisTemplate.setValueSerializer(redisSerializer);
        redisTemplate.setHashKeySerializer(redisSerializer);
        redisTemplate.setHashValueSerializer(redisSerializer);
        valueOperations = redisTemplate.opsForValue();
    }

    @Override
    public void set(String key, String value) {
        valueOperations.set(key, value, DURATION, TimeUnit.MILLISECONDS);
        log.info("key={}, value is: {} into redis cache", key, value);
    }

    @Override
    public String get(String key) {
        String redisValue = valueOperations.get(key);
        log.info("get from redis, value is: {}", redisValue);
        return redisValue;
    }

    @Override
    public boolean delete(String key) {
        boolean result = redisTemplate.delete(key);
        log.info("delete from redis, key is: {}", key);
        return result;
    }

    @Override
    public Long getExpireTime(String key) {
        return valueOperations.getOperations().getExpire(key);
    }
}
复制代码

RedisTemplate简单封装

业务实现
登陆功能
public Stringlogin(LoginUserVO loginUserVO) {
    //1.判断用户名密码是否正确
    UserPO userPO = userMapper.getByUsername(loginUserVO.getUsername());
    if (userPO == null) {
        throw new UserException(ErrorCodeEnum.TNP1001001);
    }
    if (!loginUserVO.getPassword().equals(userPO.getPassword())) {
        throw new UserException(ErrorCodeEnum.TNP1001002);
    }

    //2.用户名密码正确生成token
    UserTokenDTO userTokenDTO = new UserTokenDTO();
    PropertiesUtil.copyProperties(userTokenDTO, loginUserVO);
    userTokenDTO.setId(userPO.getId());
    userTokenDTO.setGmtCreate(System.currentTimeMillis());
    String token = JWTUtil.generateToken(userTokenDTO);

    //3.存入token至redis
    redisService.set(userPO.getId(), token);
    return token;
}
复制代码

说明:

  • 判断用户名密码是否正确
  • 用户名密码正确则生成token
  • 将生成的token保存至redis
登出功能
public boolean loginOut(String id) {
     boolean result = redisService.delete(id);
     if (!redisService.delete(id)) {
        throw new UserException(ErrorCodeEnum.TNP1001003);
     }

     return result;
}
复制代码

将对应的key删除即可

更新密码功能
public StringupdatePassword(UpdatePasswordUserVO updatePasswordUserVO) {
    //1.修改密码
    UserPO userPO = UserPO.builder().password(updatePasswordUserVO.getPassword())
            .id(updatePasswordUserVO.getId())
            .build();
    UserPO user = userMapper.getById(updatePasswordUserVO.getId());
    if (user == null) {
        throw new UserException(ErrorCodeEnum.TNP1001001);
    }

    if (userMapper.updatePassword(userPO) != 1) {
        throw new UserException(ErrorCodeEnum.TNP1001005);
    }
    //2.生成新的token
    UserTokenDTO userTokenDTO = UserTokenDTO.builder()
            .id(updatePasswordUserVO.getId())
            .username(user.getUsername())
            .gmtCreate(System.currentTimeMillis()).build();
    String token = JWTUtil.generateToken(userTokenDTO);
    //3.更新token
    redisService.set(user.getId(), token);
    return token;
}
复制代码

说明:
更新用户密码时需要重新生成新的token,并将新的token返回给前端,由前端更新保存在local storage中的token,同时更新存储在redis中的token,这样实现可以避免用户重新登陆,用户体验感不至于太差

其他说明
  • 在实际项目中,用户分为普通用户和管理员用户,只有管理员用户拥有删除用户的权限,这一块功能也是涉及token操作的,但是我太懒了,demo工程就不写了
  • 在实际项目中,密码传输是加密过的
拦截器类
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                             Object handler) throws Exception {
    String authToken = request.getHeader("Authorization");
    String token = authToken.substring("Bearer".length() + 1).trim();
    UserTokenDTO userTokenDTO = JWTUtil.parseToken(token);
    //1.判断请求是否有效
    if (redisService.get(userTokenDTO.getId()) == null 
            || !redisService.get(userTokenDTO.getId()).equals(token)) {
        return false;
    }

    //2.判断是否需要续期
    if (redisService.getExpireTime(userTokenDTO.getId()) < 1 * 60 * 30) {
        redisService.set(userTokenDTO.getId(), token);
        log.error("update token info, id is:{}, user info is:{}", userTokenDTO.getId(), token);
    }
    return true;
}
复制代码

说明:
拦截器中主要做两件事,一是对token进行校验,二是判断token是否需要进行续期
token校验:

  • 判断id对应的token是否不存在,不存在则token过期
  • 若token存在则比较token是否一致,保证同一时间只有一个用户操作

token自动续期: 为了不频繁操作redis,只有当离过期时间只有30分钟时才更新过期时间

拦截器配置类
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(authenticateInterceptor())
                .excludePathPatterns("/logout/**")
                .excludePathPatterns("/login/**")
                .addPathPatterns("/**");
    }

    @Bean
    public AuthenticateInterceptor authenticateInterceptor() {
        return new AuthenticateInterceptor();
    }
}
复制代码
写在最后

若有纰漏不足,欢迎指出

来源:https://juejin.cn/post/6932702419344162823

点个赞在走

Redis实现JWT(JSON Web TOKEN)自动延长TOKEN过期时间 JWT是JSON WEB TOKEN的简写,常用于生成及校验Token。常见的使用场景为:用户携带name和秘钥访问后端服务器,应用后端在校验通过后使用JWT生成并返回一串Token,后续用户只需要携带此Token就可以访问服务器,在此不多赘述。本文目的是基于redis实现token自动更新其过期时间,在校验用户姓名和密码后使用JWT工具类生成会过期的Token,当用户携带此Token访问服务器后会自动延长其过期时间。 阅读详情

相关推荐

基于JWT实现用户身份认证

JWT(JSON WEB TOKEN) 是目前最流行的跨域认证解决方案,是一种基于Token认证授权机制,JWT自身包含了身份验证所需要的所有信息,因此我们服务端不需要存储Session信息,这显然增加了系统的可用性和伸缩性,大大减轻了服务端压力。

子悠のziyou 1666

JWT - 生成token并配置过期时间

话说项目配置了Token, 就像是炒菜加味精。 一滴加进去,鲜味倍增, 保证安全又好吃。

产品经理克星(狗头) 2776

Linux系统杀毒软件深度评测:8款主流工具对比(2025最新版)

本文针对Linux服务器/桌面环境,实测8款杀毒软件,涵盖开源免费、企业级防护、轻量级工具等类型,附保姆级安装教程和避坑指南,新手也能5分钟选对工具!

awei0916的专栏 1万+

jwt----token过期时间设置

【代码】jwt----token过期时间设置。

淘淘桃的博客 2496

数字音频接口之I2S总线协议详解

1、SDATA,也就是串行数据信号,使用二进制传输数字信号。请注意数据的传输形式为补码的方式2、例如音频的采样率是8K,一s内存采样8K个点,一个点包含一个左声道,一个右声道,所以LRCK是8K,BCLK是2×采样频率×采样位数3、同时双向(TX和RX)音频流:串行数据同步移位到时钟信号SCK和LRCK。在SCK的下降沿将TX数据写入SDOUT引脚,并在SCK的上升沿从SDIN引脚读取RX数据。始终首先发送最高有效位(MSB);TX和RX均可在主模式和从模式下使用。

卓学课堂 3万+

PCB阻焊工艺全解析:从油墨选择到显影测试的完整指南

本文深入解析PCB阻焊工艺全流程,从材料科学角度对比液态光成像(LPI)油墨、干膜阻焊等核心材料特性,系统阐述前处理、涂覆、预烤曝光、显影固化等关键步骤的工艺要点与参数控制。重点探讨了如何通过优化油墨选择与工艺参数,解决常见缺陷,提升PCB的可靠性、美观度及可制造性,为工程师提供从理论到产线实战的完整指南。

s3t4u的博客 553

JWT设置过期时间隐藏的坑

原来当你尝试计算2592000 * 1000时,实际的结果2592000000已经超出了int类型的最大值。因此,在Java中,这个计算会导致整数溢出,而溢出的结果是一个负数,即你看到的-1702967296。过期时间为2024-08-24T07:33:11,当前时间为2024-09-13T00:36:00,token过期了,但是我明明是获取token后马上执行的解密,那么问题应该在设置过期时间。看着没啥问题,过期时间是在当前时间上加的,而且这个时间设置得挺长的。他给的回答看不出问题。

weixin_38501809的博客 1356

零成本实现飞牛 OS 内网穿透:CloudFlare 隧道搭建全攻略

一、引言:突破内网限制的最优解当你的内网环境中部署了飞牛 OS 系统(无论是老旧电脑还是 NAS 设备),想要实现公网访问时,传统方案往往面临两大痛点:公网 IP 申请困难且成本高DDNS 动态解析配置复杂专业内网穿透工具存在付费门槛今天为大家带来零成本解决方案:通过免费域名搭配 CloudFlare 隧道技术,无需公网 IP 即可轻松实现飞牛 OS 服务的公网访问。全程操作可视化,新手也能快速上手!二、前期准备:必备工具清单(1)域名准备。

Honmaple的博客 7493

关于JAVA JWT实现登陆认证及Token自动续期的理解

JAVA JWT实现登陆认证及Token自动续期

探索 Java 编程的技巧、最佳实践和解决方案。本博客提供针对 Java 开发者的实用指南,涵盖从基础到高级话题,旨在提升编程效率和技能。 1156

VsCode+JLink+GCC替代Keil开发GD32程序-个人总结

对个人使用vscode+Jlink开发GD32程序做了总结,供需要者参考。

MyCollege1999的博客 2763

从零到一,打造你的无人机侦察兵!树莓派+Python+MQTT,实时目标检测全攻略

物联网、人工智能与计算机视觉三大前沿技术的深度融合。

u013558123的博客 1473

java不用缓存实现token续签_JWT实现登陆认证及Token自动续期

过去这段时间主要负责了项目中的用户管理模块,用户管理模块会涉及到加密及认证流程,加密已经在前面的文章中介绍了,可以阅读:https://juejin.cn/post/6916150628955717646今天就来讲讲认证功能的技术选型及实现。技术上没啥难度当然也没啥挑战,但是对一个原先没写过认证功能的菜鸡甜来说也是一种锻炼吧技术选型要实现认证功能,很容易就会想到JWT或者session,但是两者有...

weixin_36234738的博客 1654

多周期路径约束(set_multicycle_path )

1.由慢到快的多周期约束 如下图所示,发射时钟周期是接受时钟周期的4倍。默认情况下,按照1T原则来进行静态时序分析和布局布线的。即建立时间是在发射沿后的第一个捕获沿检查,而保持时间是同一时刻下源时钟和捕获时钟的触发边沿下检查的(建立时间检查边沿的前一个触发沿)。 但是这样导致的问题就是该路径过于苛刻,为了满足这条路径的要求,不得不放弃其他路径。或者根本没办法保证这个默认的约束。总之最终造成...

Arist. 5506

JWT 实现登录认证 + Token 自动续期方案,这才是正确的使用姿势

过去这段时间主要负责了项目中的用户管理模块,用户管理模块会涉及到加密及认证流程,加密已经在前面的文章中介绍了。今天就来讲讲认证功能的技术选型及实现。技术上没啥难度当然也没啥挑战,但是对一个原先没写过认证功能的菜鸡甜来说也是一种锻炼吧。

z1ztai的博客 751

Unity GameObject 核心机制深度解析:从底层结构到工程实践

从底层机制讲透 Unity GameObject:托管壳加原生芯的双层对象模型如何制造假 null 陷阱、Transform 层级与组件通信的原理、生命周期回调的确切顺序。文章同时给出 Instantiate 成本分析与对象池等高频场景的工程实践。

游戏人生 40

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

JWT 登录认证 + Token 自动续期方案,写得太好了!

作者:何甜甜在吗过去这段时间主要负责了项目中的用户管理模块,用户管理模块会涉及到加密及认证流程,加密已经在前面的文章中介绍了,可以阅读用户管理模块:如何保证用户数据安全。今天就来讲讲认证功能的技术选型及实现。技术上没啥难度当然也没啥挑战,但是对一个原先没写过认证功能的菜鸡甜来说也是一种锻炼吧。

weixin_68742131的博客 6181

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

JWT双令牌认证实现无感Token自动续约

JWT双令牌认证实现无感Token自动续约

abcafdsgr123456789的博客 1766

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

【SpringBoot】JWT+Token之Token自动续期

Springboot+jwt+token实现双token认证

低调做人,低调编码,神码都是浮云 2231

【黑马点评优化】1-使用JWT登录认证+redis实现自动续期

【黑马点评优化】1-使用JWT登录认证+redis实现自动续期

m0_52031708的博客 4927

redis + jwt实现token认证

文章目录业务场景拦截器1.登录授权获取token2.登出使token失效3.续期token4.获取新的token 业务场景 在前后端分离的场景下,越来越多的项目使用token作为接口的安全机制,APP或者web端使用token与后端接口交互,以达到安全的目的 拦截器 拦截器 获取前端请求的token 判断token是否为空 token不为空,则查询redis缓存是否有相关token 有相关t...

kevin的博客 8798

JWT生成token及过期和自动续期

效果展示: token时间续签 思路: 1:在登录接口中 如果校验账号密码成功 则根据用户id和用户类型创建jwt token(有效期设置为-1,即永不过期),得到A 2:更新登录日期(当前时间new Date()即可)(业务上可选),得到B 3:在redis中缓存key为ACCESS_TOKEN:userId:A(加上A是为了防止用户多个客户端登录 造成token覆盖),value为

jq1223的博客 7097

实现Token自动续约JWT双令牌认证

JWT(JSON Web Token)是一种开放标准的安全令牌格式,用于在各方之间安全传输JSON格式的信息。它由三部分组成:Header(头部)、Payload(负载)和Signature(签名),通过数字签名确保信息完整性。JWT认证流程中,用户登录后获得Token,后续请求携带该Token进行身份验证。为解决Token频繁过期问题,采用双令牌方案:短期有效的AccessToken(2小时)用于资源访问,长期有效的RefreshToken(7天)用于自动续期AccessToken。这种机制既保证了安全性

2509_93910070的博客 687
上一篇: SpringBoot中如何解决Redis的缓存穿透、缓存击穿、缓存雪崩?
下一篇: 细数List的10个大坑,看看你踩过多少?
码趣资源网
博客等级 码龄4年 268粉丝 518原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值