天天看點

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對話框