天天看點

springboot整合shiro和JWT 使用指北

shiro是什麼?

Shiro

是一個強大的簡單易用的

Java

安全架構,主要用來更便捷的

認證,授權,加密,會話管理

Shiro

首要的和最重要的目标就是容易使用并且容易了解,通過

Shiro

易于了解的

API

,您可以快速、輕松地獲得任何應用程式——從最小的移動應用程式最大的網絡和企業應用程式。

shiro的功能

  1. Authentication:身份認證、登入,驗證使用者是不是擁有相應的身份;
  2. Authorization:授權,即權限驗證,驗證某個已認證的使用者是否擁有某個權限,即判斷使用者能否進行什麼操作,如:驗證某個使用者是否擁有某個角色,或者細粒度的驗證某個使用者對某個資源是否具有某個權限!
  3. Session Manager:會話管理,即使用者登入後就是第一次會話,在沒有退出之前,它的所有資訊都在會話中;會話可以是普通的JavaSE環境,也可以是Web環境;
  4. Cryptography:加密,保護資料的安全性,如密碼加密存儲到資料庫中,而不是明文存儲;
  5. Web Support: Web支援,可以非常容易的內建到Web環境;
  6. Caching:緩存,比如使用者登入後,其使用者資訊,擁有的角色、權限不必每次去查,這樣可以提高效率
  7. Concurrency: Shiro支援多線程應用的并發驗證,即,如在一個線程中開啟另一個線程,能把權限自動的傳播過去Testing:提供測試支援;
  8. Run As:允許一個使用者假裝為另一個使用者(如果他們允許)的身份進行通路;
  9. Remember Me:記住我,這個是非常常見的功能,即一次登入後,下次再來的話不用登入了

JWT是什麼?

JWT :JWT(JSON Web Token)是一種身份認證的方式,JWT 本質上就一段簽名的 JSON 格式的資料。由于它是帶有簽名的,是以接收者便可以驗證它的真實性。可以了解為對 token 這一技術提出一套規範,是在

RFC 7519

中提出的。

JWT的組成和優缺點

本文在這裡僅簡單介紹JWT的組成和優缺點。詳細分析可以在相關搜尋引擎中搜尋。

JWT的組成

一個JWT token是一個字元串,它由頭部、載荷、與簽名中間有.分割組成;

頭部(header):由令牌的類型和正在使用的簽名算法組成;

載荷(playload):放置了token的一些基本資訊,以幫助接收伺服器了解,同時還有使用者自定義資訊,支援使用者資訊交換;

簽名(Signature):主要是将前面的頭部和載荷進行加密。

JWT的優缺點

JWT的優點:

  • 防CSRF:CSRF(Cross Site Request Forgery)跨站僞造請求。使用JWT登入成功後,會存放在本地local storage中;
  • 适合移動端:使用session需要服務端儲存一份資訊,使用token不需要隻要請求攜帶token,還可跨語言使用;
  • 無狀态:token中有身份驗證的所有資訊,增加了系統的可用性和伸縮性也帶了同樣的問題(在缺點中會介紹);
  • 單點登入友好:使用session需要我們把所有session資訊存在一台電腦上,使用token的話token儲存在用戶端。

JWT的缺點:

  • 無狀态導緻在登出、登出、修改密碼、修改角色||權限等場景下token依舊有效;
  • token續簽問題:即token過期後如何動态重新整理token。

shiro + JWT 使用

JWTtoken 令牌

`import org.apache.shiro.authc.AuthenticationToken;

public class JwtToken implements AuthenticationToken{

private String token;

public JwtToken(String jwt){

this.token = jwt;

}

@Override

public Object getCredentials() {

return token;

public Object getPrincipal() {

}

}`

jwtutils JWT工具類主要是生成token設定過期時間

`import java.util.Date;

import org.springframework.boot.context.properties.ConfigurationProperties;

import org.springframework.stereotype.Component;

import io.jsonwebtoken.Claims;

import io.jsonwebtoken.Jwts;

import io.jsonwebtoken.SignatureAlgorithm;

import lombok.Data;

import lombok.extern.slf4j.Slf4j;

@Slf4j

@Data

@Component

@ConfigurationProperties(prefix = "blogspringback.jwt")

public class JwtUtils {

private static String secret;

private long expire;

private String header;

/**

* @description: 生成token

*** @param {long} userId

*** @return {JWT token}

* @Author: 醜牛

*/

public String generateToken(long userId){

// 過期時間

Date dateNow = new Date();

Date dateExpire = new Date(dateNow.getTime() + expire * 1000);

return Jwts.builder()

.setHeaderParam("type", "JWT")

.setSubject(userId + "")

.setIssuedAt(dateNow)

.setExpiration(dateExpire)

.signWith(SignatureAlgorithm.HS512, secret)

.compact();

public static Claims getClaimsByToken(String token){

try {

return Jwts.parser()

.setSigningKey(secret)

.parseClaimsJws(token)

.getBody();

} catch (Exception e) {

log.debug("token error", e);

return null;

}

* @description: 判斷token是否過期

*** @param {Date} expeiration

*** @return true:過期;false:未過期

public boolean isTokenExpired(Date expeiration){

return expeiration.before(new Date());

JWTFilter 攔截請求

`@Component

public class Jwtfilter extends AuthenticatingFilter {

@Autowired

JwtUtils jwtUtils;

protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse serverResponse) throws Exception {

HttpServletRequest request = (HttpServletRequest) servletRequest;

String jwt = request.getHeader("Authorization");

if (ObjectUtils.isEmpty(jwt)){

return new JwtToken(jwt);

protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse serverResponse) throws Exception {

if (ObjectUtils.isEmpty(jwt)) {

return true;

} else {

Claims claim = JwtUtils.getClaimsByToken(jwt);

if (claim == null || jwtUtils.isTokenExpired(claim.getExpiration())) {

throw new ExpiredCredentialsException("token失效,請重新登入");

}

return executeLogin(servletRequest, serverResponse);

}

protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response){

HttpServletResponse httpServletResponse = (HttpServletResponse) response;

Throwable throwable = e.getCause() == null ? e : e.getCause();

Result result = Result.fail(throwable.getMessage());

String json = JSONUtil.toJsonStr(result);

httpServletResponse.getWriter().print(json);

} catch (IOException ioException) {

return false;

protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {

HttpServletRequest httpServletRequest = WebUtils.toHttp(request);

HttpServletResponse httpServletResponse = WebUtils.toHttp(response);

httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));

httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");

httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));

// 跨域時會首先發送一個OPTIONS請求,這裡我們給OPTIONS請求直接傳回正常狀态

if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {

httpServletResponse.setStatus(org.springframework.http.HttpStatus.OK.value());

return false;

return super.preHandle(request, response);

shiro config 設定自定義的filter

`@Configuration

public class ShiroConfig {

Jwtfilter jwtFilter;

@Bean

public SessionManager sessionManager(RedisSessionDAO redisSessionDAO) {

DefaultSessionManager sessionManager = new DefaultSessionManager();

sessionManager.setSessionDAO(redisSessionDAO);

return sessionManager;

public DefaultWebSecurityManager securityManager(AccountRealm accountRealm, SessionManager sessionManager,

RedisCacheManager redisCacheManager) {

DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(accountRealm);

// inject sessionManager

securityManager.setSessionManager(sessionManager);

// inject redisCacheManager

securityManager.setCacheManager(redisCacheManager);

return securityManager;

public ShiroFilterChainDefinition shiroFilterChainDefinition() {

DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();

Map<String, String> filterMap = new LinkedHashMap<>();

filterMap.put("/**", "jwt");

chainDefinition.addPathDefinitions(filterMap);

return chainDefinition;

@Bean("shiroFilterFactoryBean")

public ShiroFilterFactoryBean shiroFilterFactoryBean(org.apache.shiro.mgt.SecurityManager securityManager,

ShiroFilterChainDefinition shiroFilterChainDefinition) {

ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();

shiroFilter.setSecurityManager(securityManager);

Map<String, Filter> filters = new HashMap<>();

filters.put("jwt", jwtFilter);

shiroFilter.setFilters(filters);

Map<String, String> filterMap = shiroFilterChainDefinition.getFilterChainMap();

shiroFilter.setFilterChainDefinitionMap(filterMap);

return shiroFilter;

shiro工具類 擷取目前使用者登入資訊

`public class ShiroUtil {

public static AccountProfile getProfile(){

return (AccountProfile) SecurityUtils.getSubject().getPrincipal();

realm類 實作認證與授權

public class AccountRealm extends AuthorizingRealm{

UserService userService;

public boolean supports(AuthenticationToken token) {

return token instanceof JwtToken;

protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

return null;

protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

JwtToken jwtToken = (JwtToken) token;

String userId = JwtUtils.getClaimsByToken((String) jwtToken.getPrincipal()).getSubject();

User user = userService.getById(Long.valueOf(userId));

if (user == null) {

throw new UnknownAccountException("賬戶不存在");

if (user.getStatus() == -1) {

throw new LockedAccountException("賬戶已被鎖定");

AccountProfile profile = new AccountProfile();

BeanUtil.copyProperties(user, profile);

return new SimpleAuthenticationInfo(profile, jwtToken.getCredentials(), getName());

PS 項目git位址

https://github.com/hugfeature/spring-example/tree/main/blog-spring-back