關(guān)于SpringBoot創(chuàng)建存儲令牌的媒介類和過濾器的問題
之所以需要創(chuàng)建存儲令牌的媒介類,是因?yàn)楹竺娴膄ilter界面要使用。
一、創(chuàng)建ThreadLocalToken類
創(chuàng)建ThreadLocalToken類的目的:

在com.example.emos.wx.config.shiro中創(chuàng)建ThreadLocalToken類。
寫入如下代碼:
package com.example.emos.wx.config.shiro;
import org.springframework.stereotype.Component;
@Component
public class ThreadLocalToken {
private ThreadLocal local=new ThreadLocal();
//因?yàn)橐赥hreadLocal中保存令牌,所以需要setToken。
public void setToken(String token){
local.set(token);
}
public String getToken(){
return (String) local.get();
}
public void clear(){
local.remove();//把綁定的數(shù)據(jù)刪除了
}
}
下圖為創(chuàng)建目錄的層級關(guān)系:

二、創(chuàng)建OAuth2Filter類
創(chuàng)建過濾器的目的:

因?yàn)?code>OAuth2Filter類要讀寫ThreadLocal中的數(shù)據(jù),所以OAuth2Filter類必須要設(shè)置成多例的,否則ThreadLocal將無法使用。
在配置文件中,添加JWT需要的密匙,過期時間和緩存過期時間。
emos:
jwt:
#密鑰
secret: abc123456
#令牌過期時間(天)
expire: 5
#令牌緩存時間(天數(shù))
cache-expire: 10
在com.example.emos.wx.config.shiro中創(chuàng)建OAuth2Filter類。
package com.example.emos.wx.config.shiro; import com.auth0.jwt.exceptions.JWTDecodeException; import com.auth0.jwt.exceptions.TokenExpiredException; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpStatus; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.web.filter.authc.AuthenticatingFilter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Scope; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.RequestMethod; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.concurrent.TimeUnit;
在寫好@Scope("prototype")后,就表明以后Spring使用OAuth2Filter類默認(rèn)是多例。
@Value("${emos.jwt.cache-expire}")
考察的一個知識點(diǎn),從xml文件中獲取屬性文件的屬性值。
因?yàn)橐赗edis中操作,所以要聲明private RedisTemplate redisTemplate;
申明好這個對象后,就可以對redis中的數(shù)據(jù)進(jìn)行讀寫操作了。
filter類用來區(qū)分哪些請求應(yīng)該被shiro處理,哪些請求不該被shiro處理。
如果請求被shiro處理的話,那么createToken方法就被執(zhí)行了,
createToken從請求中獲取令牌字符串,然后封裝成令牌對象OAuth2Token,交給shiro框架去處理。
getRequestToken是一個自定義方法,用來獲取令牌字符串,然后傳遞給字符串Token對象。
@Component
@Scope("prototype")
public class OAuth2Filter extends AuthenticatingFilter {
@Autowired
private ThreadLocalToken threadLocalToken;
@Value("${emos.jwt.cache-expire}")
private int cacheExpire;
@Autowired
private JwtUtil jwtUtil;
@Autowired
private RedisTemplate redisTemplate;
/**
* 攔截請求之后,用于把令牌字符串封裝成令牌對象
*/
@Override
protected AuthenticationToken createToken(ServletRequest request,
ServletResponse response) throws Exception {
//獲取請求token
String token = getRequestToken((HttpServletRequest) request);
if (StringUtils.isBlank(token)) {
return null;
}
return new OAuth2Token(token);
}
filter過濾這一塊細(xì)講一下:
isAccessAllowed是判斷哪些請求可以被shiro處理,哪些不可以被shiro處理。
由于isAccessAllowed方法中request是ServletRequest ,所以需要進(jìn)行轉(zhuǎn)換HttpServletRequest,
然后判斷這次request請求是不是options請求。如果不是,就需要被shiro處理。
/**
* 攔截請求,判斷請求是否需要被Shiro處理
*/
@Override
protected boolean isAccessAllowed(ServletRequest request,
ServletResponse response, Object mappedValue) {
HttpServletRequest req = (HttpServletRequest) request;
// Ajax提交application/json數(shù)據(jù)的時候,會先發(fā)出Options請求
// 這里要放行Options請求,不需要Shiro處理
if (req.getMethod().equals(RequestMethod.OPTIONS.name())) {
return true;
}
// 除了Options請求之外,所有請求都要被Shiro處理
return false;
}
那么,shiro是怎么處理的呢?
onAccessDenied 方法
設(shè)置響應(yīng)的字符集,和響應(yīng)的請求頭。setHeader方法用來設(shè)置跨域請求。
/**
* 該方法用于處理所有應(yīng)該被Shiro處理的請求
*/
@Override
protected boolean onAccessDenied(ServletRequest request,
ServletResponse response) throws Exception {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
resp.setHeader("Content-Type", "text/html;charset=UTF-8");
//允許跨域請求
resp.setHeader("Access-Control-Allow-Credentials", "true");
resp.setHeader("Access-Control-Allow-Origin", req.getHeader("Origin"));
//clear方法用來清理threadLocal類中的方法,
threadLocalToken.clear();
//獲取請求token,如果token不存在,直接返回401
String token = getRequestToken((HttpServletRequest) request);
if (StringUtils.isBlank(token)) {
resp.setStatus(HttpStatus.SC_UNAUTHORIZED);
resp.getWriter().print("無效的令牌");
return false;
}
然后驗(yàn)證令牌是否過期。
如果驗(yàn)證出現(xiàn)問題,就會拋出異常。
通過捕獲異常,就知道是令牌有問題,還是令牌過期了。
JWTDecodeException 是內(nèi)容異常。
通過redisTemplate的hasKey查詢Redis是否存在令牌。
如果存在令牌,就刪除老令牌,重新生成一個令牌,給客戶端。
executeLogin方法,讓shiro執(zhí)行realm類。
try {
jwtUtil.verifierToken(token); //檢查令牌是否過期
} catch (TokenExpiredException e) {
//客戶端令牌過期,查詢Redis中是否存在令牌,如果存在令牌就重新生成一個令牌給客戶端
if (redisTemplate.hasKey(token)) {
redisTemplate.delete(token);//刪除老令牌
int userId = jwtUtil.getUserId(token);
token = jwtUtil.createToken(userId); //生成新的令牌
//把新的令牌保存到Redis中
redisTemplate.opsForValue().set(token, userId + "", cacheExpire, TimeUnit.DAYS);
//把新令牌綁定到線程
threadLocalToken.setToken(token);
} else {
//如果Redis不存在令牌,讓用戶重新登錄
resp.setStatus(HttpStatus.SC_UNAUTHORIZED);
resp.getWriter().print("令牌已經(jīng)過期");
return false;
}
} catch (JWTDecodeException e) {
resp.setStatus(HttpStatus.SC_UNAUTHORIZED);
resp.getWriter().print("無效的令牌");
return false;
}
boolean bool = executeLogin(request, response);
return bool;
}
登錄失敗后輸出的信息。
@Override
protected boolean onLoginFailure(AuthenticationToken token,
AuthenticationException e, ServletRequest request, ServletResponse response) {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
resp.setStatus(HttpStatus.SC_UNAUTHORIZED);
resp.setContentType("application/json;charset=utf-8");
resp.setHeader("Access-Control-Allow-Credentials", "true");
resp.setHeader("Access-Control-Allow-Origin", req.getHeader("Origin"));
try {
resp.getWriter().print(e.getMessage());//捕獲認(rèn)證失敗的消息
} catch (IOException exception) {
}
return false;
}
獲取請求頭里面的token
/**
* 獲取請求頭里面的token
*/
private String getRequestToken(HttpServletRequest httpRequest) {
//從header中獲取token
String token = httpRequest.getHeader("token");
//如果header中不存在token,則從參數(shù)中獲取token
if (StringUtils.isBlank(token)) {
token = httpRequest.getParameter("token");
}
return token;
}
doFilterInternal方法從父類doFilterInternal中繼承,掌管攔截請求和響應(yīng)的。這里不覆寫。
@Override
public void doFilterInternal(ServletRequest request,
ServletResponse response, FilterChain chain) throws ServletException, IOException {
super.doFilterInternal(request, response, chain);
}
}
到此這篇關(guān)于SpringBoot創(chuàng)建存儲令牌的媒介類和過濾器的文章就介紹到這了,更多相關(guān)SpringBoot內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java開發(fā)Activiti進(jìn)階篇流程實(shí)例詳解
這篇文章主要為大家介紹了java開發(fā)Activiti進(jìn)階篇流程實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08
Java for循環(huán)性能優(yōu)化實(shí)現(xiàn)解析
這篇文章主要介紹了Java for循環(huán)性能優(yōu)化實(shí)現(xiàn)解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-01-01
解決一個JSON反序列化問題的辦法(空字符串變?yōu)榭占?
在平時的業(yè)務(wù)開發(fā)中,經(jīng)常會有拿到一串序列化后的字符串要來反序列化,下面這篇文章主要給大家介紹了如何解決一個JSON反序列化問題的相關(guān)資料,空字符串變?yōu)榭占?需要的朋友可以參考下2024-03-03

