效果展示:

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;
    }


}

完整版页面

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐