Spring-boot結(jié)合Shrio實現(xiàn)JWT的方法
本文介紹了Spring-boot結(jié)合Shrio實現(xiàn)JWT的方法,分享給大家,具體如下:
關(guān)于驗證大致分為兩個方面:
- 用戶登錄時的驗證;
- 用戶登錄后每次訪問時的權(quán)限認(rèn)證
主要解決方法:使用自定義的Shiro Filter
項目搭建:
這是一個spring-boot 的web項目,不了解spring-boot的項目搭建,請google。
pom.mx引入相關(guān)jar包
<!-- shiro 權(quán)限管理 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>${shiro.version}</version>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
Shrio 的相關(guān)配置
劃重點?。∽远x了一個Filter
filterMap.put("JWTFilter", new JWTFilter());
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 添加自己的過濾器并且取名為JWTFilter
Map<String, Filter> filterMap = new HashMap<>();
filterMap.put("JWTFilter", new JWTFilter());
shiroFilterFactoryBean.setFilters(filterMap);
/*
* 自定義url規(guī)則
* http://shiro.apache.org/web.html#urls-
*/
Map<String, String> filterChainDefinitionMap = shiroFilterFactoryBean.getFilterChainDefinitionMap();
filterChainDefinitionMap.put("/**", "JWTFilter");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* securityManager 不用直接注入shiroDBRealm,可能會導(dǎo)致事務(wù)失效
* 解決方法見 handleContextRefresh
* http://www.debugrun.com/a/NKS9EJQ.html
*/
@Bean("securityManager")
public DefaultWebSecurityManager securityManager(TokenRealm tokenRealm) {
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(tokenRealm);
/*
* 關(guān)閉shiro自帶的session,詳情見文檔
* http://shiro.apache.org/session-management.html#SessionManagement-StatelessApplications%28Sessionless%29
*/
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
manager.setSubjectDAO(subjectDAO);
return manager;
}
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean(name = "TokenRealm")
@DependsOn("lifecycleBeanPostProcessor")
public TokenRealm tokenRealm() {
return new TokenRealm();
}
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
// 強制使用cglib,防止重復(fù)代理和可能引起代理出錯的問題
// https://zhuanlan.zhihu.com/p/29161098
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return new AuthorizationAttributeSourceAdvisor();
}
}
自定義Shrio filter
執(zhí)行順序:preHandle -> doFilterInternal -> executeLogin -> onLoginSuccess
主要判斷是不是登錄請求的是 doFilterInternal
public class JWTFilter extends BasicHttpAuthenticationFilter {
/**
* 自定義執(zhí)行登錄的方法
*/
@Override
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws IOException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
UsernamePasswordToken usernamePasswordToken = JSON.parseObject(httpServletRequest.getInputStream(), UsernamePasswordToken.class);
// 提交給realm進(jìn)行登入,如果錯誤他會拋出異常并被捕獲
Subject subject = this.getSubject(request, response);
subject.login(usernamePasswordToken);
return this.onLoginSuccess(usernamePasswordToken, subject, request, response);
//錯誤拋出異常
}
/**
* 最先執(zhí)行的方法
*/
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
return super.preHandle(request, response);
}
/**
* 登錄成功后登錄的操作
* 加上jwt 的header
*/
@Override
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) {
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
String jwtToken = Jwts.builder()
.setId(token.getPrincipal().toString())
.setExpiration(DateTime.now().plusMinutes(30).toDate())
.signWith(SignatureAlgorithm.HS256, JWTCost.signatureKey)
.compact();
httpServletResponse.addHeader(AUTHORIZATION_HEADER, jwtToken);
return true;
}
/**
* 登錄以及校驗的主要流程
* 判斷是否是登錄,或者是登陸后普通的一次請求
*/
@Override
public void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
String servletPath = httpServletRequest.getServletPath();
if (StringUtils.equals(servletPath, "/login")) {
//執(zhí)行登錄
this.executeLogin(servletRequest, servletResponse);
} else {
String authenticationHeader = httpServletRequest.getHeader(AUTHORIZATION_HEADER);
if (StringUtils.isNotEmpty(authenticationHeader)) {
Claims body = Jwts.parser()
.setSigningKey(JWTCost.signatureKey)
.parseClaimsJws(authenticationHeader)
.getBody();
if (body != null) {
//更新token
body.setExpiration(DateTime.now().plusMinutes(30).toDate());
String updateToken = Jwts.builder().setClaims(body).compact();
httpServletResponse.addHeader(AUTHORIZATION_HEADER, updateToken);
//添加用戶憑證
PrincipalCollection principals = new SimplePrincipalCollection(body.getId(), JWTCost.UserNamePasswordRealm);//拼裝shiro用戶信息
WebSubject.Builder builder = new WebSubject.Builder(servletRequest, servletResponse);
builder.principals(principals);
builder.authenticated(true);
builder.sessionCreationEnabled(false);
WebSubject subject = builder.buildWebSubject();
//塞入容器,統(tǒng)一調(diào)用
ThreadContext.bind(subject);
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
} else {
httpServletResponse.setStatus(HttpStatus.FORBIDDEN.value());
}
}
}
}
登錄失敗處理
處理Shrio異常
@RestControllerAdvice
public class GlobalControllerExceptionHandler {
@ExceptionHandler(value = Exception.class)
public Object allExceptionHandler(HttpServletRequest request, HttpServletResponse response, Exception exception) {
String message = exception.getCause().getMessage();
LogUtil.error(message);
return new ResultInfo(exception.getClass().getName(), message);
}
/*=========== Shiro 異常攔截==============*/
@ExceptionHandler(value = IncorrectCredentialsException.class)
public String IncorrectCredentialsException(HttpServletRequest request, HttpServletResponse response, Exception exception) {
response.setStatus(HttpStatus.FORBIDDEN.value());
return "IncorrectCredentialsException";
}
@ExceptionHandler(value = UnknownAccountException.class)
public String UnknownAccountException(HttpServletRequest request, HttpServletResponse response, Exception exception) {
response.setStatus(HttpStatus.FORBIDDEN.value());
return "UnknownAccountException";
}
@ExceptionHandler(value = LockedAccountException.class)
public String LockedAccountException(HttpServletRequest request, HttpServletResponse response, Exception exception) {
response.setStatus(HttpStatus.FORBIDDEN.value());
return "LockedAccountException";
}
@ExceptionHandler(value = ExcessiveAttemptsException.class)
public String ExcessiveAttemptsException(HttpServletRequest request, HttpServletResponse response, Exception exception) {
response.setStatus(HttpStatus.FORBIDDEN.value());
return "ExcessiveAttemptsException";
}
@ExceptionHandler(value = AuthenticationException.class)
public String AuthenticationException(HttpServletRequest request, HttpServletResponse response, Exception exception) {
response.setStatus(HttpStatus.FORBIDDEN.value());
return "AuthenticationException";
}
@ExceptionHandler(value = UnauthorizedException.class)
public String UnauthorizedException(HttpServletRequest request, HttpServletResponse response, Exception exception) {
response.setStatus(HttpStatus.FORBIDDEN.value());
return "UnauthorizedException";
}
}
處理JWT異常
這是個坑,因為是在filter內(nèi)發(fā)生的異常,@ExceptionHandler是截獲不到的。
/**
* 截獲spring boot Error頁面
*/
@RestController
public class GlobalExceptionHandler implements ErrorController {
@Override
public String getErrorPath() {
return "/error";
}
@RequestMapping(value = "/error")
public Object error(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 錯誤處理邏輯
Exception exception = (Exception) request.getAttribute("javax.servlet.error.exception");
Throwable cause = exception.getCause();
if (cause instanceof ExpiredJwtException) {
response.setStatus(HttpStatus.GATEWAY_TIMEOUT.value());
return new ResultInfo("ExpiredJwtException", cause.getMessage());
}
if (cause instanceof MalformedJwtException) {
response.setStatus(HttpStatus.FORBIDDEN.value());
return new ResultInfo("MalformedJwtException", cause.getMessage());
}
return new ResultInfo(cause.getCause().getMessage(), cause.getMessage());
}
}
關(guān)于權(quán)限等授權(quán)信息,可以直接放到Redis中實現(xiàn)緩存。我認(rèn)為也是不錯的。
源碼奉上:githup-shiro分支 :溫馨提示:平時測試代碼可能比較亂。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Java數(shù)據(jù)結(jié)構(gòu)和算法之鏈表詳解
鏈表是一種物理存儲單元上非連續(xù)、非順序的存儲結(jié)構(gòu),java代碼實現(xiàn)單鏈表,插入,刪除和遍歷等功能,這篇文章主要給大家介紹了關(guān)于Java數(shù)據(jù)結(jié)構(gòu)和算法之鏈表的相關(guān)資料,需要的朋友可以參考下2024-01-01
使用springboot的jar包能夠以service方式啟動
這篇文章主要介紹了使用springboot的jar包能夠以service方式啟動,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10
Java 自定義Spring框架與Spring IoC相關(guān)接口分析
Spring框架是由于軟件開發(fā)的復(fù)雜性而創(chuàng)建的。Spring使用的是基本的JavaBean來完成以前只可能由EJB完成的事情。然而,Spring的用途不僅僅限于服務(wù)器端的開發(fā)2021-10-10
Spring Boot熱加載jar實現(xiàn)動態(tài)插件的思路
本文主要介紹在 Spring Boot 工程中熱加載 jar 包并注冊成為 Bean 對象的一種實現(xiàn)思路,在動態(tài)擴(kuò)展功能的同時支持在插件中注入主程序的 Bean 實現(xiàn)功能更強大的插件2021-10-10
關(guān)于Spring中的@Configuration中的proxyBeanMethods屬性
這篇文章主要介紹了關(guān)于Spring中的@Configuration中的proxyBeanMethods屬性,需要的朋友可以參考下2023-07-07

