在web.xml中配置shiro的filter
在web系統中,shiro也通過filter進行攔截,filter攔截器后將操作權交給Spring中配置的filterChain(過濾器鏈),shiro提供很多filter。要使用代理filter類DelegatingFilterProxy
<!-- shiro的filter --> <!-- shiro過濾器,DelegatingFilterProxy通過代理模式將Spring容器中的bean和filter關聯起來 --> <filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <!-- 設置targetFilterLifecycle為true 由servlet控制filter生命周期 --> <init-param> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param> <!-- 設置Spring容器filter的bean id,如果不設置則在Spring注冊的bean中查找與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>
applicationContext-shiro.xml
<!-- web.xml中shiro的filter對應的bean --> <!-- Shiro的web過濾器 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <!-- loginUrl認證提交地址,如果沒有認證將會請求此地址進行認證,請求此地址將由formAuthenticationFilter進行表單認證 --> <property name="loginUrl" value="/login.action"/> <!-- 認證成功統一跳轉到first.action,建議不配置,默認情況下,shiro認證成功后自動跳轉上一個請求路徑 --> <property name="successUrl" value="/first.action"/> <!-- 通過unauthorizedUrl 指定沒有權限操作時的跳轉頁面 --> <property name="unauthorizedUrl" value="/refuse.jsp"/> <!-- 過濾器鏈定義,從上向下執行,一般將/**放在最下邊 --> <property name="filterChainDefinitions"> <value> <!-- 退出攔截,請求logout.action執行退出操作 shiro自動清除Session--> /logout.action = logout <!-- 無權訪問頁面 anon表示可以匿名訪問 --> /refuse.jsp = anon <!-- 驗證碼可以匿名訪問 --> /validatecode.jsp = anon <!-- perms[xx] 表示有xx權限才可以訪問 --> /item/queryItem.action = perms[item:query] /item/editItem.action = perms[item:edit] <!-- 對靜態資源設置匿名訪問 --> /js/** = anon /images/** = anon /styles/** = anon <!-- /** = authc 表示所有的URL都必須認真通過才可以進行訪問--> /** = authc </value> </property> </bean> <!-- 安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="customRealm" /> </bean> <!-- 自定義Realm --> <bean id="customRealm" class="liuxun.ssm.shiro.CustomRealm"/>
第二中加入cas配置------applicationContext-shiro.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/data/elasticsearch http://www.springframework.org/schema/data/elasticsearch/spring-elasticsearch.xsd" default-lazy-init="false"> <description>Shiro公共配置</description> <bean id="shiroCasRealm" class="com.dousnl.framework.security.ShiroRealm"> <property name="cachingEnabled" value="true" /> <property name="authenticationCachingEnabled" value="true" /> <property name="authenticationCacheName" value="authenticationCache" /> <!-- cas integration start--> <!-- 配置cas服務器地址 --> <property name="casServerUrlPrefix" value="http://${sso.server.url}/cas" /> <!-- 客戶端的回調地址設置,必須和上面的shiro-cas過濾器casFilter攔截的地址一致 --> <property name="casService" value="http://${shxm.server.url}/index/success" /> <!-- cas integration end--> <!-- <property name="userService" ref="userService" /> <property name="userAuthService" ref="userAuthService" /> --> </bean> <bean id="casSubjectFactory" class="org.apache.shiro.cas.CasSubjectFactory" /> <bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie"> <constructor-arg value="rememberMe" /> <property name="httpOnly" value="true" /> <property name="maxAge" value="604800" /><!-- 保存7天 --><!-- 單位為秒 --><!-- 最小為30分鍾 --> </bean> <!-- rememberMe管理器 --> <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager"> <property name="cipherKey" value="#{T(org.apache.shiro.codec.Base64).decode('4AvVhmFLUs0KTA3Kprsdag==')}" /> <property name="cookie" ref="rememberMeCookie" /> </bean> <!-- 會話管理器 --> <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.ServletContainerSessionManager"> </bean> <!-- 用戶授權信息Cache, 采用EhCache --> <!-- 緩存管理器 使用Ehcache實現 --> <bean id="shiroEhcacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> <property name="cacheManagerConfigFile" value="classpath:ehcache/ehcache-shiro.xml"/> </bean> <!-- 安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="shiroCasRealm" /> <!-- 解決sessionid問題 本地化 --> <property name="sessionMode" value="native"/> <property name="cacheManager" ref="shiroEhcacheManager" /> <property name="sessionManager" ref="sessionManager" /> <property name="rememberMeManager" ref="rememberMeManager" /> <property name="subjectFactory" ref="casSubjectFactory" /> </bean> <!-- shiro-cas登錄過濾器 --> <bean id="casFilter" class="org.apache.shiro.cas.CasFilter"> <!-- 配置驗證錯誤時的失敗頁面 ,這里配置為登錄頁面 --> <property name="failureUrl" value="http://${sso.server.url}/cas/login?service=http://${shxm.server.url}/login" /> </bean> <!-- 退出登錄過濾器 --> <bean id="logoutFilter" class="org.apache.shiro.web.filter.authc.LogoutFilter"> <property name="redirectUrl" value="http://${sso.server.url}/cas/logout?service=http://${shxm.server.url}/login" /> </bean> <!-- Shiro生命周期處理器 --> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" /> <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> <property name="staticMethod" value="org.apache.shiro.SecurityUtils.setSecurityManager" /> <property name="arguments" ref="securityManager" /> </bean> <!-- 后台配置管理 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager" /> <!-- cas integration start--> <!-- 設定角色的登錄鏈接,這里為cas登錄頁面的鏈接可配置回調地址 --> <property name="loginUrl" value="http://${sso.server.url}/cas/login?service=http://${shxm.server.url}/index/success" /> <!-- cas integration end--> <property name="successUrl" value="/index/success" /> <!-- <property name="unauthorizedUrl" value="/admin/unauthorized" /> --> <property name="filters"> <util:map> <!-- <entry key="authc" value-ref="captchaAuthenticationFilter" /> <entry key="logout" value-ref="adminLogoutFilter" /> <entry key="session" value-ref="onlineSessionFilter" /> --> <entry key="casFilter" value-ref="casFilter" /> <entry key="logout" value-ref="logoutFilter" /> </util:map> </property> <property name="filterChainDefinitions"> <value> /index/success = casFilter /static/** = anon /logout = logout /** = user <!-- /admin/** =session,user /shiro-cas = user /fieldDef/** = user /mouldClass/** = user /mouldConfig/** = user /mouldDatil/** = user /mouldDef/** = user /check/** = user /expenseAppForm/** = user /reportForm/** = user /bzCheckController/** = user --> </value> </property> <!-- spel語言 --> <!-- <property name="filterChainDefinitionMap" value="#{filterChainDefinitionService.loadFilterChains()}" /> --> </bean> </beans>
自定義Realm模擬測試
此Realm先不從數據庫查詢權限數據,當前需要先將shiro整合完成。package liuxun.ssm.shiro; import java.util.ArrayList; import java.util.List; 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.authc.UsernamePasswordToken; 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 liuxun.ssm.po.ActiveUser; import liuxun.ssm.po.SysPermission; public class CustomRealm extends AuthorizingRealm { // 設置Realm名稱 @Override public void setName(String name) { super.setName("CustomRealm"); } // 支持UsernamePasswordToken @Override public boolean supports(AuthenticationToken token) { return token instanceof UsernamePasswordToken; } // 用於認證 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { // 從token中獲取用戶身份信息 String username = (String) token.getPrincipal(); // 拿着username從數據庫中進行查詢 // .... // 如果查詢不到返回null if (!username.equals("zhangsan")) { return null; } // 獲取從數據庫查詢出來的用戶密碼 String password = "123"; // 這里使用靜態數據進行測試 // 根據用戶id從數據庫中取出菜單 // ...先使用靜態數據 List<SysPermission> menus = new ArrayList<SysPermission>(); SysPermission sysPermission_1 = new SysPermission(); sysPermission_1.setName("商品管理"); sysPermission_1.setUrl("/item/queryItem.action"); SysPermission sysPermission_2 = new SysPermission(); sysPermission_2.setName("用戶管理"); sysPermission_2.setUrl("/user/query.action"); menus.add(sysPermission_1); menus.add(sysPermission_2); // 構建用戶身份信息 ActiveUser activeUser = new ActiveUser(); activeUser.setUserid(username); activeUser.setUsername(username); activeUser.setUsercode(username); activeUser.setMenus(menus); // 返回認證信息由父類AuthenticationRealm進行認證 SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(activeUser, password, this.getName()); return simpleAuthenticationInfo; } // 用於授權 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { //獲取身份信息 ActiveUser activeUser = (ActiveUser) principals.getPrimaryPrincipal(); //用戶id String userid = activeUser.getUserid(); // 根據用戶id從數據庫中查詢權限數據 // ...這里使用靜態數據模擬 List<String> permissions = new ArrayList<String>(); permissions.add("item:query"); permissions.add("item:update"); //將權限信息封裝為AuthorizationInfo SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); //基於資源權限的訪問控制 for (String permission : permissions) { simpleAuthorizationInfo.addStringPermission(permission); } // 如果基於角色進行訪問控制 // for (String role : roles) { // simpleAuthorizationInfo.addRole(role); // } return simpleAuthorizationInfo; } }
登錄
(1) 登錄原理
使用FormAuthenticationFilter過濾器實現,原理如下:
當用戶沒有認證時,請求loginUrl進行認證,用戶身份和用戶密碼提交數據到loginUrl,FormAuthorizationFilter攔截去除request中的username和password(兩個參數名稱是可以配置的),FormAuthorizationFilter調用Realm傳入一個token(username和password),realm認證時根據username查詢用戶信息(在ActiveUser中存儲,包括userid、usercode、username、menus) 如果查詢不到,Realm返回null,FormAuthorizationFilter向request域中填充了一個參數"shiroLoginFailure" 記錄了異常信息。
(2) 登錄頁面
由於FormAuthorizationFilter的用戶身份和密碼的input的默認值(username和password),修改頁面中賬號和密碼的input的name屬性為username和password。
(3)控制器登錄代碼的實現,如下:
---------------------
//用戶登錄提交方法 @RequestMapping("/login") public String login(HttpServletRequest request) throws Exception{ //shiro在認證通過后出現錯誤后將異常類路徑通過request返回 //如果登陸失敗從request中獲取認證異常信息,shiroLoginFailure就是shiro異常類的全限定名 String exceptionClassName = (String)request.getAttribute("shiroLoginFailure"); 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(); //最終在設置的異常處理器中生成未知錯誤 } } //此方法不處理登錄成功(認證成功)的情況 //如果登錄失敗還到login頁面 return "login"; }
首頁
1.認證成功后用戶菜單在首頁顯示(從activeUser獲取)//系統首頁 @RequestMapping("/first") public String first(Model model)throws Exception{ //主體 Subject subject = SecurityUtils.getSubject(); //身份 ActiveUser activeUser = (ActiveUser) subject.getPrincipal(); model.addAttribute("activeUser", activeUser); return "/first"; }
退出
由於使用shiro的sessionManager,不用開發退出功能,使用shiro的logout攔截器即可。也就是說不用我們去實現退出,只要去訪問一個退出的url(該 url是可以不存在),由LogoutFilter攔截住,清除session。所以可以屏蔽退出相關的控制器代碼。
<!-- 退出攔截,請求logout.action執行退出操作 -->
/logout.action = logout
無權限頁面refuse.jsp
當用戶無操作權限,shiro將跳轉到refuse.jsp頁面。
<!-- 通過unauthorizedUrl 指定沒有權限操作時的跳轉頁面 -->
<property name="unauthorizedUrl" value="/refuse.jsp"/>
授權過濾器測試
使用PermissionsAuthorizationFilter
在applicationContext-shiro.xml中配置url所對應的權限。
測試流程:
1、在applicationContext-shiro.xml中配置filter規則
<!--商品查詢需要商品查詢權限 -->
/item/queryItem.action = perms[item:query]
2、用戶在認證通過后,請求/items/queryItems.action
3、被PermissionsAuthorizationFilter攔截,發現需要“item:query”權限
4、PermissionsAuthorizationFilter調用realm中的doGetAuthorizationInfo獲取數據庫中正確的權限
5、PermissionsAuthorizationFilter對item:query 和從realm中獲取權限進行對比,如果“item:query”在realm返回的權限列表中,授權通過。如果授權失敗,跳轉到refuse.jsp
頁面如下:
---------------------
問題總結
1、在applicationContext-shiro.xml中配置過慮器鏈接,需要將全部的url和權限對應起來進行配置,比較發麻不方便使用。
2、每次授權都需要調用realm查詢數據庫,對於系統性能有很大影響,可以通過shiro緩存來解決。
shiro過濾器總結
過濾器簡稱 對應的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 沒有參數,表示可以匿名訪問。
authc:例如/admins/user/**=authc 表示需要認證(登錄)才能使用,FormAuthenticationFilter是表單認證,沒有參數。
roles:例如/admins/user/**=roles[admin],參數可以寫多個,多個時必須加引號,並且參數之間用逗號隔開,,當有多個參數時,例如/admin/user/**=roles["admin,geust"],每個參數通過才算通過,相當於hasAllRoles()方法。
perms:例如/admins/user/**=perms[user:add:*],參數可以寫多個,多個時必須加引號,並且參數之間用逗號分隔,例如/admins/user/**=perms["user:add:*,user:modiffy:*"],當有多個參數時必須每個參數都通過才算通過,相當於isPermittedAll()方法。
rest:例如/admins/user/**=rest[user],根據請求的方法,相當於/admins/user/**=perms[user:method],其中method為post,get,put,delete等 rest 即restful,就是用於對restful風格的設計進行權限管理。
port:例子/admins/user/**=port[8081],當請求的url的端口不是8081是跳轉到schemal://serverName:8081?queryString,其中schmal是協議http或https等,serverName是你訪問的host,8081是url配置里port的端口,queryString是你訪問的url里的?后面的參數。
authcBasic:例如/admins/user/**=authcBasic沒有參數表示httpBasic認證
ssl:例子/admins/user/**=ssl沒有參數,表示安全的url請求,協議為https
user:例如/admins/user/**=user沒有參數表示必須存在用戶, 身份認證通過或通過記住我認證通過的可以訪問,當登入操作時不做檢查
注意:anon、authcBasic、authc、user是認證過濾器,perms、roles、ssl、rest、port是授權過濾器
URL表達式說明:
(1)URL目錄是基於HttpServletRequest.getContextPath()此目錄設置。
(2)URL可以使用通配符,** 代表任意子目錄
(3)shiro驗證URL時,URL匹配成功便不再不再繼續匹配查找。所以要注意配置文件中的URL順序,尤其是在使用通配符時。
過濾器鏈定義說明:
(1)一個URL可以配置多個Filter,使用逗號隔開。
(2)當設置多個過濾器時,全部驗證通過,才視為通過。
(3)部分過濾器可以指定參數,如perms、roles
認證
需求
修改realm的doGetAuthenticationInfo方法,從數據庫中查詢用戶,realm返回的用戶信息中包括(MD5加密后的串和salt),實現讓shiro進行散列串的校驗。
添加憑證匹配器
添加憑證匹配器實現MD5加密校驗,修改applicationContext-shiro.xml 修改內容如下:
<!-- 自定義Realm --> <bean id="customRealm" class="liuxun.ssm.shiro.CustomRealm"> <!-- 將憑證匹配器設置到realm中,realm按照憑證匹配器的要求進行散列 --> <property name="credentialsMatcher" ref="credentialsMatcher"/> </bean> <!-- 憑證匹配器 --> <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <property name="hashAlgorithmName" value="md5"/> <property name="hashIterations" value="1"/> </bean>
修改realm的認證方法
修改realm代碼從數據庫中查詢用戶身份信息,將sysService注入realm。
public class CustomRealm extends AuthorizingRealm { @Autowired private SysService sysService; // 設置Realm名稱 @Override public void setName(String name) { super.setName("CustomRealm"); } // 支持UsernamePasswordToken @Override public boolean supports(AuthenticationToken token) { return token instanceof UsernamePasswordToken; } // 用於認證(從數據庫中查詢用戶信息) @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { // 從token中獲取用戶身份信息 String userCode = (String) token.getPrincipal(); SysUser sysUser = null; try { sysUser = sysService.findSysUserByUserCode(userCode); } catch (Exception e) { e.printStackTrace(); } //如果賬號不存在則返回null if (sysUser == null) { return null; } //根據用戶id取出菜單 List<SysPermission> menus = null; try { menus = sysService.findMenuListByUserId(sysUser.getId()); } catch (Exception e) { e.printStackTrace(); } //用戶密碼 String password = sysUser.getPassword(); //鹽 String salt = sysUser.getSalt(); //構建用戶身份信息 ActiveUser activeUser = new ActiveUser(); activeUser.setUserid(sysUser.getId()); activeUser.setUsername(sysUser.getUsername()); activeUser.setUsercode(sysUser.getUsercode()); activeUser.setMenus(menus); //將activeUser設置simpleAuthenticationInfo SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(activeUser, password, ByteSource.Util.bytes(salt), this.getName()); return simpleAuthenticationInfo; } ...... }
// 用於授權(從數據庫中查詢授權信息) @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { //獲取身份信息 ActiveUser activeUser = (ActiveUser) principals.getPrimaryPrincipal(); //用戶id String userid = activeUser.getUserid(); //獲取用戶權限 List<SysPermission> permissionsList = null; try { permissionsList = sysService.findPermissionListByUserId(userid); } catch (Exception e) { e.printStackTrace(); } //構建shiro授權信息 SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); //單獨定義一個集合 List<String> permissions = new ArrayList<String>(); for (SysPermission sysPermission : permissionsList) { //將數據庫中的權限標簽放入集合 permissions.add(sysPermission.getPercode()); } simpleAuthorizationInfo.addStringPermissions(permissions); return simpleAuthorizationInfo; }
對controller開啟AOP
在springmvc.xml中配置shiro注解支持,可在controller方法中使用shiro注解配置權限<!-- 開啟aop,對類代理 --> <aop:config proxy-target-class="true"></aop:config> <!-- 開啟shiro注解支持 --> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager" /> </bean>
權限注解控制
商品查詢的controller方法添加權限(item:query)
// 查詢商品列表信息 @RequestMapping("/queryItem") @RequiresPermissions("item:query") public ModelAndView queryItems(HttpServletRequest request) throws Exception {
// 方法返回字符串,字符串就是邏輯視圖名,Model作用就是將數據填充到request域,在頁面展示 @RequestMapping(value="/editItem",method={RequestMethod.GET}) @RequiresPermissions("item:update") public String editItems(Model model,Integer id) throws Exception{
商品修改提交
// 商品修改提交 // itemsQueryVo是包裝類型的pojo @RequestMapping("/editItemSubmit") @RequiresPermissions("item:update") //注意:每個校驗pojo的前邊必須加@Validated, 每個校驗的pojo后邊必須加BindingResult接收錯誤信息 public String editItemSubmit(Model model,Integer id, @Validated(value={ValidGroup1.class}) @ModelAttribute(value="item")ItemsCustom itemsCustom, BindingResult bindingResult, // 上傳圖片 MultipartFile pictureFile ) throws Exception {
JSP標簽控制
shiro標簽介紹
Jsp頁面添加:
<%@ tagliburi="http://shiro.apache.org/tags" prefix="shiro" %>
標簽名稱 標簽條件(均是顯示標簽內容)
<shiro:authenticated> 登錄之后
<shiro:notAuthenticated> 不在登錄狀態時
<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"> 擁有權限資源abc
<shiro:lacksPermission name="abc"> 沒有abc權限資源
<shiro:principal> 顯示用戶身份名稱
<shiro:principal property="username"/> 顯示用戶身份中的屬性值
jsp頁面添加標簽
如果有商品修改權限 頁面顯示"修改鏈接"
<td> <!-- 有item:update權限才顯示修改鏈接,沒有權限則不顯示相當於if(hasPermission(item:update)) --> <shiro:hasPermission name="item:update"> <a href="${pageContext.request.contextPath }/item/editItem.action?id=${item.id}">修改</a> </shiro:hasPermission> </td>
授權測試
(1) 當調用controller的一個方法,由於該 方法加了@RequiresPermissions("item:query") ,shiro調用realm獲取數據庫中的權限信息,看"item:query"是否在權限數據中存在,如果不存在就拒絕訪問,如果存在就授權通過。
(2) 當展示一個jsp頁面時,頁面中如果遇到<shiro:hasPermission name="item:update">,shiro調用realm獲取數據庫中的權限信息,看item:update是否在權限數據中存在,如果不存在就拒絕訪問,如果存在就授權通過。
問題:只要遇到注解或jsp標簽的授權,都會調用realm方法查詢數據庫,需要使用緩存解決此問題。
shiro緩存
添加Ehcache的jar包
配置cacheManager
<!-- 安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="customRealm" /> <!-- 注入緩存管理器 --> <property name="cacheManager" ref="cacheManager"/> </bean> <!-- 緩存管理器 --> <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> <property name="cacheManagerConfigFile" value="classpath:shiro-ehcache.xml"/> </bean>
配置shiro-ehcache.xml
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd"> <!--diskStore:緩存數據持久化的目錄 地址 --> <diskStore path="/Users/liuxun/Desktop/ehcache" /> <defaultCache maxElementsInMemory="1000" maxElementsOnDisk="10000000" eternal="false" overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="120" timeToLiveSeconds="120" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> </defaultCache> </ehcache>
清空緩存
如果用戶正常退出,緩存自動清空。如果用戶非正常退出,緩存自動清空。
如果修改了用戶的權限,而用戶不退出系統,舊的權限數據緩存在服務器,讀取仍先從緩存獲取權限數據,修改的權限無法立即生效。
需要手動進行編程實現:
在權限修改后調用realm的clearCache方法清除緩存。
下邊的代碼正常開發時要放在service中調用。
在service中,權限修改后調用realm的方法。
在自定義的Realm中定義clearCached方法
//清空緩存 public void clearCached(){ //清空所有用戶的身份緩存信息 PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals(); super.clearCache(principals); }
在權限修改后調用realm中的方法,realm已經由spring管理,所以從spring中獲取realm實例,調用clearCached方法。
測試清除緩存controller方法
@Controller public class ClearShiroCache { //注入realm @Autowired private CustomRealm customRealm; @RequestMapping("/clearShiroCache") public String clearShiroCache(){ //清除緩存,如果按照標准寫法是在Service中調用customRealm.clearCached(); customRealm.clearCached(); return "success"; } }
session管理
<!-- 安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="customRealm" /> <!-- 注入緩存管理器 --> <property name="cacheManager" ref="cacheManager"/> <!-- 注入Session管理器 --> <property name="sessionManager" ref="sessionManager"/> </bean> <!-- 會話管理器 --> <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"> <!-- Session的失效時長,單位:毫秒 --> <property name="globalSessionTimeout" value="600000"/> <!-- 刪除失效的Session --> <property name="deleteInvalidSessions" value="true"/> </bean>
驗證碼
思路
shiro使用FormAuthenticationFilter進行表單認證,驗證校驗的功能應該加在FormAuthenticationFilter中,在認證之前進行驗證碼校驗。
需要寫FormAuthenticationFilter的子類,繼承FormAuthenticationFilter,改寫它的認證方法,在認證之前進行驗證碼校驗。
自定義FormAuthenticationFilter
需要在驗證賬號和名稱之前校驗驗證碼
public class CustomFormAuthenticationFilter extends FormAuthenticationFilter { //原FormAuthenticationFilter的認證方法 @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { //在這里進行驗證碼的校驗 //從Session中獲取正確的驗證碼 HttpServletRequest httpServletRequest = (HttpServletRequest) request; HttpSession session = httpServletRequest.getSession(); //取出Session中的驗證碼(正確的驗證碼) String validateCode = (String) session.getAttribute("validateCode"); //取出頁面的驗證碼 //輸入的驗證和session中的驗證進行對比 String randomcode = httpServletRequest.getParameter("randomcode"); if(randomcode!=null && validateCode!=null && !randomcode.equals(validateCode)){ //如果校驗失敗,將驗證碼錯誤失敗信息,通過shiroLoginFailure設置到request中 httpServletRequest.setAttribute("shiroLoginFailure", "randomCodeError"); //拒絕訪問,不再校驗賬號和密碼 return true; } return super.onAccessDenied(request, response); } }
配置FormAuthenticationFilter
修改applicationContext-shiro.xml中對FormAuthenticationFilter的配置<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <!-- loginUrl認證提交地址,如果沒有認證將會請求此地址進行認證,請求此地址將由formAuthenticationFilter進行表單認證 --> <property name="loginUrl" value="/login.action"/> <!-- 認證成功統一跳轉到first.action,建議不配置,默認情況下,shiro認證成功后自動跳轉上一個請求路徑 --> <property name="successUrl" value="/first.action"/> <!-- 通過unauthorizedUrl 指定沒有權限操作時的跳轉頁面 --> <property name="unauthorizedUrl" value="/refuse.jsp"/> <!-- 自定義filter配置 --> <property name="filters"> <map> <!-- 將自定義的FormAuthenticationFilter注入shiroFilter --> <entry key="authc" value-ref="authenticationFilter"/> </map> </property> ......
(2)formAuthenticationFilter定義
<!-- 自定義form認證過濾器 -->
<!-- 基於Form表單的身份認證過濾器,即使不配置也會注冊此過濾器,表單中的用戶賬號、密碼及loginurl將采用默認值,建議配置 -->
<bean id="authenticationFilter" class="liuxun.ssm.shiro.CustomFormAuthenticationFilter">
<!-- 表單中賬號的input名稱 -->
<property name="usernameParam" value="username"/>
<!-- 表單中密碼的input名稱 -->
<property name="passwordParam" value="password"/>
</bean>
登錄頁面添加驗證碼
<TR> <TD>驗證碼:</TD> <TD><input id="randomcode" name="randomcode" size="8" /> <img id="randomcode_img" src="${baseurl}validatecode.jsp" alt="" width="56" height="20" align='absMiddle' /> <a href=javascript:randomcode_refresh()>刷新</a> </TD> </TR>
配置validatecode.jsp匿名訪問
在login.action對錯誤信息進行解析
記住我rememberme
用戶身份實現Serializable序列化接口

所以還需要將SySPermission類實現序列化接口
配置rememberMeManager
<!-- 安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="customRealm" /> <!-- 注入緩存管理器 --> <property name="cacheManager" ref="cacheManager"/> <!-- 注入Session管理器 --> <property name="sessionManager" ref="sessionManager"/> <!-- 注入rememberMe管理器 --> <property name="rememberMeManager" ref="rememberMeManager"/> </bean> <!-- rememberMeManager管理器,寫cookie,取出cookie生成用戶信息 --> <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager"> <property name="cookie" ref="rememberMeCookie"/> </bean> <!-- 記住我cookie --> <bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie"> <!-- rememberMe是cookie的名稱 --> <constructor-arg value="rememberMe"/> <!-- 記住我cookie的有效時間是30天 30*24*60*60 單位是秒 --> <property name="maxAge" value="2592000"></property> </bean>
FormAuthenticationFilter配置rememberMe
修改formAuthenticationFitler添加頁面中“記住我checkbox”的input名稱
<!-- 自定義form認證過濾器 -->
<!-- 基於Form表單的身份認證過濾器,即使不配置也會注冊此過濾器,表單中的用戶賬號、密碼及loginurl將采用默認值,建議配置 -->
<bean id="authenticationFilter" class="liuxun.ssm.shiro.CustomFormAuthenticationFilter">
<!-- 表單中賬號的input名稱 -->
<property name="usernameParam" value="username"/>
<!-- 表單中密碼的input名稱 -->
<property name="passwordParam" value="password"/>
<!-- 記住我input的名稱 -->
<property name="rememberMeParam" value="rememberMe"/>
</bean>
登錄頁面加rememberMe組件
在login.jsp中添加"記住我"checkbox
<tr> <TD></TD> <td><input type="checkbox" name="rememberMe" />自動登陸</td> </tr>
使用UserFilter
如果設置記住我,下次訪問某些URL時可以不用登陸,將記住我即可訪問的地址配置讓UserFilter攔截。(因為用戶身份信息已經存儲在名稱為rememberMe的cookie中,凡是需要身份認證才能訪問的url都可以添加user配置)
注意:
user和authc過濾器的區別
user可以說是針對rememberMe來使用的,當為非匿名訪問的某地址配置了user過濾器,那么通過rememberMe不通過登錄認證也可直接訪問,凡是沒有配置user過濾器的URL,即使配置了rememberMe功能也必須通過登錄認證才能訪問,當然可以匿名訪問的除外。/**=user 表示所有的地址都可通過rememberMe功能進行訪問。
authc 表示通過身份信息認證的地址(包括rememberMe或登錄實現認證)都可進行訪問,范圍更廣。

點擊修改后效果如下:

再重新關閉瀏覽器 重新打開 可以發現商品的所有鏈接都可以通過rememberMe進行訪問了
項目代碼

<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> <display-name>springmvc_mybatis_1</display-name> <!-- 配置Spring容器監聽器 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/applicationContext-*.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- 前端控制器 --> <servlet> <servlet-name>springmvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 加載springmvc配置 --> <init-param> <param-name>contextConfigLocation</param-name> <!-- 配置文件的地址 如果不配置contextConfigLocation, 默認查找的配置文件名稱classpath下的:servlet名稱+"-serlvet.xml" 即:springmvc-serlvet.xml --> <param-value>classpath:spring/springmvc.xml</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>springmvc</servlet-name> <!-- 可以配置/ ,此工程 所有請求全部由springmvc解析,此種方式可以實現 RESTful方式,需要特殊處理對靜態文件的解析不能由springmvc解析 可以配置*.do或*.action,所有請求的url擴展名為.do或.action由springmvc解析,此種方法常用 不可以/*,如果配置/*,返回jsp也由springmvc解析,這是不對的。 --> <url-pattern>*.action</url-pattern> </servlet-mapping> <!-- shiro的filter --> <!-- shiro過濾器,DelegatingFilterProxy通過代理模式將Spring容器中的bean和filter關聯起來 --> <filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <!-- 設置targetFilterLifecycle為true 由servlet控制filter生命周期 --> <init-param> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param> <!-- 設置Spring容器filter的bean id,如果不設置則在Spring注冊的bean中查找與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> <!-- post亂碼處理 --> <filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <welcome-file-list> <welcome-file>index.html</welcome-file> <welcome-file>index.htm</welcome-file> <welcome-file>index.jsp</welcome-file> <welcome-file>default.html</welcome-file> <welcome-file>default.htm</welcome-file> <welcome-file>default.jsp</welcome-file> </welcome-file-list> </web-app>
applicationContext-shiro.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd "> <!-- web.xml中shiro的filter對應的bean --> <!-- Shiro的web過濾器 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <!-- loginUrl認證提交地址,如果沒有認證將會請求此地址進行認證,請求此地址將由formAuthenticationFilter進行表單認證 --> <property name="loginUrl" value="/login.action"/> <!-- 認證成功統一跳轉到first.action,建議不配置,默認情況下,shiro認證成功后自動跳轉上一個請求路徑 --> <property name="successUrl" value="/first.action"/> <!-- 通過unauthorizedUrl 指定沒有權限操作時的跳轉頁面 --> <property name="unauthorizedUrl" value="/refuse.jsp"/> <!-- 自定義filter配置 --> <property name="filters"> <map> <!-- 將自定義的FormAuthenticationFilter注入shiroFilter --> <entry key="authc" value-ref="authenticationFilter"/> </map> </property> <!-- 過濾器鏈定義,從上向下執行,一般將/**放在最下邊 --> <property name="filterChainDefinitions"> <value> <!-- 退出攔截,請求logout.action執行退出操作 shiro自動清除Session--> /logout.action = logout <!-- 測試清空緩存 --> /clearShiroCache.action = anon <!-- 無權訪問頁面 anon表示可以匿名訪問 --> /refuse.jsp = anon <!-- 驗證碼可以匿名訪問 --> /validatecode.jsp = anon <!-- perms[xx] 表示有xx權限才可以訪問 一般此類使用注解替代配置 --> <!-- /item/queryItem.action = perms[item:query] --> <!-- /item/editItem.action = perms[item:edit] --> <!-- 配置記住我或認證通過可以訪問的地址 --> /index.jsp = user /first.action = user /welcome.jsp = user <!-- /item/queryItem.action = user --> /item/** = user <!-- 對靜態資源設置匿名訪問 --> /js/** = anon /images/** = anon /styles/** = anon <!-- /** = authc 表示所有的URL都必須認真通過才可以進行訪問--> /** = authc </value> </property> </bean> <!-- 安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="customRealm" /> <!-- 注入緩存管理器 --> <property name="cacheManager" ref="cacheManager"/> <!-- 注入Session管理器 --> <property name="sessionManager" ref="sessionManager"/> <!-- 注入rememberMe管理器 --> <property name="rememberMeManager" ref="rememberMeManager"/> </bean> <!-- 自定義Realm --> <bean id="customRealm" class="liuxun.ssm.shiro.CustomRealm"> <!-- 將憑證匹配器設置到realm中,realm按照憑證匹配器的要求進行散列 --> <property name="credentialsMatcher" ref="credentialsMatcher"/> </bean> <!-- 憑證匹配器 --> <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <property name="hashAlgorithmName" value="md5"/> <property name="hashIterations" value="1"/> </bean> <!-- 緩存管理器 --> <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> <property name="cacheManagerConfigFile" value="classpath:shiro-ehcache.xml"/> </bean> <!-- 會話管理器 --> <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"> <!-- Session的失效時長,單位:毫秒 --> <property name="globalSessionTimeout" value="600000"/> <!-- 刪除失效的Session --> <property name="deleteInvalidSessions" value="true"/> </bean> <!-- 自定義form認證過濾器 --> <!-- 基於Form表單的身份認證過濾器,即使不配置也會注冊此過濾器,表單中的用戶賬號、密碼及loginurl將采用默認值,建議配置 --> <bean id="authenticationFilter" class="liuxun.ssm.shiro.CustomFormAuthenticationFilter"> <!-- 表單中賬號的input名稱 --> <property name="usernameParam" value="username"/> <!-- 表單中密碼的input名稱 --> <property name="passwordParam" value="password"/> <!-- 記住我input的名稱 --> <property name="rememberMeParam" value="rememberMe"/> </bean> <!-- rememberMeManager管理器,寫cookie,取出cookie生成用戶信息 --> <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager"> <property name="cookie" ref="rememberMeCookie"/> </bean> <!-- 記住我cookie --> <bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie"> <!-- rememberMe是cookie的名稱 --> <constructor-arg value="rememberMe"/> <!-- 記住我cookie的有效時間是30天 30*24*60*60 單位是秒 --> <property name="maxAge" value="2592000"></property> </bean> </beans>
springmvc.xml中設計shiro注解的配置如下:
<!-- 開啟aop,對類代理 --> <aop:config proxy-target-class="true"></aop:config> <!-- 開啟shiro注解支持 --> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager" /> </bean>
shiro-ehcache.xml
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd"> <!--diskStore:緩存數據持久化的目錄 地址 --> <diskStore path="/Users/liuxun/Desktop/ehcache" /> <defaultCache maxElementsInMemory="1000" maxElementsOnDisk="10000000" eternal="false" overflowToDisk="true" diskPersistent="false" timeToIdleSeconds="120" timeToLiveSeconds="120" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU"> </defaultCache> </ehcache>
CustomRealm.java
package liuxun.ssm.shiro; import java.util.ArrayList; import java.util.List; import org.apache.shiro.SecurityUtils; 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.authc.UsernamePasswordToken; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.cache.CacheManager; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource; import org.springframework.beans.factory.annotation.Autowired; import liuxun.ssm.po.ActiveUser; import liuxun.ssm.po.SysPermission; import liuxun.ssm.po.SysUser; import liuxun.ssm.service.SysService; public class CustomRealm extends AuthorizingRealm { @Autowired private SysService sysService; // 設置Realm名稱 @Override public void setName(String name) { super.setName("CustomRealm"); } // 支持UsernamePasswordToken @Override public boolean supports(AuthenticationToken token) { return token instanceof UsernamePasswordToken; } // 用於認證(使用靜態數據模擬測試) /**@Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { // 從token中獲取用戶身份信息 String username = (String) token.getPrincipal(); // 拿着username從數據庫中進行查詢 // .... // 如果查詢不到返回null if (!username.equals("zhangsan")) { return null; } // 獲取從數據庫查詢出來的用戶密碼 String password = "123"; // 這里使用靜態數據進行測試 // 根據用戶id從數據庫中取出菜單 // ...先使用靜態數據 List<SysPermission> menus = new ArrayList<SysPermission>(); SysPermission sysPermission_1 = new SysPermission(); sysPermission_1.setName("商品管理"); sysPermission_1.setUrl("/item/queryItem.action"); SysPermission sysPermission_2 = new SysPermission(); sysPermission_2.setName("用戶管理"); sysPermission_2.setUrl("/user/query.action"); menus.add(sysPermission_1); menus.add(sysPermission_2); // 構建用戶身份信息 ActiveUser activeUser = new ActiveUser(); activeUser.setUserid(username); activeUser.setUsername(username); activeUser.setUsercode(username); activeUser.setMenus(menus); // 返回認證信息由父類AuthenticationRealm進行認證 SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(activeUser, password, this.getName()); return simpleAuthenticationInfo; } // 用於授權(使用靜態數據進行測試) @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { //獲取身份信息 ActiveUser activeUser = (ActiveUser) principals.getPrimaryPrincipal(); //用戶id String userid = activeUser.getUserid(); // 根據用戶id從數據庫中查詢權限數據 // ...這里使用靜態數據模擬 List<String> permissions = new ArrayList<String>(); permissions.add("item:query"); permissions.add("item:update"); //將權限信息封裝為AuthorizationInfo SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); //基於資源權限的訪問控制 for (String permission : permissions) { simpleAuthorizationInfo.addStringPermission(permission); } // 如果基於角色進行訪問控制 // for (String role : roles) { // simpleAuthorizationInfo.addRole(role); // } return simpleAuthorizationInfo; } **/ // 用於認證(從數據庫中查詢用戶信息) @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { // 從token中獲取用戶身份信息 String userCode = (String) token.getPrincipal(); SysUser sysUser = null; try { sysUser = sysService.findSysUserByUserCode(userCode); } catch (Exception e) { e.printStackTrace(); } //如果賬號不存在則返回null if (sysUser == null) { return null; } //根據用戶id取出菜單 List<SysPermission> menus = null; try { menus = sysService.findMenuListByUserId(sysUser.getId()); } catch (Exception e) { e.printStackTrace(); } //用戶密碼 String password = sysUser.getPassword(); //鹽 String salt = sysUser.getSalt(); //構建用戶身份信息 ActiveUser activeUser = new ActiveUser(); activeUser.setUserid(sysUser.getId()); activeUser.setUsername(sysUser.getUsername()); activeUser.setUsercode(sysUser.getUsercode()); activeUser.setMenus(menus); //將activeUser設置simpleAuthenticationInfo SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(activeUser, password, ByteSource.Util.bytes(salt), this.getName()); return simpleAuthenticationInfo; } // 用於授權(從數據庫中查詢授權信息) @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { //獲取身份信息 ActiveUser activeUser = (ActiveUser) principals.getPrimaryPrincipal(); //用戶id String userid = activeUser.getUserid(); //獲取用戶權限 List<SysPermission> permissionsList = null; try { permissionsList = sysService.findPermissionListByUserId(userid); } catch (Exception e) { e.printStackTrace(); } //構建shiro授權信息 SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); //單獨定義一個集合 List<String> permissions = new ArrayList<String>(); for (SysPermission sysPermission : permissionsList) { //將數據庫中的權限標簽放入集合 permissions.add(sysPermission.getPercode()); } simpleAuthorizationInfo.addStringPermissions(permissions); return simpleAuthorizationInfo; } //清除用戶的授權信息 //清空緩 public void clearCached(){ //清空所有用戶的身份緩存信息 PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals(); super.clearCache(principals); } }
CustomFormAuthenticationFilter.java
package liuxun.ssm.shiro; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import org.apache.shiro.web.filter.authc.FormAuthenticationFilter; /** * 認證之前實現驗證碼校驗 * @author liuxun * */ public class CustomFormAuthenticationFilter extends FormAuthenticationFilter { //原FormAuthenticationFilter的認證方法 @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { //在這里進行驗證碼的校驗 //從Session中獲取正確的驗證碼 HttpServletRequest httpServletRequest = (HttpServletRequest) request; HttpSession session = httpServletRequest.getSession(); //取出Session中的驗證碼(正確的驗證碼) String validateCode = (String) session.getAttribute("validateCode"); //取出頁面的驗證碼 //輸入的驗證和session中的驗證進行對比 String randomcode = httpServletRequest.getParameter("randomcode"); if(randomcode!=null && validateCode!=null && !randomcode.equals(validateCode)){ //如果校驗失敗,將驗證碼錯誤失敗信息,通過shiroLoginFailure設置到request中 httpServletRequest.setAttribute("shiroLoginFailure", "randomCodeError"); //拒絕訪問,不再校驗賬號和密碼 return true; } return super.onAccessDenied(request, response); } }
LoginController.java
package liuxun.ssm.controller; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.UnknownAccountException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import liuxun.ssm.exception.CustomException; import liuxun.ssm.po.ActiveUser; import liuxun.ssm.service.SysService; /** * 登錄和退出 * @author liuxun * */ @Controller public class LoginController { @Autowired private SysService sysService; //用戶登錄提交方法 /*@RequestMapping("/login") public String login(HttpSession session,String randomcode,String usercode,String password) throws Exception{ // 校驗驗證碼,防止惡性攻擊 // 從Session中獲取正確的驗證碼 String validateCode = (String) session.getAttribute("validateCode"); //輸入的驗證碼和Session中的驗證碼進行對比 if (!randomcode.equalsIgnoreCase(validateCode)) { //拋出異常 throw new CustomException("驗證碼輸入錯誤"); } //調用Service校驗用戶賬號和密碼的正確性 ActiveUser activeUser = sysService.authenticat(usercode, password); //如果Service校驗通過,將用戶身份記錄到Session session.setAttribute("activeUser", activeUser); //重定向到商品查詢頁面 return "redirect:/first.action"; } */ //用戶登錄提交方法 @RequestMapping("/login") public String login(HttpServletRequest request) throws Exception{ //shiro在認證通過后出現錯誤后將異常類路徑通過request返回 //如果登陸失敗從request中獲取認證異常信息,shiroLoginFailure就是shiro異常類的全限定名 String exceptionClassName = (String)request.getAttribute("shiroLoginFailure"); if (exceptionClassName!=null) { if (UnknownAccountException.class.getName().equals(exceptionClassName)) { throw new CustomException("賬號不存在"); } else if(IncorrectCredentialsException.class.getName().equals(exceptionClassName)){ throw new CustomException("用戶名/密碼錯誤"); }else if("randomCodeError".equals(exceptionClassName)){ throw new CustomException("驗證碼錯誤"); } else{ throw new Exception(); //最終在設置的異常處理器中生成未知錯誤 } } //此方法不處理登錄成功(認證成功)的情況 //如果登錄失敗還到login頁面 return "login"; } //用戶退出 /* @RequestMapping("/logout") public String logout(HttpSession session) throws Exception{ //session失效 session.invalidate(); //重定向到商品查詢頁面 return "redirect:/first.action"; } */ }
FirstAction.java
package liuxun.ssm.controller; import java.util.List; import javax.servlet.http.HttpSession; import org.apache.shiro.SecurityUtils; import org.apache.shiro.subject.Subject; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import liuxun.ssm.po.ActiveUser; @Controller public class FirstAction { //系統首頁 @RequestMapping("/first") public String first(Model model)throws Exception{ //主體 Subject subject = SecurityUtils.getSubject(); //身份 ActiveUser activeUser = (ActiveUser) subject.getPrincipal(); model.addAttribute("activeUser", activeUser); return "/first"; } //歡迎頁面 @RequestMapping("/welcome") public String welcome(Model model)throws Exception{ return "/welcome"; } }
其中基於原始的URL攔截(不使用shiro)的代碼是屏蔽的代碼 方便對比總結 所以沒有刪除