1. 概述這個就沒什麼好說的了,能看到這個教程的,估計都是能夠說精通了SpringBoot
的使用前端
一個安全框架,但不僅是一個安全框架。它能實現多種多樣的功能。並不僅是侷限在web層。在國內的市場份額佔比高於SpringSecurity
,是使用最多的安全框架java
能夠實現用戶的認證和受權。比SpringSecurity
要簡單的多。web
個人理解就是能夠進行客戶端與服務端之間驗證的一種技術,取代了以前使用Session來驗證的不安全性算法
爲何不適用Session?spring
原理是,登陸以後客戶端和服務端各自保存一個相應的SessionId,每次客戶端發起請求的時候就得攜帶這個SessionId來進行比對數據庫
- Session在用戶請求量大的時候服務器開銷太大了
- Session不利於搭建服務器的集羣(也就是必須訪問本來的那個服務器才能獲取對應的SessionId)
它使用的是一種令牌技術apache
Jwt字符串分爲三部分json
Header安全
存儲兩個變量springboot
payload
存儲不少東西,基礎信息有以下幾個
userId
Signature
這個是上面兩個通過Header中的算法加密生成的,用於比對信息,防止篡改Header和payload
而後將這三個部分的信息通過加密生成一個JwtToken
的字符串,發送給客戶端,客戶端保存在本地。當客戶端發起請求的時候攜帶這個到服務端(能夠是在cookie
,能夠是在header
,能夠是在localStorage
中),在服務端進行驗證
好了,廢話很少說了,下面開始實戰,實戰分爲如下幾個部分
SpringBoot
整合Shiro
SpringBoot
整合Jwt
SpringBoot
+Shiro
+Jwt
<dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.11.0</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency>
兩種方式:
pom.xml
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-web-starter</artifactId> <version>1.4.0</version> </dependency> <!--注意不要寫成shiro-spring-boot-starter-->
application.properties
shiro.loginUrl="xxx" #認證不經過的頁面 shiro.UnauthorizedUrl="xxx" #受權不經過的跳轉頁面
建立ShiroConfig.java進行一些簡單的配置
@Configuration public class SpringShiroConfig { @Bean public Realm customRealm() { return new CustomRealm(); } @Bean public DefaultWebSecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(customRealm()); // 關閉 ShiroDAO 功能 DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO(); DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator(); // 不須要將 Shiro Session 中的東西存到任何地方(包括 Http Session 中) defaultSessionStorageEvaluator.setSessionStorageEnabled(false); subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator); securityManager.setSubjectDAO(subjectDAO); return securityManager; } @Bean public ShiroFilterChainDefinition shiroFilterChainDefinition() { DefaultShiroFilterChainDefinition chain = new DefaultShiroFilterChainDefinition(); // 哪些請求能夠匿名訪問 chain.addPathDefinition("/login", "anon"); // 登陸接口 chain.addPathDefinition("/notLogin", "anon"); // 未登陸錯誤提示接口 chain.addPathDefinition("/403", "anon"); // 權限不足錯誤提示接口 // 除了以上的請求外,其它請求都須要登陸 chain.addPathDefinition("/**", "authc"); return chain; } // Shiro 和 Spring AOP 整合時的特殊設置 @Bean public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator(); creator.setProxyTargetClass(true); return creator; } } //還有關閉ShiroDao功能
建立自定義的Realm
public class CustomRealm extends AuthorizingRealm { private static final Set<String> tomRoleNameSet = new HashSet<>(); private static final Set<String> tomPermissionNameSet = new HashSet<>(); private static final Set<String> jerryRoleNameSet = new HashSet<>(); private static final Set<String> jerryPermissionNameSet = new HashSet<>(); static { tomRoleNameSet.add("admin"); jerryRoleNameSet.add("user"); tomPermissionNameSet.add("user:insert"); tomPermissionNameSet.add("user:update"); tomPermissionNameSet.add("user:delete"); tomPermissionNameSet.add("user:query"); jerryPermissionNameSet.add("user:query"); } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String username = (String) principals.getPrimaryPrincipal(); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); if (username.equals("tom")) { info.addRoles(tomRoleNameSet); info.addStringPermissions(tomPermissionNameSet); } else if (username.equals("jerry")) { info.addRoles(jerryRoleNameSet); info.addStringPermissions(jerryPermissionNameSet); } return info; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String username = (String) token.getPrincipal(); if (username == null) throw new UnknownAccountException("用戶名不能爲空"); SimpleAuthenticationInfo info = null; if (username.equals("tom")) return new SimpleAuthenticationInfo("tom", "123", CustomRealm.class.getName()); else if (username.equals("jerry")) return new SimpleAuthenticationInfo("jerry", "123", CustomRealm.class.getName()); else return null; } }
<!-- 自動依賴導入 shiro-core 和 shiro-web --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.1</version> </dependency>
編寫 Shiro 的配置類:ShiroConfig
將 Shiro 的配置信息(spring-shiro.xml 和 spring-web.xml)以 Java 代碼配置的形式改寫:
@Configuration public class ShiroConfig { @Bean public Realm realm() { return new CustomRealm(); } @Bean public DefaultWebSecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(realm()); return securityManager; } @Bean public ShiroFilterFactoryBean shirFilter() { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager()); shiroFilterFactoryBean.setLoginUrl("/loginPage"); shiroFilterFactoryBean.setUnauthorizedUrl("/403"); Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); filterChainDefinitionMap.put("/loginPage", "anon"); filterChainDefinitionMap.put("/403", "anon"); filterChainDefinitionMap.put("/login", "anon"); filterChainDefinitionMap.put("/hello", "anon"); filterChainDefinitionMap.put("/**", "authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } /* ################################################################# */ @Bean public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } @Bean @DependsOn("lifecycleBeanPostProcessor") public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); // 強制指定註解的底層實現使用 cglib 方案 defaultAdvisorAutoProxyCreator.setProxyTargetClass(true); return defaultAdvisorAutoProxyCreator; } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) { AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager); return advisor; } }
3. SpringBoot整合Jwt
1. springboot 2. java-jwt--核心依賴 3. jjwt--java版本的輔助幫助模塊
建立JwtUtil
package cn.coderymy.utils; import java.util.*; import com.auth0.jwt.*; import com.auth0.jwt.algorithms.Algorithm; import io.jsonwebtoken.*; import org.apache.commons.codec.binary.Base64; import java.util.*; public class JwtUtil { // 生成簽名是所使用的祕鑰 private final String base64EncodedSecretKey; // 生成簽名的時候所使用的加密算法 private final SignatureAlgorithm signatureAlgorithm; public JwtUtil(String secretKey, SignatureAlgorithm signatureAlgorithm) { this.base64EncodedSecretKey = Base64.encodeBase64String(secretKey.getBytes()); this.signatureAlgorithm = signatureAlgorithm; } /** * 生成 JWT Token 字符串 * * @param iss 簽發人名稱 * @param ttlMillis jwt 過時時間 * @param claims 額外添加到荷部分的信息。 * 例如能夠添加用戶名、用戶ID、用戶(加密前的)密碼等信息 */ public String encode(String iss, long ttlMillis, Map<String, Object> claims) { if (claims == null) { claims = new HashMap<>(); } // 簽發時間(iat):荷載部分的標準字段之一 long nowMillis = System.currentTimeMillis(); Date now = new Date(nowMillis); // 下面就是在爲payload添加各類標準聲明和私有聲明瞭 JwtBuilder builder = Jwts.builder() // 荷載部分的非標準字段/附加字段,通常寫在標準的字段以前。 .setClaims(claims) // JWT ID(jti):荷載部分的標準字段之一,JWT 的惟一性標識,雖不強求,但儘可能確保其惟一性。 .setId(UUID.randomUUID().toString()) // 簽發時間(iat):荷載部分的標準字段之一,表明這個 JWT 的生成時間。 .setIssuedAt(now) // 簽發人(iss):荷載部分的標準字段之一,表明這個 JWT 的全部者。一般是 username、userid 這樣具備用戶表明性的內容。 .setSubject(iss) // 設置生成簽名的算法和祕鑰 .signWith(signatureAlgorithm, base64EncodedSecretKey); if (ttlMillis >= 0) { long expMillis = nowMillis + ttlMillis; Date exp = new Date(expMillis); // 過時時間(exp):荷載部分的標準字段之一,表明這個 JWT 的有效期。 builder.setExpiration(exp); } return builder.compact(); } /** * JWT Token 由 頭部 荷載部 和 簽名部 三部分組成。簽名部分是由加密算法生成,沒法反向解密。 * 而 頭部 和 荷載部分是由 Base64 編碼算法生成,是能夠反向反編碼回原樣的。 * 這也是爲何不要在 JWT Token 中放敏感數據的緣由。 * * @param jwtToken 加密後的token * @return claims 返回荷載部分的鍵值對 */ public Claims decode(String jwtToken) { // 獲得 DefaultJwtParser return Jwts.parser() // 設置簽名的祕鑰 .setSigningKey(base64EncodedSecretKey) // 設置須要解析的 jwt .parseClaimsJws(jwtToken) .getBody(); } /** * 校驗 token * 在這裏可使用官方的校驗,或, * 自定義校驗規則,例如在 token 中攜帶密碼,進行加密處理後和數據庫中的加密密碼比較。 * * @param jwtToken 被校驗的 jwt Token */ public boolean isVerify(String jwtToken) { Algorithm algorithm = null; switch (signatureAlgorithm) { case HS256: algorithm = Algorithm.HMAC256(Base64.decodeBase64(base64EncodedSecretKey)); break; default: throw new RuntimeException("不支持該算法"); } JWTVerifier verifier = JWT.require(algorithm).build(); verifier.verify(jwtToken); // 校驗不經過會拋出異常 /* // 獲得DefaultJwtParser Claims claims = decode(jwtToken); if (claims.get("password").equals(user.get("password"))) { return true; } */ return true; } public static void main(String[] args) { JwtUtil util = new JwtUtil("tom", SignatureAlgorithm.HS256); Map<String, Object> map = new HashMap<>(); map.put("username", "tom"); map.put("password", "123456"); map.put("age", 20); String jwtToken = util.encode("tom", 30000, map); System.out.println(jwtToken); /* util.isVerify(jwtToken); System.out.println("合法"); */ util.decode(jwtToken).entrySet().forEach((entry) -> { System.out.println(entry.getKey() + ": " + entry.getValue()); }); } }
解析:
建立一個Controller
JWTUtil
中的isVerify
進行該jwt數據有效的校驗因爲須要對shiro的SecurityManager進行設置,因此不能使用shiro-spring-boot-starter進行與springboot的整合,只能使用spring-shiro
<!-- 自動依賴導入 shiro-core 和 shiro-web --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.1</version> </dependency>
因爲須要實現無狀態的web,因此使用不到Shiro的Session功能,嚴謹點就是將其關閉
public class JwtDefaultSubjectFactory extends DefaultWebSubjectFactory { @Override public Subject createSubject(SubjectContext context) { // 不建立 session context.setSessionCreationEnabled(false); return super.createSubject(context); } }
這樣若是調用getSession()
方法會拋出異常
執行流程:1. 客戶端發起請求,shiro的過濾器生效,判斷是不是login或logout的請求<br/> 若是是就直接執行請求<br/> 若是不是就進入JwtFilter2. JwtFilter執行流程 1. 獲取header是否有"Authorization"的鍵,有就獲取,沒有就拋出異常 2. 將獲取的jwt字符串封裝在建立的JwtToken中,使用subject執行login()方法進行校驗。這個方法會調用建立的JwtRealm 3. 執行JwtRealm中的認證方法,使用`jwtUtil.isVerify(jwt)`判斷是否登陸過 4. 返回true就使基礎執行下去
package cn.coderymy.shiro; import org.apache.shiro.subject.Subject; import org.apache.shiro.subject.SubjectContext; import org.apache.shiro.web.mgt.DefaultWebSubjectFactory; public class JwtDefaultSubjectFactory extends DefaultWebSubjectFactory { @Override public Subject createSubject(SubjectContext context) { // 不建立 session context.setSessionCreationEnabled(false); return super.createSubject(context); } }
這個通常是固定的寫法,其中寫了大量註釋
package cn.coderymy.util; import com.auth0.jwt.JWT; import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.apache.commons.codec.binary.Base64; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.UUID; /* * 總的來講,工具類中有三個方法 * 獲取JwtToken,獲取JwtToken中封裝的信息,判斷JwtToken是否存在 * 1. encode(),參數是=簽發人,存在時間,一些其餘的信息=。返回值是JwtToken對應的字符串 * 2. decode(),參數是=JwtToken=。返回值是荷載部分的鍵值對 * 3. isVerify(),參數是=JwtToken=。返回值是這個JwtToken是否存在 * */ public class JwtUtil { //建立默認的祕鑰和算法,供無參的構造方法使用 private static final String defaultbase64EncodedSecretKey = "badbabe"; private static final SignatureAlgorithm defaultsignatureAlgorithm = SignatureAlgorithm.HS256; public JwtUtil() { this(defaultbase64EncodedSecretKey, defaultsignatureAlgorithm); } private final String base64EncodedSecretKey; private final SignatureAlgorithm signatureAlgorithm; public JwtUtil(String secretKey, SignatureAlgorithm signatureAlgorithm) { this.base64EncodedSecretKey = Base64.encodeBase64String(secretKey.getBytes()); this.signatureAlgorithm = signatureAlgorithm; } /* *這裏就是產生jwt字符串的地方 * jwt字符串包括三個部分 * 1. header * -當前字符串的類型,通常都是「JWT」 * -哪一種算法加密,「HS256」或者其餘的加密算法 * 因此通常都是固定的,沒有什麼變化 * 2. payload * 通常有四個最多見的標準字段(下面有) * iat:簽發時間,也就是這個jwt何時生成的 * jti:JWT的惟一標識 * iss:簽發人,通常都是username或者userId * exp:過時時間 * * */ public String encode(String iss, long ttlMillis, Map<String, Object> claims) { //iss簽發人,ttlMillis生存時間,claims是指還想要在jwt中存儲的一些非隱私信息 if (claims == null) { claims = new HashMap<>(); } long nowMillis = System.currentTimeMillis(); JwtBuilder builder = Jwts.builder() .setClaims(claims) .setId(UUID.randomUUID().toString())//2. 這個是JWT的惟一標識,通常設置成惟一的,這個方法能夠生成惟一標識 .setIssuedAt(new Date(nowMillis))//1. 這個地方就是以毫秒爲單位,換算當前系統時間生成的iat .setSubject(iss)//3. 簽發人,也就是JWT是給誰的(邏輯上通常都是username或者userId) .signWith(signatureAlgorithm, base64EncodedSecretKey);//這個地方是生成jwt使用的算法和祕鑰 if (ttlMillis >= 0) { long expMillis = nowMillis + ttlMillis; Date exp = new Date(expMillis);//4. 過時時間,這個也是使用毫秒生成的,使用當前時間+前面傳入的持續時間生成 builder.setExpiration(exp); } return builder.compact(); } //至關於encode的方向,傳入jwtToken生成對應的username和password等字段。Claim就是一個map //也就是拿到荷載部分全部的鍵值對 public Claims decode(String jwtToken) { // 獲得 DefaultJwtParser return Jwts.parser() // 設置簽名的祕鑰 .setSigningKey(base64EncodedSecretKey) // 設置須要解析的 jwt .parseClaimsJws(jwtToken) .getBody(); } //判斷jwtToken是否合法 public boolean isVerify(String jwtToken) { //這個是官方的校驗規則,這裏只寫了一個」校驗算法「,能夠本身加 Algorithm algorithm = null; switch (signatureAlgorithm) { case HS256: algorithm = Algorithm.HMAC256(Base64.decodeBase64(base64EncodedSecretKey)); break; default: throw new RuntimeException("不支持該算法"); } JWTVerifier verifier = JWT.require(algorithm).build(); verifier.verify(jwtToken); // 校驗不經過會拋出異常 //判斷合法的標準:1. 頭部和荷載部分沒有篡改過。2. 沒有過時 return true; } public static void main(String[] args) { JwtUtil util = new JwtUtil("tom", SignatureAlgorithm.HS256); //以tom做爲祕鑰,以HS256加密 Map<String, Object> map = new HashMap<>(); map.put("username", "tom"); map.put("password", "123456"); map.put("age", 20); String jwtToken = util.encode("tom", 30000, map); System.out.println(jwtToken); util.decode(jwtToken).entrySet().forEach((entry) -> { System.out.println(entry.getKey() + ": " + entry.getValue()); }); } }
也就是在Shiro的攔截器中多加一個,等下須要在配置文件中註冊這個過濾器
package cn.coderymy.filter; import cn.coderymy.shiro.JwtToken; import lombok.extern.slf4j.Slf4j; import org.apache.shiro.web.filter.AccessControlFilter; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; /* * 自定義一個Filter,用來攔截全部的請求判斷是否攜帶Token * isAccessAllowed()判斷是否攜帶了有效的JwtToken * onAccessDenied()是沒有攜帶JwtToken的時候進行帳號密碼登陸,登陸成功容許訪問,登陸失敗拒絕訪問 * */ @Slf4j public class JwtFilter extends AccessControlFilter { /* * 1. 返回true,shiro就直接容許訪問url * 2. 返回false,shiro纔會根據onAccessDenied的方法的返回值決定是否容許訪問url * */ @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { log.warn("isAccessAllowed 方法被調用"); //這裏先讓它始終返回false來使用onAccessDenied()方法 return false; } /** * 返回結果爲true代表登陸經過 */ @Override protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception { log.warn("onAccessDenied 方法被調用"); //這個地方和前端約定,要求前端將jwtToken放在請求的Header部分 //因此之後發起請求的時候就須要在Header中放一個Authorization,值就是對應的Token HttpServletRequest request = (HttpServletRequest) servletRequest; String jwt = request.getHeader("Authorization"); log.info("請求的 Header 中藏有 jwtToken {}", jwt); JwtToken jwtToken = new JwtToken(jwt); /* * 下面就是固定寫法 * */ try { // 委託 realm 進行登陸認證 //因此這個地方最終仍是調用JwtRealm進行的認證 getSubject(servletRequest, servletResponse).login(jwtToken); //也就是subject.login(token) } catch (Exception e) { e.printStackTrace(); onLoginFail(servletResponse); //調用下面的方法向客戶端返回錯誤信息 return false; } return true; //執行方法中沒有拋出異常就表示登陸成功 } //登陸失敗時默認返回 401 狀態碼 private void onLoginFail(ServletResponse response) throws IOException { HttpServletResponse httpResponse = (HttpServletResponse) response; httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED); httpResponse.getWriter().write("login error"); } }
其中封裝了須要傳遞的jwt
字符串
package cn.coderymy.shiro; import org.apache.shiro.authc.AuthenticationToken; //這個就相似UsernamePasswordToken public class JwtToken implements AuthenticationToken { private String jwt; public JwtToken(String jwt) { this.jwt = jwt; } @Override//相似是用戶名 public Object getPrincipal() { return jwt; } @Override//相似密碼 public Object getCredentials() { return jwt; } //返回的都是jwt }
建立判斷jwt
是否有效的認證方式的Realm
package cn.coderymy.realm; import cn.coderymy.shiro.JwtToken; import cn.coderymy.util.JwtUtil; import lombok.extern.slf4j.Slf4j; import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; @Slf4j public class JwtRealm extends AuthorizingRealm { /* * 多重寫一個support * 標識這個Realm是專門用來驗證JwtToken * 不負責驗證其餘的token(UsernamePasswordToken) * */ @Override public boolean supports(AuthenticationToken token) { //這個token就是從過濾器中傳入的jwtToken return token instanceof JwtToken; } //受權 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { return null; } //認證 //這個token就是從過濾器中傳入的jwtToken @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String jwt = (String) token.getPrincipal(); if (jwt == null) { throw new NullPointerException("jwtToken 不容許爲空"); } //判斷 JwtUtil jwtUtil = new JwtUtil(); if (!jwtUtil.isVerify(jwt)) { throw new UnknownAccountException(); } //下面是驗證這個user是不是真實存在的 String username = (String) jwtUtil.decode(jwt).get("username");//判斷數據庫中username是否存在 log.info("在使用token登陸"+username); return new SimpleAuthenticationInfo(jwt,jwt,"JwtRealm"); //這裏返回的是相似帳號密碼的東西,可是jwtToken都是jwt字符串。還須要一個該Realm的類名 } }
配置一些信息
package cn.coderymy.config; import cn.coderymy.filter.JwtFilter; import cn.coderymy.realm.JwtRealm; import cn.coderymy.shiro.JwtDefaultSubjectFactory; import org.apache.shiro.mgt.DefaultSessionStorageEvaluator; import org.apache.shiro.mgt.DefaultSubjectDAO; import org.apache.shiro.mgt.SubjectFactory; import org.apache.shiro.realm.Realm; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.filter.authc.AnonymousFilter; import org.apache.shiro.web.filter.authc.LogoutFilter; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.servlet.Filter; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; //springBoot整合jwt實現認證有三個不同的地方,對應下面abc @Configuration public class ShiroConfig { /* * a. 告訴shiro不要使用默認的DefaultSubject建立對象,由於不能建立Session * */ @Bean public SubjectFactory subjectFactory() { return new JwtDefaultSubjectFactory(); } @Bean public Realm realm() { return new JwtRealm(); } @Bean public DefaultWebSecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(realm()); /* * b * */ // 關閉 ShiroDAO 功能 DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO(); DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator(); // 不須要將 Shiro Session 中的東西存到任何地方(包括 Http Session 中) defaultSessionStorageEvaluator.setSessionStorageEnabled(false); subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator); securityManager.setSubjectDAO(subjectDAO); //禁止Subject的getSession方法 securityManager.setSubjectFactory(subjectFactory()); return securityManager; } @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean() { ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean(); shiroFilter.setSecurityManager(securityManager()); shiroFilter.setLoginUrl("/unauthenticated"); shiroFilter.setUnauthorizedUrl("/unauthorized"); /* * c. 添加jwt過濾器,並在下面註冊 * 也就是將jwtFilter註冊到shiro的Filter中 * 指定除了login和logout以外的請求都先通過jwtFilter * */ Map<String, Filter> filterMap = new HashMap<>(); //這個地方其實另外兩個filter能夠不設置,默認就是 filterMap.put("anon", new AnonymousFilter()); filterMap.put("jwt", new JwtFilter()); filterMap.put("logout", new LogoutFilter()); shiroFilter.setFilters(filterMap); // 攔截器 Map<String, String> filterRuleMap = new LinkedHashMap<>(); filterRuleMap.put("/login", "anon"); filterRuleMap.put("/logout", "logout"); filterRuleMap.put("/**", "jwt"); shiroFilter.setFilterChainDefinitionMap(filterRuleMap); return shiroFilter; } }
package cn.coderymy.controller; import cn.coderymy.util.JwtUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import java.util.HashMap; import java.util.Map; @Slf4j @Controller public class LoginController { @RequestMapping("/login") public ResponseEntity<Map<String, String>> login(String username, String password) { log.info("username:{},password:{}",username,password); Map<String, String> map = new HashMap<>(); if (!"tom".equals(username) || !"123".equals(password)) { map.put("msg", "用戶名密碼錯誤"); return ResponseEntity.ok(map); } JwtUtil jwtUtil = new JwtUtil(); Map<String, Object> chaim = new HashMap<>(); chaim.put("username", username); String jwtToken = jwtUtil.encode(username, 5 * 60 * 1000, chaim); map.put("msg", "登陸成功"); map.put("token", jwtToken); return ResponseEntity.ok(map); } @RequestMapping("/testdemo") public ResponseEntity<String> testdemo() { return ResponseEntity.ok("我愛蛋炒飯"); } }
在JwtRealm中的受權部分,可使用JwtUtil.decode(jwt).get("username")
獲取到username,使用username去數據庫中查找到對應的權限,而後將權限賦值給這個用戶就能夠實現權限的認證了