shiro是什麼?
Shiro
是一個強大的簡單易用的
Java
安全架構,主要用來更便捷的
認證,授權,加密,會話管理
。
Shiro
首要的和最重要的目标就是容易使用并且容易了解,通過
Shiro
易于了解的
API
,您可以快速、輕松地獲得任何應用程式——從最小的移動應用程式最大的網絡和企業應用程式。
shiro的功能
- Authentication:身份認證、登入,驗證使用者是不是擁有相應的身份;
- Authorization:授權,即權限驗證,驗證某個已認證的使用者是否擁有某個權限,即判斷使用者能否進行什麼操作,如:驗證某個使用者是否擁有某個角色,或者細粒度的驗證某個使用者對某個資源是否具有某個權限!
- Session Manager:會話管理,即使用者登入後就是第一次會話,在沒有退出之前,它的所有資訊都在會話中;會話可以是普通的JavaSE環境,也可以是Web環境;
- Cryptography:加密,保護資料的安全性,如密碼加密存儲到資料庫中,而不是明文存儲;
- Web Support: Web支援,可以非常容易的內建到Web環境;
- Caching:緩存,比如使用者登入後,其使用者資訊,擁有的角色、權限不必每次去查,這樣可以提高效率
- Concurrency: Shiro支援多線程應用的并發驗證,即,如在一個線程中開啟另一個線程,能把權限自動的傳播過去Testing:提供測試支援;
- Run As:允許一個使用者假裝為另一個使用者(如果他們允許)的身份進行通路;
- 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