天天看点

java jwt token reids_SpringBoot结合JWT+Shiro+Redis实现token无状态登录授权-Go语言中文社区...

SpringBoot结合JWT+Shiro+Redis实现token无状态登录授权

一、引言

​在微服务中我们一般采用的是无状态登录,而传统的session方式,在前后端分离的微服务架构下,如继续使用则必将要解决跨域sessionId问题、集群session共享问题等等。这显然是费力不讨好的,而整合shiro,却很不恰巧的与我们的期望有所违背,原因:

(1)shiro默认的拦截跳转都是跳转url页面,而前后端分离后,后端并无权干涉页面跳转。

(2)shiro默认使用的登录拦截校验机制恰恰就是使用的session。

这当然不是我们想要的,因此如需使用shiro,我们就需要对其进行改造,那么要如何改造呢?我们可以在整合shiro的基础上自定义登录校验,继续整合JWT,或者oauth2.0等,使其成为支持服务端无状态登录,即token登录。

二、相关说明

2.1. Shiro + JWT实现无状态鉴权机制

1. 首先post用户名与密码到login进行登入,如果成功在请求头Header返回一个加密的Authorization,失败的话直接返回未登录,以后访问都带上这个Authorization即可。

2. 鉴权流程主要是要重写shiro的入口过滤器BasicHttpAuthenticationFilter,在此基础上进行拦截、token验证授权等操作

2.2. 关于AccessToken及RefreshToken概念说明

1. AccessToken:用于接口传输过程中的用户授权标识,客户端每次请求都需携带,出于安全考虑通常有效时长较短。

2. RefreshToken:与AccessToken为共生关系,一般用于刷新AccessToken,保存于服务端,客户端不可见,有效时长较长。

2.3. 关于Redis中保存RefreshToken信息(做到JWT的可控性)

1. 登录认证通过后返回AccessToken信息(在AccessToken中保存当前的时间戳和帐号),同时在Redis中设置一条以帐号为Key,Value为当前时间戳(登录时间)的RefreshToken,现在认证时必须AccessToken没失效以及Redis存在所对应的RefreshToken,且RefreshToken时间戳和AccessToken信息中时间戳一致才算认证通过,这样可以做到JWT的可控性,如果重新登录获取了新的AccessToken,旧的AccessToken就认证不了,因为Redis中所存放的的RefreshToken时间戳信息只会和最新的AccessToken信息中携带的时间戳一致,这样每个用户就只能使用最新的AccessToken认证。

2. Redis的RefreshToken也可以用来判断用户是否在线,如果删除Redis的某个RefreshToken,那这个RefreshToken所对应的AccessToken之后也无法通过认证了,就相当于控制了用户的登录,可以剔除用户

2.4. 关于根据RefreshToken自动刷新AccessToken

1. 本身AccessToken的过期时间为5分钟,RefreshToken过期时间为30分钟,当登录后时间过了5分钟之后,当前AccessToken便会过期失效,再次带上AccessToken访问JWT会抛出TokenExpiredException异常说明Token过期,开始判断是否要进行AccessToken刷新,首先redis查询RefreshToken是否存在,以及时间戳和过期AccessToken所携带的时间戳是否一致,如果存在且一致就进行AccessToken刷新。

2. 刷新后新的AccessToken过期时间依旧为5分钟,时间戳为当前最新时间戳,同时也设置RefreshToken中的时间戳为当前最新时间戳,刷新过期时间重新为30分钟过期,最终将刷新的AccessToken存放在Response的Header中的Authorization字段返回。

3. 同时前端进行获取替换,下次用新的AccessToken进行访问即可。

三、项目准备配置

项目结构:

java jwt token reids_SpringBoot结合JWT+Shiro+Redis实现token无状态登录授权-Go语言中文社区...

pom.xml

该项目要用到的组件有java-jwt、json、shiro-spring、spring-boot-starter-data-redis等。

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

4.0.0

org.springframework.boot

spring-boot-starter-parent

2.2.2.RELEASE

com.ljnt

blog

0.0.1-SNAPSHOT

blog

Demo project for Spring Boot

1.8

org.springframework.boot

spring-boot-starter-data-jpa

org.springframework.boot

spring-boot-starter-thymeleaf

org.springframework.boot

spring-boot-starter-web

org.springframework.boot

spring-boot-devtools

runtime

true

org.springframework.boot

spring-boot-starter-test

test

org.junit.vintage

junit-vintage-engine

com.auth0

java-jwt

3.9.0

org.json

json

20190722

org.apache.shiro

shiro-spring

1.4.0

org.springframework.boot

spring-boot-starter-data-redis

org.springframework.boot

spring-boot-maven-plugin

application.yml

主要是配置redis,需要用到模板、数据库、日志的,请看注释

server:

port: 8181

#spring:

# thymeleaf:

# mode: HTML5

#

# datasource:

# driver-class-name: com.mysql.jdbc.Driver

# url: jdbc:mysql://localhost:3306/blog?useSSL=false&characterEncoding=utf-8

# username: root

# password:

redis:

host: localhost

port: 6379

jedis:

pool:

max-active: -1

max-wait: 3000ms

timeout: 3000ms

#logging:

# level:

# root: info

# com.ljnt: debug

# file: log/imcoding.log

四、实现颁发token

实现颁发token需要用到JWT和Redis,所以我们需要配置Redis和实现工具类。

4.1. 配置Redis:RedisConfig

@Configuration

public class RedisConfig {

@Bean

public RedisTemplate redisTemplate(RedisConnectionFactory factory) {

RedisTemplate template = new RedisTemplate<>();

// 配置连接工厂

template.setConnectionFactory(factory);

//使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)

Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class);

ObjectMapper om = new ObjectMapper();

// 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public

om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);

// 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常

//om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL);

jacksonSeial.setObjectMapper(om);

// 值采用json序列化

template.setValueSerializer(jacksonSeial);

//使用StringRedisSerializer来序列化和反序列化redis的key值

template.setKeySerializer(new StringRedisSerializer());

// 设置hash key 和value序列化模式

template.setHashKeySerializer(new StringRedisSerializer());

template.setHashValueSerializer(jacksonSeial);

template.afterPropertiesSet();

return template;

}

@Bean

public HashOperations hashOperations(RedisTemplate redisTemplate) {

return redisTemplate.opsForHash();

}

@Bean

public ValueOperations valueOperations(RedisTemplate redisTemplate) {

return redisTemplate.opsForValue();

}

@Bean

public ListOperations listOperations(RedisTemplate redisTemplate) {

return redisTemplate.opsForList();

}

@Bean

public SetOperations setOperations(RedisTemplate redisTemplate) {

return redisTemplate.opsForSet();

}

@Bean

public ZSetOperations zSetOperations(RedisTemplate redisTemplate) {

return redisTemplate.opsForZSet();

}

}

4.2. 编写工具类

RedisUtil,这里主要用到redisTemplate的一些方法,代码没有全部给出来,可以根据redisTemplate方法去编写或者看我的源码。。

@Component

public class RedisUtil {

@Autowired

private static RedisTemplate redisTemplate;

public RedisUtil(RedisTemplate redisTemplate) {

this.redisTemplate = redisTemplate;

}

public static boolean expire(String key,long time){

try {

if(time>0){

redisTemplate.expire(key, time, TimeUnit.SECONDS);

}

return true;

} catch (Exception e) {

e.printStackTrace();

return false;

}

}

public static long getExpire(String key){

return redisTemplate.getExpire(key,TimeUnit.SECONDS);

}

public static boolean hasKey(String key){

try {

return redisTemplate.hasKey(key);

} catch (Exception e) {

e.printStackTrace();

return false;

}

}

//=========代码太长,省略代码

}

TokenUtil,主要实现token的签发、验证和数据解析。

public class TokenUtil {

//这里的token属性配置最好写在配置文件中,这里为了方面直接写成静态属性

public static final long EXPIRE_TIME= 5*60*1000;//token到期时间5分钟,毫秒为单位

public static final long REFRESH_EXPIRE_TIME=30*60;//RefreshToken到期时间为30分钟,秒为单位

private static final String TOKEN_SECRET="ljdyaishijin**3nkjnj??"; //密钥盐

public static String sign(String account,Long currentTime){

String token=null;

try {

Date expireAt=new Date(currentTime+EXPIRE_TIME);

token = JWT.create()

.withIssuer("auth0")//发行人

.withClaim("account",account)//存放数据

.withClaim("currentTime",currentTime)

.withExpiresAt(expireAt)//过期时间

.sign(Algorithm.HMAC256(TOKEN_SECRET));

} catch (IllegalArgumentException|JWTCreationException je) {

}

return token;

}

public static Boolean verify(String token) throws Exception{

JWTVerifier jwtVerifier=JWT.require(Algorithm.HMAC256(TOKEN_SECRET)).withIssuer("auth0").build();//创建token验证器

DecodedJWT decodedJWT=jwtVerifier.verify(token);

System.out.println("认证通过:");

System.out.println("account: " + decodedJWT.getClaim("account").asString());

System.out.println("过期时间: " + decodedJWT.getExpiresAt());

return true;

}

public static String getAccount(String token){

try{

DecodedJWT decodedJWT=JWT.decode(token);

return decodedJWT.getClaim("account").asString();

}catch (JWTCreationException e){

return null;

}

}

public static Long getCurrentTime(String token){

try{

DecodedJWT decodedJWT=JWT.decode(token);

return decodedJWT.getClaim("currentTime").asLong();

}catch (JWTCreationException e){

return null;

}

}

}

4.3. 编写登录接口:LoginController

登录成功颁发token,生成RefreshToken保存在redis,返回在Header的Authorization中。

@Controller

public class LoginController {

@Autowired

RedisUtil redisUtil;

@PostMapping("/login")

@ResponseBody

public Result login(String username, String password, HttpServletResponse response) throws JsonProcessingException {

User user=new User();

user.setUsername(username);

user.setPassword(password);

//去数据库拿密码验证用户名密码,这里直接验证

if(username.equals("admin")){

if (!password.equals("admin")){

return new Result(400,"密码错误");

}

}else if (username.equals("user")){

if (!password.equals("user")){

return new Result(400版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/ljlj8888/article/details/104826421

站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。

上一篇: SWT对话框