Shrio是一個輕量級的,基於AOP 和 Servlet 過濾器的安全框架。它提供全面的安全性解決方案,同時在 Web 請求級和方法調用級處理身份確認和受權。java
JWT(JSON Web Token)是目前最流行的跨域身份驗證解決方案,具備加密和自包含的特性。web
1.maven配置redis
<!-- shiro --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.3.2</version> </dependency> <!--jwt--> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.2.0</version> </dependency>
2.自定義Token Authenticationspring
package com.bby.security; import org.apache.shiro.authc.AuthenticationToken; /** * 自定義jwt類型的token * * @author: zhangyang * @create: 2018/11/28 8:39 **/ public class MyJWTToken implements AuthenticationToken { private String token; public MyJWTToken(String token) { this.token = token; } @Override public Object getPrincipal() { return token; } @Override public Object getCredentials() { return token; } }
3.自定義Shiro過濾器數據庫
package com.bby.security; import com.alibaba.fastjson.JSONObject; import com.bby.common.vo.Result; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.shiro.util.AntPathMatcher; import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.io.PrintWriter; /** * jwt token filter * * @Author zhangyang * @Date 下午 8:42 2018/11/27 0027 **/ @Slf4j public class MyJWTFilter extends BasicHttpAuthenticationFilter { private String tokenHeader; private String loginUri; public MyJWTFilter(String tokenHeader, String loginUri) { this.tokenHeader = tokenHeader; this.loginUri = loginUri; } /** * 若是是登陸則直接放行; * 若是帶有 token,則對 token 進行檢查 * * @param request * @param response * @param mappedValue * @return */ @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { HttpServletRequest req = (HttpServletRequest) request; AntPathMatcher matcher = new AntPathMatcher(); // 開放登陸接口訪問 if (matcher.match(loginUri, req.getRequestURI())) { return true; } // 判斷請求的請求頭是否帶上token if (StringUtils.isBlank(req.getHeader(tokenHeader))) { return false; } // 若是存在,則進入 executeLogin 方法執行登入,檢查 token 是否正確 try { executeLogin(request, response); } catch (Exception e) { log.error(e.getMessage()); try { // globalExceptionHandler沒法處理filter中的異常,這裏手動處理 PrintWriter out = response.getWriter(); out.print(JSONObject.toJSON(Result.failureWithCode(Result.UNAUTHORIZED, e.getMessage()))); out.flush(); } catch (IOException e1) { e1.printStackTrace(); } return false; } return true; } /** * 執行登錄操做 * * @param request * @param response * @return */ @Override protected boolean executeLogin(ServletRequest request, ServletResponse response) { HttpServletRequest httpServletRequest = (HttpServletRequest) request; String token = httpServletRequest.getHeader(tokenHeader); MyJWTToken jwtToken = new MyJWTToken(token); // 提交給realm進行登入,若是錯誤他會拋出異常並被捕獲 getSubject(request, response).login(jwtToken); // 若是沒有拋出異常則表明登入成功,返回true return true; } }
4.自定義Shiro Realmapache
package com.bby.security; import com.bby.common.util.RedisRepository; import com.bby.mapper.system.SysUserMapper; import org.apache.commons.collections4.CollectionUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.List; /** * 實現AuthorizingRealm接口用戶用戶認證 * * @author: zhangyang * @create: 2018/11/24 21:25 **/ @Component public class MyRealm extends AuthorizingRealm { @Autowired private RedisRepository redisRepository; @Autowired private SysUserMapper sysUserMapper; /** * 獲取用戶權限信息 * * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { // 獲取token String token = (String) principalCollection.getPrimaryPrincipal(); // 查詢用戶權限信息 List<String> permissions = sysUserMapper.getPermissionByUserId(JWTUtil.getUserId(token)); // 只添加權限(角色方式不靈活) SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); if (CollectionUtils.isNotEmpty(permissions)) { simpleAuthorizationInfo.addStringPermissions(permissions); } return simpleAuthorizationInfo; } /** * 獲取用戶認證信息 * * @param authenticationToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { // 加這一步的目的是在Post請求的時候會先進認證,而後在到請求 Object principal = authenticationToken.getPrincipal(); if (principal == null) { return null; } String token = (String) principal; // 解密得到username,用於和數據庫進行對比 String claim = JWTUtil.getUserId(token); // 驗證緩存中的登陸狀態 if (claim == null || !JWTUtil.verify(token, claim) || !redisRepository.exists(token)) { throw new AuthenticationException("token validation failed"); } // 獲取用戶信息 SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(token, token, getName()); return simpleAuthenticationInfo; } /** * 設置支持的token類型爲自定義jwtToken * * @param token * @return */ @Override public boolean supports(AuthenticationToken token) { return token instanceof MyJWTToken; } /** * 覆蓋驗證密碼是否匹配的方法,由於在自定義的login方法中已經實現了 * * @param token * @param info * @throws AuthenticationException */ @Override protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException { } }
5.登陸代碼json
package com.bby.controller; import com.bby.common.vo.Result; import com.bby.security.JWTUtil; import com.bby.service.system.ISysUserService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import java.util.Map; /** * 登陸/鑑權controller * * @author: zhangyang * @create: 2018/11/25 20:26 **/ @Api(tags = "認證受權") @RestController @RequestMapping("auth") public class AuthController { @Autowired private ISysUserService sysUserService; /** * 登陸 * * @param authInfo * @return */ @ApiOperation("登陸") @PostMapping("login") public Result login(@ApiParam("用戶名") @RequestBody Map<String, String> authInfo) { return sysUserService.validate(authInfo.get("username"), authInfo.get("password")); } /** * 登出 * * @return */ @ApiOperation("登出") @PostMapping("logout") public Result logout(HttpServletRequest request) { // 由於token的方式是無狀態的,要實現登出,則須要使用緩存來保存狀態 return sysUserService.logout(JWTUtil.getToken(request)); } /** * 用戶信息 * 用戶名: * @return */ @ApiOperation("獲取用戶信息") @GetMapping("info") public Result info(HttpServletRequest request) { return sysUserService.getAuthInfo(JWTUtil.getUserId(JWTUtil.getToken(request))); } }
6.shiro配置跨域
package com.bby.security; import org.apache.shiro.mgt.DefaultSessionStorageEvaluator; import org.apache.shiro.mgt.DefaultSubjectDAO; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; import javax.servlet.Filter; import java.util.HashMap; import java.util.Map; /** * shiro 配置 * * @author: zhangyang * @create: 2018/11/24 21:34 **/ @Configuration public class ShiroConfiguration { @Value("${jwt.token-header}") private String tokenHeader; @Value("${jwt.filter-name}") private String jwtFilterName; @Value("${login.uri}") private String loginUri; /** * 將本身的驗證方式加入容器 * * @return */ @Autowired private MyRealm myRealm; /** * 權限管理,配置主要是Realm的管理認證 * * @return */ @Bean @DependsOn("myRealm") public SecurityManager securityManager() { DefaultWebSecurityManager manager = new DefaultWebSecurityManager(); // 使用本身的realm System.out.println(myRealm); manager.setRealm(myRealm); // 關閉shiro自帶的session DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO(); DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator(); defaultSessionStorageEvaluator.setSessionStorageEnabled(false); subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator); manager.setSubjectDAO(subjectDAO); return manager; } /** * Filter工廠,設置對應的過濾條件和跳轉條件 * * @param securityManager * @return */ @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean(); // 添加本身的過濾器而且取名爲jwt Map<String, Filter> filterMap = new HashMap<>(); System.out.println(tokenHeader); filterMap.put("jwt", new MyJWTFilter(tokenHeader, loginUri)); factoryBean.setFilters(filterMap); factoryBean.setSecurityManager(securityManager); factoryBean.setUnauthorizedUrl("/401"); Map<String, String> filterRuleMap = new HashMap<>(); // 全部請求經過咱們本身的JWT Filter filterRuleMap.put("/**", "jwt"); // 訪問401和404頁面不經過咱們的Filter filterRuleMap.put("/401", "anon"); factoryBean.setFilterChainDefinitionMap(filterRuleMap); return factoryBean; } /** * 下面的代碼是添加註解支持 */ @Bean public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); // 強制使用cglib,防止重複代理和可能引發代理出錯的問題 // https://zhuanlan.zhihu.com/p/29161098 defaultAdvisorAutoProxyCreator.setProxyTargetClass(true); return defaultAdvisorAutoProxyCreator; } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) { AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager); return advisor; } }