javascript
学习Spring Boot:(十三)配置 Shiro 权限认证
經(jīng)過前面學(xué)習(xí) Apache Shiro ,現(xiàn)在結(jié)合 Spring Boot 使用在項(xiàng)目里,進(jìn)行相關(guān)配置。
正文
添加依賴
在 pom.xml 文件中添加 shiro-spring 的依賴:
<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>${shiro.version}</version></dependency>RBAC
RBAC 1 是基于角色的訪問控制,權(quán)限與角色關(guān)聯(lián),給用戶配置相關(guān)角色,來獲取權(quán)限信息。
Shiro 配置
新建一個(gè)新的 Shiro 配置類 ShiroConfig:
package com.wuwii.common.config;import com.wuwii.module.sys.autho2.OAuth2Realm; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.session.mgt.SessionManager; import org.apache.shiro.spring.LifecycleBeanPostProcessor; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;import java.util.LinkedHashMap; import java.util.Map;/*** Apache Shiro 核心通過 Filter 來實(shí)現(xiàn),就好像SpringMvc 通過DispachServlet 來主控制一樣。* 既然是使用 Filter 一般也就能猜到,是通過URL規(guī)則來進(jìn)行過濾和權(quán)限校驗(yàn),* 所以我們需要定義一系列關(guān)于URL的規(guī)則和訪問權(quán)限。** @author KronChan* @version 1.0* @since <pre>2018/2/9 10:35</pre>*/ @Configuration public class ShiroConfig {@Beanpublic SessionManager sessionManager() {DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();sessionManager.setSessionValidationSchedulerEnabled(true);sessionManager.setSessionIdCookieEnabled(true);return sessionManager;}/*** 注冊(cè)安全管理,必須設(shè)置 SecurityManager** @param oAuth2Realm 認(rèn)證* @param sessionManager 緩存* @return*/@Beanpublic SecurityManager securityManager(OAuth2Realm oAuth2Realm, SessionManager sessionManager) {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();// 可以添加多個(gè)認(rèn)證,執(zhí)行順序是有影響的securityManager.setRealm(oAuth2Realm);securityManager.setSessionManager(sessionManager);return securityManager;}@Beanpublic ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();shiroFilter.setSecurityManager(securityManager);//自定義一個(gè)oauth2攔截器,不設(shè)置就是使用默認(rèn)的攔截器/*Map<String, Filter> filters = new HashMap<>();filters.put("oauth2", new OAuth2Filter());shiroFilter.setFilters(filters);*///攔截器//<!-- 過濾鏈定義,從上向下順序執(zhí)行,一般將 /**放在最為下邊 -->//<!-- authc:所有url都必須認(rèn)證通過才可以訪問; anon:所有url都都可以匿名訪問-->Map<String, String> filterMap = new LinkedHashMap<>();//配置退出過濾器,其中的具體的退出代碼Shiro已經(jīng)替我們實(shí)現(xiàn)了filterMap.put("/sys/logout", "logout");// 驗(yàn)證碼filterMap.put("/sys/captcha.jpg", "anon");// 設(shè)置系統(tǒng)模塊下訪問需要權(quán)限filterMap.put("/sys/login", "anon");// 自定義的攔截//filterMap.put("/sys/**", "oauth2");filterMap.put("/sys/**", "authc");// 登陸的 urlshiroFilter.setLoginUrl("/sys/login");// 登陸成功跳轉(zhuǎn)的 urlshiroFilter.setSuccessUrl("/");// 未授權(quán)的 url// shiroFilter.setUnauthorizedUrl("/login.html");//未授權(quán)界面;shiroFilter.setUnauthorizedUrl("/403");shiroFilter.setFilterChainDefinitionMap(filterMap);return shiroFilter;}/*** Shiro生命周期處理器* @return*/@Beanpublic LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {return new LifecycleBeanPostProcessor();}/*** 開啟Shiro的注解,* (如@RequiresRoles,@RequiresPermissions),需借助SpringAOP掃描使用Shiro注解的類,* 并在必要時(shí)進(jìn)行安全邏輯驗(yàn)證 * 配置以下兩個(gè)bean(DefaultAdvisorAutoProxyCreator(可選)和AuthorizationAttributeSourceAdvisor)即可實(shí)現(xiàn)此功能** @return*/@Beanpublic DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();proxyCreator.setProxyTargetClass(true);return proxyCreator;}/*** 開啟 shiro aop注解支持.** @param securityManager* @return*/@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();advisor.setSecurityManager(securityManager);return advisor;}/*** 憑證匹配器* (由于我們的密碼校驗(yàn)交給Shiro的SimpleAuthenticationInfo進(jìn)行處理了* 所以我們需要修改下doGetAuthenticationInfo中的代碼;* )* <b>需要在身份認(rèn)證中添加 realm.setCredentialsMatcher(hashedCredentialsMatcher())</b>* @return*//*@Beanpublic HashedCredentialsMatcher hashedCredentialsMatcher() {HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:這里使用MD5算法;hashedCredentialsMatcher.setHashIterations(2);//散列的次數(shù),比如散列兩次,相當(dāng)于 md5(md5(""));return hashedCredentialsMatcher;}*/ }Filter Chain定義說明:
Shiro內(nèi)置的FilterChain:
| anon | org.apache.shiro.web.filter.authc.AnonymousFilter |
| authc | org.apache.shiro.web.filter.authc.FormAuthenticationFilter |
| authcBasic | org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter |
| perms | org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter |
| port | org.apache.shiro.web.filter.authz.PortFilter |
| rest | org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter |
| roles | org.apache.shiro.web.filter.authz.RolesAuthorizationFilter |
| ssl | org.apache.shiro.web.filter.authz.SslFilter |
| user | org.apache.shiro.web.filter.authc.UserFilter |
* anon:所有url都都可以匿名訪問
* authc: 需要認(rèn)證才能進(jìn)行訪問
* user:配置記住我或認(rèn)證通過可以訪問
自定義的攔截器(可選)
如果需要按照自己的需要定義一個(gè) oauth2 的攔截器,則需要 繼承 AuthenticatingFilter 實(shí)現(xiàn)幾個(gè)方法即可。
/*** oauth2過濾器*/ public class OAuth2Filter extends AuthenticatingFilter {/*** logger*/private static final Logger LOGGER = LoggerFactory.getLogger(OAuth2Filter.class);@Overrideprotected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {//獲取請(qǐng)求tokenString token = getRequestToken((HttpServletRequest) request);if (StringUtils.isBlank(token)) {return null;}return new OAuth2Token(token);}/*** shiro權(quán)限攔截核心方法 返回true允許訪問resource,* @param request* @param response* @param mappedValue* @return*/@Overrideprotected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {return false;}/*** 當(dāng)訪問拒絕時(shí)是否已經(jīng)處理了;* 如果返回true表示需要繼續(xù)處理;* 如果返回false表示該攔截器實(shí)例已經(jīng)處理完成了,將直接返回即可。* @param request* @param response* @return* @throws Exception*/@Overrideprotected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {//獲取請(qǐng)求token,如果token不存在,直接返回401String token = getRequestToken((HttpServletRequest) request);if (StringUtils.isBlank(token)) {HttpServletResponse httpResponse = (HttpServletResponse) response;((HttpServletResponse) response).setStatus(401);response.getWriter().print("沒有權(quán)限,請(qǐng)聯(lián)系管理員授權(quán)");return false;}return executeLogin(request, response);}/*** 鑒定失敗,返回錯(cuò)誤信息* @param token* @param e* @param request* @param response* @return*/@Overrideprotected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {try {((HttpServletResponse) response).setStatus(401);response.getWriter().print("沒有權(quán)限,請(qǐng)聯(lián)系管理員授權(quán)");} catch (IOException e1) {LOGGER.error(e1.getMessage(), e1);}return false;}/*** 獲取請(qǐng)求的token*/private String getRequestToken(HttpServletRequest httpRequest) {//從header中獲取tokenString token = httpRequest.getHeader("token");//如果header中不存在token,則從參數(shù)中獲取tokenif (StringUtils.isBlank(token)) {return httpRequest.getParameter("token");}// 還可以實(shí)現(xiàn)從 cookie 獲取Cookie[] cookies = httpRequest.getCookies();if(null==cookies||cookies.length==0){return null;}for (Cookie cookie : cookies) {if (cookie.getName().equals("token")) {token = cookie.getValue();continue;}}return token;} }具體實(shí)現(xiàn)可以參考我的上篇文章 《Apache Shiro的攔截器和認(rèn)證》
認(rèn)證實(shí)現(xiàn)
Shiro的認(rèn)證過程最終會(huì)交由Realm執(zhí)行,這時(shí)會(huì)調(diào)用Realm的 getAuthenticationInfo(token) 方法。
該方法主要執(zhí)行以下操作:
而在我們的應(yīng)用程序中要做的就是自定義一個(gè)Realm類,繼承 AuthorizingRealm 抽象類,重載 doGetAuthenticationInfo (),重寫獲取用戶信息的方法。
@Component public class OAuth2Realm extends AuthorizingRealm {@Resourceprivate ShiroService shiroService;@Resourceprivate SysUserService sysUserService;/*** 此方法調(diào)用 hasRole,hasPermission的時(shí)候才會(huì)進(jìn)行回調(diào).** 權(quán)限信息.(授權(quán)):* 1、如果用戶正常退出,緩存自動(dòng)清空;* 2、如果用戶非正常退出,緩存自動(dòng)清空;* 3、如果我們修改了用戶的權(quán)限,而用戶不退出系統(tǒng),修改的權(quán)限無法立即生效。* (需要手動(dòng)編程進(jìn)行實(shí)現(xiàn);放在service進(jìn)行調(diào)用)* 在權(quán)限修改后調(diào)用realm中的方法,realm已經(jīng)由spring管理,所以從spring中獲取realm實(shí)例,* 調(diào)用clearCached方法;* :Authorization 是授權(quán)訪問控制,用于對(duì)用戶進(jìn)行的操作授權(quán),證明該用戶是否允許進(jìn)行當(dāng)前操作,如訪問某個(gè)鏈接,某個(gè)資源文件等。*** 當(dāng)沒有使用緩存的時(shí)候,不斷刷新頁面的話,這個(gè)代碼會(huì)不斷執(zhí)行,* 當(dāng)其實(shí)沒有必要每次都重新設(shè)置權(quán)限信息,所以我們需要放到緩存中進(jìn)行管理;* 當(dāng)放到緩存中時(shí),這樣的話,doGetAuthorizationInfo就只會(huì)執(zhí)行一次了,* 緩存過期之后會(huì)再次執(zhí)行。** @param principals* @return*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {SysUserEntity user =(SysUserEntity) (principals.getPrimaryPrincipal());;// 獲取該用戶權(quán)限列表Set<String> permsSet = shiroService.getUserPermissions(user.getId());SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();info.setStringPermissions(permsSet);return info;}/*** 認(rèn)證回調(diào)函數(shù),登錄時(shí)調(diào)用* 首先根據(jù)傳入的用戶名獲取User信息;然后如果user為空,那么拋出沒找到帳號(hào)異常UnknownAccountException;* 如果user找到但鎖定了拋出鎖定異常LockedAccountException;最后生成AuthenticationInfo信息,* 交給間接父類AuthenticatingRealm使用CredentialsMatcher進(jìn)行判斷密碼是否匹配,*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;SysUserEntity user = sysUserService.queryByUsername(usernamePasswordToken.getUsername());//賬號(hào)不存在、密碼錯(cuò)誤if (user == null) {throw new KCException("賬號(hào)或密碼不正確");}// 交給 shiro 自己去驗(yàn)證,// 明文驗(yàn)證return new SimpleAuthenticationInfo(user, // 存入憑證的信息,登陸成功后可以使用 SecurityUtils.getSubject().getPrincipal();在任何地方使用它user.getPassword(),getName());// 加密的方式// 交給AuthenticatingRealm使用CredentialsMatcher進(jìn)行密碼匹配,如果覺得人家的不好可以自定義實(shí)現(xiàn)/*return new SimpleAuthenticationInfo(user,user.getPassword(),ByteSource.Util.bytes(user.getSalt()), // 加鹽,可以注冊(cè)憑證匹配器 HashedCredentialsMatcher 告訴它怎么加密的getName());*/} }實(shí)現(xiàn)上面兩個(gè)方法即可完成身份驗(yàn)證和權(quán)限驗(yàn)證。
登陸實(shí)現(xiàn)
@PostMapping("/login")@ApiOperation("系統(tǒng)登陸")public ResponseEntity<String> login(@RequestBody SysUserLoginForm userForm) {String kaptcha = ShiroUtils.getKaptcha(Constants.KAPTCHA_SESSION_KEY);if (!userForm.getCaptcha().equalsIgnoreCase(kaptcha)) {throw new KCException("驗(yàn)證碼不正確!");}UsernamePasswordToken token = new UsernamePasswordToken(userForm.getUsername(), userForm.getPassword());Subject currentUser = SecurityUtils.getSubject();currentUser.login(token);//賬號(hào)鎖定if (getUser().getStatus() == SysConstant.SysUserStatus.LOCK) {throw new KCException("賬號(hào)已被鎖定,請(qǐng)聯(lián)系管理員");}return ResponseEntity.status(HttpStatus.OK).body("登陸成功!");}權(quán)限驗(yàn)證
@ApiOperation("用于測試,查詢")@ApiImplicitParam(name = "string", value = "id", dataType = "string")@RequiresPermissions("sys:user:list1")@GetMapping()public ResponseEntity<List<SysUserEntity>> query(@CustomValid String string) {return new ResponseEntity<>(sysUserService.query(new SysUserEntity()), OK);}簡單測試一個(gè)例子,sys:user:list1 多加一個(gè) 1 肯定會(huì)驗(yàn)證失敗,查看程序會(huì)做什么,它會(huì)去我們定義的 Realm 中的 doGetAuthorizationInfo(PrincipalCollection principals) 方法中,執(zhí)行查詢?cè)撚脩舻乃袡?quán)限。
驗(yàn)證失敗后最后程序結(jié)果如下:
權(quán)限注解
@RequiresAuthentication 表示當(dāng)前Subject已經(jīng)通過login進(jìn)行了身份驗(yàn)證;即Subject. isAuthenticated()返回true。 @RequiresUser 表示當(dāng)前Subject已經(jīng)身份驗(yàn)證或者通過記住我登錄的。@RequiresGuest 表示當(dāng)前Subject沒有身份驗(yàn)證或通過記住我登錄過,即是游客身份。@RequiresRoles(value={“admin”, “user”}, logical= Logical.AND) 表示當(dāng)前Subject需要角色admin和user。@RequiresPermissions (value={“user:a”, “user:b”}, logical= Logical.OR) 表示當(dāng)前Subject需要權(quán)限user:a或user:b。參考資料
- [Spring Boot 集成-Shiro]
- 39.2. Spring Boot Shiro權(quán)限管理【從零開始學(xué)Spring Boot】
總結(jié)
以上是生活随笔為你收集整理的学习Spring Boot:(十三)配置 Shiro 权限认证的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 开源python-文档撰写
- 下一篇: 剑指offer全套题解:Python版