SSM整合Shiro进行登陆认证和授权详细配置
本篇博客將進(jìn)行詳細(xì)介紹Shiro+Spring+SpringMVC+Mybatis+數(shù)據(jù)庫整合并進(jìn)行登陸認(rèn)證和授權(quán)詳細(xì)配置。
SSM的整合可以參考:https://blog.csdn.net/a745233700/article/details/81049763
下面主要介紹Shiro與SSM的整合和使用:
1、導(dǎo)入Shiro需要的maven依賴:
<!-- shiro --><!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-all --><dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.2.3</version> </dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-ehcache</artifactId><version>1.2.3</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-web</artifactId><version>1.2.3</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.2.3</version></dependency>或者一次性導(dǎo)入shiro的所有依賴:
<dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-all</artifactId><version>1.2.3</version> </dependency>2、在web.xml文件中配置shiro的filter攔截器:
在與Spring整合中,shiro也通過Filter進(jìn)行攔截,但是攔截后的操作權(quán)交給spring中配置的filterChainDefinitions(過濾鏈)處理。
<!-- shiro的filter --><!-- shiro過慮器,DelegatingFilterProxy通過代理模式將spring容器中的bean和filter關(guān)聯(lián)起來 --><filter><filter-name>shiroFilter</filter-name><filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class><!-- 設(shè)置true由servlet容器控制filter的生命周期 --><init-param><param-name>targetFilterLifecycle</param-name><param-value>true</param-value></init-param><!-- 設(shè)置spring容器filter的bean id,如果不設(shè)置則找與filter-name一致的bean--><init-param><param-name>targetBeanName</param-name><param-value>shiroFilter</param-value></init-param></filter><filter-mapping><filter-name>shiroFilter</filter-name><url-pattern>/*</url-pattern></filter-mapping>3、配置Shiro框架的相關(guān)配置:(applicationContext-shiro.xml文件中)
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsdhttp://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd"><!-- id屬性值要對應(yīng) web.xml中shiro的filter對應(yīng)的bean --><bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"><property name="securityManager" ref="securityManager"></property><!-- loginUrl認(rèn)證提交地址,如果沒有認(rèn)證將會請求此地址進(jìn)行認(rèn)證,請求地址將由formAuthenticationFilter進(jìn)行表單認(rèn)證 --><property name="loginUrl" value="/login.action"></property><!-- 認(rèn)證成功統(tǒng)一跳轉(zhuǎn)到first.action,建議不配置,shiro認(rèn)證成功會默認(rèn)跳轉(zhuǎn)到上一個請求路徑 --><!-- <property name="successUrl" value="/first.action"></property> --><!-- 通過unauthorizedUrl指定沒有權(quán)限操作時跳轉(zhuǎn)頁面,這個位置會攔截不到,下面有給出解決方法 --><!-- <property name="unauthorizedUrl" value="/refuse.jsp"></property> --><!-- 過濾器定義,從上到下執(zhí)行,一般將/**放在最下面 --><property name="filterChainDefinitions"><value><!-- 對靜態(tài)資源設(shè)置匿名訪問 -->/images/** = anon/js/** = anon/styles/** = anon/validatecode.jsp =anon<!-- 請求logout.action地址,shiro去清除session -->/logout.action = logout<!-- /**=authc 所有的url都必須通過認(rèn)證才可以訪問 -->/** = authc<!-- /**=anon 所有的url都可以匿名訪問,不能配置在最后一排,不然所有的請求都不會攔截 --></value></property></bean><!-- 解決shiro配置的沒有權(quán)限訪問時,unauthorizedUrl不跳轉(zhuǎn)到指定路徑的問題 --><bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"><property name="exceptionMappings"><props><prop key="org.apache.shiro.authz.UnauthorizedException">/refuse</prop></props></property></bean><!-- securityManager安全管理器 --><bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"><property name="realm" ref="customRealm"></property></bean><!-- 配置自定義Realm --><bean id="customRealm" class="com.zwp.shiro.CustomRealm"><!-- 將憑證匹配器設(shè)置到realm中,realm按照憑證匹配器的要求進(jìn)行散列 --><property name="credentialsMatcher" ref="credentialsMatcher"></property></bean><!-- 憑證匹配器 --><bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"><!-- 加密算法 --><property name="hashAlgorithmName" value="md5"></property><!-- 迭代次數(shù) --><property name="hashIterations" value="1"></property></bean> </beans>4、配置自定義的Realm,重寫認(rèn)證的方法:
//自定義的Realm public class CustomRealm extends AuthorizingRealm{//注入service@Autowiredprivate SysService sysService;// 設(shè)置realm的名稱@Overridepublic void setName(String name) {super.setName("customRealm");}//認(rèn)證的方法@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {// token是用戶輸入的用戶名和密碼// 第一步從token中取出用戶名String userCode = (String) token.getPrincipal();// 第二步:根據(jù)用戶輸入的userCode從數(shù)據(jù)庫查詢用戶信息SysUser sysUser = null;try {sysUser = sysService.findSysUserByUserCode(userCode);} catch (Exception e1) {e1.printStackTrace();}// 如果查詢不到返回nullif(sysUser==null){return null;}// 從數(shù)據(jù)庫查詢到密碼String password = sysUser.getPassword();//鹽String salt = sysUser.getSalt();// 如果查詢到,返回認(rèn)證信息AuthenticationInfo//activeUser就是用戶身份信息ActiveUser activeUser = new ActiveUser();activeUser.setUserid(sysUser.getId());activeUser.setUsercode(sysUser.getUsercode());activeUser.setUsername(sysUser.getUsername());//..//根據(jù)用戶id取出菜單List<SysPermission> menus = null;try {//通過service取出菜單 menus = sysService.findMenuListByUserId(sysUser.getId());} catch (Exception e) {e.printStackTrace();}//將用戶菜單,設(shè)置到activeUseractiveUser.setMenus(menus);//將activeUser設(shè)置simpleAuthenticationInfoSimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(activeUser, password,ByteSource.Util.bytes(salt), this.getName());return simpleAuthenticationInfo;}}5、登陸:
(1)原理:使用FormAuthenticationFilter的過濾器實(shí)現(xiàn)。
①在用戶沒有認(rèn)證時,請求loginUrl進(jìn)行認(rèn)證,用戶身份和用戶密碼提交到loginUrl;
②FormAuthenticationFilter攔截住,并取出request中的username和password(兩個參數(shù)名稱可以配置)
③FormAuthenticationFilter調(diào)用realm傳入一個token(即username和password);
④realm認(rèn)證是根據(jù)username查詢用戶信息,(并在ActiveUser中存儲,包括userid、usercode、username、menus等),如果查詢不到,realm返回null,FormAuthenticationFilter向requset域中填充一個參數(shù)(記錄了異常信息)。
(2)登陸頁面:
由于FormAuthenticationFilter的用戶身份和密碼的input的默認(rèn)值(username和password),修改頁面的賬號和密碼 的input的名稱為username和password。
(3)登陸代碼實(shí)現(xiàn):
@Controller public class LoginController {//loginUrl指定的認(rèn)證提交地址@RequestMapping("/login.action")public String login(HttpServletRequest request) throws Exception{//如果登陸失敗,則從request中獲取認(rèn)證異常信息,shiroLoginFailure就是shiro異常類的全限定名String exceptionClassName=request.getParameter("shiroLoginFailure");//根據(jù)shiro返回的異常路徑判斷,拋出指定異常信息if(exceptionClassName!=null){if(UnknownAccountException.class.getName().equals(exceptionClassName)){throw new CustomException("賬戶不存在");}else if(IncorrectCredentialsException.class.getName().equals(exceptionClassName)){throw new CustomException("用戶名/密碼錯誤");}else{throw new Exception();}}//此方法不處理登陸成功(認(rèn)證成功),如果shiro認(rèn)證成功會自動跳轉(zhuǎn)到上一個請求路徑。//登陸失敗回到login頁面:return "login";} }(4)認(rèn)證攔截器:/** = authu?
6、退出登陸:
在shiro中,不需要我們?nèi)?shí)現(xiàn)退出登陸接口,只要去訪問一個退出的url(該url是可以不存在),由LogoutFilter攔截住,清除session。
7、認(rèn)證信息在頁面顯示:
在controller層取出用戶信息,并設(shè)置在Attribute中,first.action會跳轉(zhuǎn)到首頁頁面。
@RequestMapping("/first.action")public String first(Model model)throws Exception{//從shiro的session中取activeUserSubject subject = SecurityUtils.getSubject();//取身份信息ActiveUser activeUser = (ActiveUser) subject.getPrincipal();//通過model傳到頁面model.addAttribute("activeUser", activeUser);return "/first";}、至此,Shiro+Spring+SpringMVC+Mybatis整合實(shí)現(xiàn)登陸認(rèn)證和退出登陸的功能就完成了。啟動工程進(jìn)行測試,在你沒有登陸認(rèn)證成功之前,訪問項目中的任何路徑,都會被強(qiáng)制跳轉(zhuǎn)到loginUrl指定的路徑進(jìn)行登陸提交。只有登陸認(rèn)證成功,才可以訪問項目中的內(nèi)容。
?
8、配置Shiro授權(quán)過濾器:使用PermissionsAuthorizationFilter
在applicationContext-shiro.xml中配置url所對應(yīng)的權(quán)限。
測試流程:
(1)在applicationContext-shiro.xml中配置filter規(guī)則
/permissionTest =?perms[item:query]? ?//即訪問“/permissionTest“” 路徑需要“item:query”權(quán)限
(2)用戶在認(rèn)證通過后,請求“/permissionTest”,被PermissionsAuthorizationFilter攔截,發(fā)現(xiàn)需要“item:query”權(quán)限;
(3)PermissionsAuthorizationFilter 調(diào)用 doGetAuthorizationInfo 獲取數(shù)據(jù)庫中的正確權(quán)限并返回;
(4)PermissionsAuthorizationFilter對“item:query”和從realm中獲取的權(quán)限進(jìn)行對比,如果“item:query”在realm返回的權(quán)限列表中,授權(quán)通過。
9、創(chuàng)建授權(quán)失敗頁面refuse.jsp,并配置自動跳轉(zhuǎn):(記錄一個在此處遇到的小問題)
如果授權(quán)失敗,跳轉(zhuǎn)到refuse.jsp,需要在spring容器中配置。
(1)授權(quán)失敗,頁面沒有自動跳轉(zhuǎn):
?在一開始,使用上面這種方法進(jìn)行配置,但是配置完之后,發(fā)現(xiàn)授權(quán)失敗之后,頁面并沒有自動跳轉(zhuǎn),而是直接拋出異常。
(2)原因:通過查看Shiro的源碼:
private void applyUnauthorizedUrlIfNecessary(Filter filter) {String unauthorizedUrl = getUnauthorizedUrl();if (StringUtils.hasText(unauthorizedUrl) && (filter instanceof AuthorizationFilter)) {AuthorizationFilter authzFilter = (AuthorizationFilter) filter;//only apply the unauthorizedUrl if they haven't explicitly configured one already:String existingUnauthorizedUrl = authzFilter.getUnauthorizedUrl();if (existingUnauthorizedUrl == null) {authzFilter.setUnauthorizedUrl(unauthorizedUrl);}}}?發(fā)現(xiàn)是因為shiro源代碼中判斷了filter是否為AuthorizationFilter,只有perms,roles,ssl,rest,port才是屬于AuthorizationFilter,而anon,authcBasic,auchc,user是AuthenticationFilter,所以unauthorizedUrl設(shè)置后不起作用。
(3)解決方法:異常全路徑做key,錯誤頁面做value。
<!-- 解決shiro配置的沒有權(quán)限訪問時,unauthorizedUrl不跳轉(zhuǎn)到指定路徑的問題 --><bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"><property name="exceptionMappings"><props><prop key="org.apache.shiro.authz.UnauthorizedException">/refuse</prop></props></property></bean>10、shiro常見的默認(rèn)過濾器:
| 過濾器簡稱 | 對應(yīng)的java類 |
| 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 |
| logout | org.apache.shiro.web.filter.authc.LogoutFilter |
anon:例子/admins/**=anon 沒有參數(shù),表示可以匿名使用。
authc:例如/admins/user/**=authc表示需要認(rèn)證(登錄)才能使用,FormAuthenticationFilter是表單認(rèn)證,沒有參數(shù)
perms:例子/admins/user/**=perms[user:add:*],參數(shù)可以寫多個,多個時必須加上引號,并且參數(shù)之間用逗號分割,例如/admins/user/**=perms["user:add:*,user:modify:*"],當(dāng)有多個參數(shù)時必須每個參數(shù)都通過才通過,想當(dāng)于isPermitedAll()方法。
user:例如/admins/user/**=user沒有參數(shù)表示必須存在用戶, 身份認(rèn)證通過或通過記住我認(rèn)證通過的可以訪問,當(dāng)?shù)侨氩僮鲿r不做檢查。
11、授權(quán):重寫自定義realm的doGetAuthorizationInfo方法,從數(shù)據(jù)庫查詢權(quán)限信息。
通常使用注解式授權(quán)方法和Jsp標(biāo)簽授權(quán)方法。
//自定義的Realm public class CustomRealm extends AuthorizingRealm{//注入service@Autowiredprivate SysService sysService;// 設(shè)置realm的名稱@Overridepublic void setName(String name) {super.setName("customRealm");}//授權(quán)的方法@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {//從principals獲取主身份信息//將getPrimaryPrincipal方法返回值轉(zhuǎn)為真實(shí)身份類型(在上邊doGetAuthenticationInfo認(rèn)證通過填充到SimpleAuthenticationInfo中的身份類型)ActiveUser activeUser=(ActiveUser)principals.getPrimaryPrincipal();//根據(jù)身份信息獲取權(quán)限信息:從數(shù)據(jù)庫獲取到權(quán)限數(shù)據(jù)List<SysPermission> permissionList = null;try{permissionList = sysService.findPermissionListByUserId(activeUser.getUserid());}catch (Exception e) {e.printStackTrace();}//單獨(dú)定一個集合對象List<String> permissions = new ArrayList<String>();if(permissionList!=null){for(SysPermission sysPermission:permissionList){//將數(shù)據(jù)庫中權(quán)限標(biāo)簽符放入集合permissions.add(sysPermission.getPercode());}}//查到權(quán)限數(shù)據(jù),返回授權(quán)信息(要包括上邊的permissions)SimpleAuthorizationInfo simpleAuthorizationInfo =new SimpleAuthorizationInfo();simpleAuthorizationInfo.addStringPermissions(permissions);//這里添加用戶有的權(quán)限列表simpleAuthorizationInfo.addRole("manager");//這里添加用戶所擁有的角色return simpleAuthorizationInfo;} }12、授權(quán):開啟controller類的aop支持:
對系統(tǒng)中類的方法給用戶授權(quán),建議在controller層進(jìn)行方法授權(quán)。
在springmvc.xml文件中配置:
<!-- 使用注解驅(qū)動:自動配置處理器映射器與處理器適配器 --><mvc:annotation-driven /><!-- 開啟aop,對類代理 --><aop:config proxy-target-class="true"></aop:config><!-- 開啟shiro注解支持 --><beanclass="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"><property name="securityManager" ref="securityManager" /></bean>13、授權(quán):在controller方法中添加注解:
//權(quán)限測試方法@RequestMapping("/permissionTest")@RequiresPermissions("item:update")//執(zhí)行此方法需要"item:update"權(quán)限public String permissionTest(Model model){return "/permissionTest";}至此,項目啟動后,當(dāng)訪問“/permissionTest”時,如果用戶沒有“item:update”權(quán)限,將會自動跳轉(zhuǎn)到refuse.jsp頁面;如果如果用戶擁有該權(quán)限,就會跳轉(zhuǎn)到permissionTest.jsp頁面。
14、授權(quán):Jsp標(biāo)簽授權(quán):(permissionTest.jsp)
(1)jsp頁面添加:
<%@ tagliburi="http://shiro.apache.org/tags" prefix="shiro" %>(2)jsp標(biāo)簽授權(quán):
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ tagliburi="http://shiro.apache.org/tags" prefix="shiro" %> <html><body><shiro:authenticated>這是登陸之后 才可以看到的內(nèi)容</shiro:authenticated><br/><shiro:notAuthenticated>這是未登陸的時候 才可以看到的內(nèi)容</shiro:notAuthenticated><br/><shiro:hasRole name="manager">這是擁有商品管理員角色才可以看到的內(nèi)容</shiro:hasRole><br/><shiro:lacksRole name="manager">這是沒有商品管理員角色才可以看到的內(nèi)容</shiro:lacksRole><br/><shiro:hasPermission name="item:update">這是擁有item:update資源權(quán)限才可以看到的內(nèi)容</shiro:hasPermission><br/><shiro:lacksPermission name="item:update">這是沒有item:update資源權(quán)限才可以看到的內(nèi)容</shiro:lacksPermission><br/><shiro:lacksPermission name="user:query">這是沒有user:query資源權(quán)限才可以看到的內(nèi)容</shiro:lacksPermission><br/></body> </html>至此,當(dāng)用戶成功進(jìn)入到授權(quán)成功頁面時,只能看到符合自己所屬權(quán)限和角色的內(nèi)容。
(3)常見的shiro授權(quán)標(biāo)簽:
| 標(biāo)簽名稱 | 標(biāo)簽條件(均是顯示標(biāo)簽內(nèi)容) |
| <shiro:authenticated> | 登錄之后 |
| <shiro:notAuthenticated> | 不在登錄狀態(tài)時 |
| <shiro:guest> | 用戶在沒有RememberMe時 |
| <shiro:user> | 用戶在RememberMe時 |
| <shiro:hasAnyRoles name="abc,123" > | 在有abc或者123角色時 |
| <shiro:hasRole name="abc"> | 擁有角色abc |
| <shiro:lacksRole name="abc"> | 沒有角色abc |
| <shiro:hasPermission name="abc"> | 擁有權(quán)限資源abc |
| <shiro:lacksPermission name="abc"> | 沒有abc權(quán)限資源 |
| <shiro:principal> | 顯示用戶身份名稱 |
| <shiro:principal property="username"/>? | 顯示用戶身份中的屬性值 |
15、授權(quán)測試:
(1)當(dāng)調(diào)用controller的一個方法,由于該 方法加了@RequiresPermissions("item:query")?,shiro調(diào)用realm獲取數(shù)據(jù)庫中的權(quán)限信息,看"item:query"是否在權(quán)限數(shù)據(jù)中存在,如果不存在就拒絕訪問,如果存在就授權(quán)通過。
(2)當(dāng)展示一個jsp頁面時,頁面中如果遇到<shiro:hasPermission name="item:update">,shiro調(diào)用realm獲取數(shù)據(jù)庫中的權(quán)限信息,看item:update是否在權(quán)限數(shù)據(jù)中存在,如果不存在就拒絕訪問,如果存在就授權(quán)通過。
16、完成:
至此,使用Shiro整合Spring+SpringMVC+Mybatis進(jìn)行登陸認(rèn)證和授權(quán)就完成了,但是在這里,授權(quán)的時候存在一個問題,只要遇到注解或jsp標(biāo)簽的授權(quán),都會調(diào)用realm方法查詢數(shù)據(jù)庫,因此需要使用緩存解決此問題。
?
最后,推薦幾篇有關(guān)Shiro的文章:
https://www.cnblogs.com/learnhow/p/5694876.html
https://www.sojson.com/shiro#so358852059
https://www.xttblog.com/?p=1272
?
?
總結(jié)
以上是生活随笔為你收集整理的SSM整合Shiro进行登陆认证和授权详细配置的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Shiro框架:授权流程、授权方式、Sh
- 下一篇: Shiro框架:缓存、session会话