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

40 篇文章 1 订阅
4 篇文章 0 订阅

效果展示:

token时间续签

思路:

1:在登录接口中 如果校验账号密码成功 则根据用户id和用户类型创建jwt token(有效期设置为-1,即永不过期),得到A
2:更新登录日期(当前时间new Date()即可)(业务上可选),得到B
3:在redis中缓存key为ACCESS_TOKEN:userId:A(加上A是为了防止用户多个客户端登录 造成token覆盖),value为B的毫秒数(转换成字符串类型),过期时间为7天(7 * 24 * 60 * 60)
4:在登录结果中返回json格式为{“result”:“success”,“token”: A}
5:用户在接口请求header中携带token进行登录,后端在所有接口前置拦截器进行拦截,作用是解析token 拿到userId和用户类型(用户调用业务接口只需要传token即可), 如果解析失败(抛出SignatureException),则返回json(code = 0 ,info= Token验证不通过, errorCode = ‘1001’); 此外如果解析成功,验证redis中key为ACCESS_TOKEN:userId:A 是否存在 如果不存在 则返回json(code = 0 ,info= 会话过期请重新登录, errorCode = ‘1002’); 如果缓存key存在,则自动续7天超时时间(value不变),实现频繁登录用户免登陆。
6:把userId和用户类型放入request参数中 接口方法中可以直接拿到登录用户信息
7:如果是修改密码或退出登录 则废除access_tokens(删除key)

文章摘自

后台实现:

1:导入redis jar包

 <!-- redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

application.properties配置文件:

#用户
spring.datasource.username=root
#密码
spring.datasource.password=123123
#连接是数据库
spring.datasource.url=jdbc:mysql://localhost:3306/jin1?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#打印Sql
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
#扫描.xml文件
mybatis-plus.mapper-locations=classpath*:xml/*.xml


# Redis 服务器地址
spring.redis.host=localhost
# Redis 服务器连接端?
spring.redis.port=6379
# 连接池最大连接数(使用负值表示没有限制) 默认 8
spring.redis.lettuce.pool.max-active=100
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
spring.redis.lettuce.pool.max-wait=PT10S
# 连接池中的最大空闲连接 默认 8
spring.redis.lettuce.pool.max-idle=30
# 连接池中的最小空闲连接 默认 0
spring.redis.lettuce.pool.min-idle=1
#链接超时时间
spring.redis.timeout=PT10S

登录控制器中:

package com.ff.controller;

import com.ff.common.ResultCode;
import com.ff.common.ResultObj;
import com.ff.entity.User;
import com.ff.service.UserService;
import com.ff.util.JWTUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping("/login")
@CrossOrigin
public class LoginController {

    @Autowired
    private UserService userService;

    private  static final String ACCESS_TOKEN="ACCESS_TOKEN";

    @Autowired
    private RedisTemplate redisTemplate;

    //登录
    @PostMapping("login")
     public ResultObj login(String username,String password) {
        //判断用户名密码为空
        if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
            return ResultObj.error(ResultCode.USERNAME_PASSWORD_ISNULL);
        }

        //判断用户名是否存在
        User user = userService.queryUserByUsername(username);
        if (user == null) {
            return ResultObj.error(ResultCode.USER_NOEXIST);
        }

        //判断密码是否正确
        if (!password.equals(user.getPassword())) {
            return ResultObj.error(ResultCode.PASSWORD_ERROR);
        }

        //登录成功,生成token
        String token = JWTUtil.createToken(user);


        //获取当前时间的毫秒值
        String currentTime = String.valueOf(System.currentTimeMillis());
        //拼接存到redis中的Key中
        String accessKey=ACCESS_TOKEN+user.getId()+":"+token;
        //往拼接存到redis中的Key存value值
        redisTemplate.opsForValue().set(accessKey,currentTime);
        //设置redis key的过期时间 2分钟  可自定义
        redisTemplate.expire(accessKey,2, TimeUnit.MINUTES);

        return ResultObj.success(token);


    }


}

登录切面类代码:

package com.ff.common;

import com.ff.annotation.LoginAnnotation;
import com.ff.util.JWTUtil;
import io.jsonwebtoken.Claims;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.TimeUnit;

@Component
@Aspect
public class LoginAop {

    @Resource
    private HttpServletRequest request;

    private  static final String ACCESS_TOKEN="ACCESS_TOKEN";

    @Autowired
    private RedisTemplate redisTemplate;

    //扫描controller层所有自定义注解
    @Around(value = "execution(* com.fh.controller.*.*(..)) && @annotation(loginAnnotation)")
    public Object loginAround(ProceedingJoinPoint joinPoint, LoginAnnotation loginAnnotation) {
        Object proceed=null;
       //验证token的值
        String token = request.getHeader("Authorization-token");
        ResultObj resultObj = JWTUtil.verifToken(token);


        //失败的情况
        if(resultObj.getCode()!= 200){
            return resultObj;
        }

        //获取data中数据
        Claims claims = (Claims)resultObj.getData();
        //拼接存到redis中的Key中
        String accessKey=ACCESS_TOKEN+claims.get("id")+":"+token;

        //验证redis中的token得值是否存在
        if(!redisTemplate.hasKey(accessKey)){
            //登录失败
            return ResultObj.error(ResultCode.TOKEN_ERROR);
        }
        //如果redis中的token得值存在,续签
        //获取当前时间的毫秒值
        String currentTime = String.valueOf(System.currentTimeMillis());
        //往拼接存到redis中的Key存value值
        redisTemplate.opsForValue().set(accessKey,currentTime);
        //设置redis key的过期时间 2分钟
        redisTemplate.expire(accessKey,2, TimeUnit.MINUTES);




        try {
            //执行目标方法
             proceed = joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }


        return proceed;
    }



}

JWTUtil工具类:设置token时间

package com.ff.util;

import com.ff.common.ResultCode;
import com.ff.common.ResultObj;
import com.ff.entity.User;
import io.jsonwebtoken.*;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class JWTUtil {

    //生成Toke的方法

    public static String createToken(User user) {
        //token分为3部分
        //第一部分我们称它为头部(header),
        // 第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),
        // 第三部分是签证(signature).

        //头部
        Map<String, Object> headerMap = new HashMap<String, Object>();
        //HS256加密方式
        headerMap.put("alg", "HS256");
        headerMap.put("type", "JWT");

        //有效载荷
        Map<String, Object> payloadMap = new HashMap<String, Object>();
        payloadMap.put("username", user.getUsername());
        payloadMap.put("id", user.getId());


        //失效时间
        long timeMillis = System.currentTimeMillis();
        //设置token时间 999999999 毫秒=11.5740740625 天
        //6000000为1分钟
        long endTime = timeMillis + 6000000;

        //签证,签名
        String token = null;
        try {
            token = Jwts.builder()
                    .setHeader(headerMap)
                    .setClaims(payloadMap)
                    .setExpiration(new Date(endTime))
                    .signWith(SignatureAlgorithm.HS256, "rtet")
                    .compact();
            System.out.println(token);
        } catch (ExpiredJwtException e) {
            e.getClaims();
        }
        return token;
    }

    //解密token
    public static ResultObj verifToken(String token) {
        try {
            //通过TWTs的parser 方法 接受
            //根据人名key解密
            Claims claims = Jwts.parser().setSigningKey("rtet")
                    .parseClaimsJws(token)
                    .getBody();
            return ResultObj.success(claims);
        } catch (Exception e) {
          return ResultObj.error(ResultCode.TOKEN_ERROR);
        }

    }
}


最后使用俩个类序列化Redis参数,不然乱码

package com.ff.cache;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;

import java.nio.charset.Charset;

public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {

    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    private Class<T> clazz;

    public FastJsonRedisSerializer(Class<T> clazz) {
        super();
        this.clazz = clazz;
    }

    @Override
    public byte[] serialize(T t) throws SerializationException {
        if (null == t) {
            return new byte[0];
        }
        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
    }

    @Override
    public T deserialize(byte[] bytes) throws SerializationException {
        if (null == bytes || bytes.length <= 0) {
            return null;
        }
        String str = new String(bytes, DEFAULT_CHARSET);
        return (T) JSON.parseObject(str, clazz);
    }
}

package com.ff.cache;

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@EnableCaching
public class RedisCacheConfig extends CachingConfigurerSupport {


    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(
            RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();

        //使用fastjson序列化
        com.ff.cache.FastJsonRedisSerializer fastJsonRedisSerializer = new com.ff.cache.FastJsonRedisSerializer(Object.class);
        // value值的序列化采用fastJsonRedisSerializer
        template.setValueSerializer(fastJsonRedisSerializer);
        template.setHashValueSerializer(fastJsonRedisSerializer);
        // key的序列化采用StringRedisSerializer
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());

        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
    @Bean
    @ConditionalOnMissingBean(StringRedisTemplate.class)
    public StringRedisTemplate stringRedisTemplate(
            RedisConnectionFactory redisConnectionFactory) {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }


}

完整版页面

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
要使用hutool生成jwt token,你需要在项目中引入hutool-all的依赖。具体的依赖配置如下所示: ```xml <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.7.13</version> </dependency> ``` 在登录接口中,如果账号密码校验成功,你可以使用用户id和用户类型创建jwt token,并将其设置为永不过期。然后,你可以更新登录日期,并将其作为一个值(B)存储在redis中,以便后续验证。在登录结果中,你可以返回一个json格式的响应,其中包含"result"为"success","token"为A的键值对。用户在接口请求header中携带token进行登录,后端可以通过拦截器解析token,获取userId和用户类型。如果解析失败,则返回一个json响应,表示Token验证不通过。如果解析成功,后端可以验证redis中的缓存key是否存在。如果不存在,则返回一个json响应,表示会话过期请重新登录。如果缓存key存在,则可以自动续7天的超时时间,以实现频繁登录用户的免登陆功能。在接口方法中,你可以直接获取登录用户的信息,将userId和用户类型放入request参数中。如果用户修改密码或退出登录,你可以废除access_tokens,即删除对应的缓存key。\[2\] 请注意,以上是一个大致的流程,具体实现可能会根据你的项目需求有所不同。 #### 引用[.reference_title] - *1* [利用hutool生成和验证JWT的示例](https://blog.csdn.net/qq125281823/article/details/120960181)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [JWT生成token过期自动续期](https://blog.csdn.net/jq1223/article/details/114580020)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jq1223

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值