在shiro基礎上整合jwt,可在項目中直接使用呦
前幾天給大家講解了一下shiro,后臺一些小伙伴跑來給我留言說:“一般不都是shiro結合jwt做身份和權限驗證嗎?能不能再講解一下jwt的用法呢?“今天阿Q就給大家講一下shiro整合jwt做權限校驗吧。
首先呢我還是要說一下jwt的概念:JWT全稱Json web token , 是為了在網絡應用環境間傳遞聲明而執行的一種基于JSON的開放標準。該token被設計為緊湊且安全的,特別適用于分布式站點的單點登錄(SSO)場景。JWT的聲明一般被用來在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便于從資源服務器獲取資源,也可以增加一些額外的其它業務邏輯所必須的聲明信息,該token也可直接被用于認證,也可被加密。通俗點說呢,就是之前的session為了區分是哪個用戶發來的請求,需要在服務端存儲用戶信息,需要消耗服務器資源。并且隨著用戶量的增大,勢必會擴展服務器,采用分布式系統,這樣的話session就可能就不太合適了,而我們今天說的jwt呢就很好地解決了單點登錄問題,很容易的解決session共享的問題。
話不多說,直接上整合教程(本期是在上期shiro的基礎上進行的改造):
一、在pom文件中引入jwt的依賴包
二、寫一個工具類用于生成簽名和驗證簽名
public class JwtUtil { //JWT-account public static final String ACCOUNT = "username"; //JWT-currentTimeMillis public final static String CURRENT_TIME_MILLIS = "currentTimeMillis"; //有效期時間2小時 public static final long EXPIRE_TIME = 2 * 60 * 60 * 1000L; //秘鑰 public static final String SECRET_KEY = "shirokey"; /** * 生成簽名返回token * * @param account * @param currentTimeMillis * @return */ public static String sign(String account, String currentTimeMillis) { // 帳號加JWT私鑰加密 String secret = account + SECRET_KEY; // 此處過期時間,單位:毫秒,在當前時間到后邊的20分鐘內都是有效的 Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME); //采用HMAC256加密 Algorithm algorithm = Algorithm.HMAC256(secret); return JWT.create() .withClaim(ACCOUNT, account) .withClaim(CURRENT_TIME_MILLIS, currentTimeMillis) .withExpiresAt(date) //創建一個新的JWT,并使用給定的算法進行標記 .sign(algorithm); } /** * 校驗token是否正確 * * @param token * @return */ public static boolean verify(String token) { String secret = getClaim(token, ACCOUNT) + SECRET_KEY; Algorithm algorithm = Algorithm.HMAC256(secret); JWTVerifier verifier = JWT.require(algorithm) .build(); verifier.verify(token); return true; } /** * 獲得Token中的信息無需secret解密也能獲得 * * @param token * @param claim * @return */ public static String getClaim(String token, String claim) { try { DecodedJWT jwt = JWT.decode(token); return jwt.getClaim(claim).asString(); } catch (JWTDecodeException e) { return null; } } }
三、封裝自己的token,用于后邊校驗token類型
public class JwtToken implements AuthenticationToken { private final String token; public JwtToken(String token){ this.token = token; } @Override public Object getPrincipal() { return token; } @Override public Object getCredentials() { return token; }
四、我們需要在登陸時創建token
//service中的登錄處理 @Override public UserTokenDTO login(UserTokenDTO userInfo) { // 從數據庫獲取對應用戶名密碼的用戶 SysUserInfo uInfo = userInfoMapper.getUserByLogin(userInfo.getName()); if (null == uInfo) { //用戶信息不存在 throw new BusinessException(CommonResultStatus.USERNAME_ERROR); } else if (!userInfo.getPassword().equals(uInfo.getPassword())) { //密碼錯誤 throw new BusinessException(CommonResultStatus.PASSWORD_ERROR); } //生成jwtToken userInfo.setToken(JwtUtil.sign(userInfo.getName(),String.valueOf(System.currentTimeMillis()))); return userInfo; }
五、在其他需要登錄后才能訪問的請求中解析token,所以我們要自定義過濾器
public class JwtFilter extends AccessControlFilter { //設置請求頭中需要傳遞的字段名 protected static final String AUTHORIZATION_HEADER = "Access-Token"; /** * 表示是否允許訪問,mappedValue就是[urls]配置中-參數部分, * 如果允許訪問返回true,否則false * @author cheetah * @date 2020/11/24 * @param request: * @param response: * @param mappedValue: * @return: boolean */ @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { return false; } /** * 表示當訪問拒絕時是否已經處理了, * 如果返回true表示需要繼續處理, * 如果返回false表示該-實例已經處理了,將直接返回即可 * @author cheetah * @date 2020/11/24 * @param request: * @param response: * @return: boolean */ @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { HttpServletRequest req = (HttpServletRequest) request; // 解決跨域問題 if(HttpMethod.OPTIONS.toString().matches(req.getMethod())) { return true; } if (isLoginAttempt(request, response)) { //生成jwt token JwtToken token = new JwtToken(req.getHeader(AUTHORIZATION_HEADER)); //委托給Realm進行驗證 try { //調用登陸會走Realm中的身份驗證方法 getSubject(request, response).login(token); return true; } catch (Exception e) { } }else{ throw new BusinessException(CommonResultStatus.LOGIN_ERROR); } return false; } /** * 判斷是否有頭部參數 * @author cheetah * @date 2020/11/24 * @param request: * @param response: * @return: boolean */ protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) { HttpServletRequest req = (HttpServletRequest) request; String authorization = req.getHeader(AUTHORIZATION_HEADER); return authorization != null; } }
六、當濾器中調用subject.login(token)方法時,會走自定義Realm中的doGetAuthenticationInfo(AuthenticationToken token)方法來驗證身份
@Slf4j public class JwtRealm extends AuthorizingRealm { @Autowired private UserInfoService userService; //驗證是不是自己的token類型 @Override public boolean supports(AuthenticationToken token) { return token instanceof JwtToken; } /** * 身份驗證 * @author cheetah * @date 2020/11/25 * @param token: * @return: org.apache.shiro.authc.AuthenticationInfo */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String credentials = (String) token.getCredentials(); String username = null; try { //jwt驗證token boolean verify = JwtUtil.verify(credentials); if (!verify) { throw new AuthenticationException("Token校驗不正確"); } username = JwtUtil.getClaim(credentials, JwtUtil.ACCOUNT); } catch (Exception e) { throw new BusinessException(CommonResultStatus.TOKEN_CHECK_ERROR,e.getMessage()); } //交給AuthenticatingRealm使用CredentialsMatcher進行密碼匹配,不設置則使用默認的SimpleCredentialsMatcher SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo( username, //用戶名 credentials, //憑證 getName() //realm name ); return authenticationInfo; } /** * 權限校驗(次數不做過多講解) * @author cheetah * @date 2020/11/25 * @param principals: * @return: org.apache.shiro.authz.AuthorizationInfo */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { // String username = principals.toString(); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); //角色權限暫時不加 // authorizationInfo.setRoles(userService.getRoles(username)); // authorizationInfo.setStringPermissions(userService.queryPermissions(username)); return authorizationInfo; } }
七、接下來我們需要修改ShiroConfig文件,將自定義的Filter和Realm交由SecurityManager進行管理
/** 此類較長,只展示部分重要代碼,其余代碼可在公眾號“阿Q說”中回復"jwt"獲取源碼 **/ @Configuration @Slf4j public class ShiroConfig { /** * 創建ShiroFilterFactoryBean * @author cheetah * @date 2020/11/21 * @return: org.apache.shiro.spring.web.ShiroFilterFactoryBean */ @Bean public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){ ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 必須設置 SecurityManager shiroFilterFactoryBean.setSecurityManager(securityManager); //設置shiro內置過濾器 Map
重點:將自定義的Realm交由SecurityManage管理,關閉shiro自帶的session
接下來我們啟動成程序驗證一下:當我們未登錄時,請求失敗,需要先登錄
登錄成功獲取token信息
當帶著頭部信息"Access-Token"訪問時就可以獲取信息了。
想了解更多學習知識,請關注“阿Q說代碼”,回復“jwt”可獲取本期源碼!你也可以后臺留言說出你的疑惑,阿Q將會在后期的文章中為你解答。每天學習一點點,每天進步一點點。
JSON
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。