shiro是apache下的一個項目,和spring security類似,用於用戶權限的管理‘
但從易用性和學習成本上考慮,shiro更具優勢,同時shiro支持和很多接口集成
用戶及權限管理是眾多系統都需要解決的問題,采用各種不同的方式均可實現。在比較出名的用戶權限控制框架中 spring security 和apache shiro 位居前列。但由於spring security使用相對復雜,學習成本較高故不被眾多開發人員采用。而apache shiro則在使用上較為簡單靈活且容易學習,故在如今新開發的系統中被廣泛采用。 http://shiro.apache.org/
如apache官方站點所述,shiro是一個很容易使用的安全框架,提供了各種不同的功能,很容易便可以集成到web項目中,也可以在web項目之外的環境運行,甚至是cmd命令窗口。在web項目中,主要使用shiro的三個功能:
① Authentication 認證
認證是進入系統的第一步操作。通常是通過輸入已經在系統中注冊的用戶名和對應的密碼進行登錄操作,也可通過指紋等設備進行。 系統對登錄信息進行認證,若通過才能進入系統進行已經授權的操作。
② Authorization 授權
登錄系統后能否進行操作還與系統對當前用戶的具體授權有關。授權即給用戶授予具體的操作權限,用戶認證成功后才能進行相關操作。授權和認證是緊密關聯的。
③ Session Management 會話管理
用戶認證成功后,系統需時刻跟蹤用戶狀態。通過對會話狀態的跟蹤,時刻對用戶用戶狀態進行更新,當用戶進行某一操作時,從會話中判斷用戶是否具有該操作權限,達到只能對授權資源進行訪問。
在shiro的實際使用中,shiro提供了眾多接口供用戶使用,在表現成更提供了眾多標簽,下面一一說明常用項目:
① AuthorizingRealm
提供了認證和授權方法,在集成時需要集成該類,根據具體的用戶權限實體設計重寫認證doGetAuthenticationInfo 和 授權doGetAuthorizationInfo方法。
② ShiroFilterFactoryBean之filterChainDefinitionMap
重寫getObject()方法,根據具體的設計可以將對應的權限進行封裝, 如動態配置用戶權限,用戶可以訪問部分url。
③ shiro標簽
在表現層,shiro提供了標簽,以便用戶使用。
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<shiro:guest> 驗證當前用戶是否為“訪客”,即未認證(包含未記住)的用戶。
<shiro:user> 認證通過或已記住的用戶。
<shiro:authenticated>
已認證通過的用戶。不包含已記住的用戶,這是與user標簽的區別所在。
<shiro:notAuthenticated>
未認證通過用戶,與authenticated標簽相對應。與guest標簽的區別是,該標簽包含已記住用戶。
<shiro:principal/>
輸出當前用戶信息,通常為登錄帳號信息。
<shiro:hasRole name="administrator">
驗證當前用戶是否屬於該角色。
<shiro:lacksRole name="administrator">
與hasRole標簽邏輯相反,當用戶不屬於該角色時驗證通過。
<shiro:hasAnyRoles name="developer, project manager, administrator">
驗證當前用戶是否屬於以下任意一個角色。
<shiro:hasPermission name="user:create">
驗證當前用戶是否擁有指定權限。
<shiro: lacksPermission name=”xxx”>
驗證當前用戶是否擁有不擁有指定權限。
④ shiro注解
如同shiro標簽一樣,在具體的controller方法中,可以針對特定方法添加注解,以此來限定訪問權限。
首先看一下實際運行效果:




以上是實際運行效果,美工很差,請看重點 O(∩_∩)O
開發步驟:
① 增加Filter
<filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <async-supported>true</async-supported> <init-param> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
②配置shiro過濾器相關信息,如默認登錄頁,緩存等,配置請參考如下文件 applicationContext-shiro.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:util="http://www.springframework.org/schema/util" 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.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> <description>apache shiro配置</description> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager" /> <property name="loginUrl" value="/login.html" /> <property name="successUrl" value="/user/index.html" /> <property name="unauthorizedUrl" value="/401.html" /> <property name="filterChainDefinitions"> <value> <!-- 登錄頁允許訪問 --> /login** = anon /doLogin** = anon <!-- 靜態資源允許訪問 --> /static/** = anon /assets/** = anon /user = authc /role = authc /permission = authc /** = authc </value> </property> </bean> <!-- 緩存管理器 使用Ehcache實現 --> <!-- <bean id="shiroEhcacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> --> <!-- <property name="cacheManagerConfigFile" value="classpath:ehcache-shiro.xml" /> --> <!-- </bean> --> <!-- 會話DAO --> <!-- <bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.MemorySessionDAO" /> --> <!-- 會話管理器 --> <!-- <bean id="sessionManager" --> <!-- class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"> --> <!-- <property name="sessionDAO" ref="sessionDAO" /> --> <!-- </bean> --> <bean id="shiroAuthorizingRealm" class="xiaochangwei.zicp.net.web.utils.ShiroAuthorizingRealm"> <property name="authorizationCacheName" value="shiro-authorizationCache"/> <!-- <property name="cacheManager" ref="shiroEhcacheManager"/> --> </bean> <!-- 安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realms"> <list> <ref bean="shiroAuthorizingRealm" /> </list> </property> <!-- cacheManager,集合spring緩存工廠 --> <!-- <property name="cacheManager" ref="shiroEhcacheManager" /> --> <!-- <property name="sessionManager" ref="sessionManager" /> --> </bean> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager"/> </bean> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"> <property name="proxyTargetClass" value="true" /> </bean> <!-- Shiro生命周期處理器 --> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" /> </beans>
③重寫xml引用的shiroAuthorizingRealm,用於具體的認證和權限的判斷,根據業務的不同,這里邏輯也有變更,請酌情修改
package xiaochangwei.zicp.net.web.utils; import java.util.List; import javax.annotation.Resource; import org.apache.log4j.Logger; 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.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.session.Session; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.subject.Subject; import xiaochangwei.zicp.net.entity.Permission; import xiaochangwei.zicp.net.entity.Role; import xiaochangwei.zicp.net.entity.User; import xiaochangwei.zicp.net.service.systemMg.PermissionService; import xiaochangwei.zicp.net.service.systemMg.RoleService; import xiaochangwei.zicp.net.service.systemMg.UserService; /** * 用戶身份驗證,授權 Realm 組件 * **/ public class ShiroAuthorizingRealm extends AuthorizingRealm { private static final Logger LOGGER = Logger .getLogger(ShiroAuthorizingRealm.class); @Resource private UserService userService; @Resource private RoleService roleService; @Resource private PermissionService permissionService; /** * 權限檢查 */ @Override protected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principals) { SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); String username = String.valueOf(principals.getPrimaryPrincipal()); LOGGER.info("權限檢查----" + username + "-----------------"); User param = new User(); param.setUserName(username); final User user = userService.getSigle(param); if (user != null) { Role role = roleService.getSingle(user.getRoleId()); if (role != null) { authorizationInfo.addRole(role.getRoleDefine()); LOGGER.info("角色:" + role.getRoleDefine()); List<Permission> permissions = permissionService .getByRole(role); for (Permission p : permissions) { if (p.getPermissionDefine() != null) { LOGGER.info("權限:" + p.getPermissionDefine()); authorizationInfo.addStringPermission(p .getPermissionDefine()); } if (p.getUrl() != null) { authorizationInfo.addStringPermission(p.getUrl()); } } } } return authorizationInfo; } /** * 登錄驗證 */ @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token) throws AuthenticationException { String username = String.valueOf(token.getPrincipal()); String password = new String((char[]) token.getCredentials()); SimpleAuthenticationInfo authenticationInfo = null; // 通過數據庫進行驗證 try { User user = new User(username, password); final User authenticatedUser = userService.getSigle(user); if (authenticatedUser == null) { throw new AuthenticationException("用戶名或密碼錯誤."); } else { Subject currentUser = SecurityUtils.getSubject(); Session session = currentUser.getSession(); session.setAttribute("user", authenticatedUser); } authenticationInfo = new SimpleAuthenticationInfo(username, password, authenticatedUser.getRealName()); } catch (Exception e) { e.printStackTrace(); } return authenticationInfo; } }
④ 具體方法的權限定義
方案一: 直接在controller中的方法級別上添加注解標簽,代碼如下:
package xiaochangwei.zicp.net.web.controller; import java.util.List; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.servlet.ModelAndView; import xiaochangwei.zicp.net.entity.User; import xiaochangwei.zicp.net.service.systemMg.UserService; @Controller @RequestMapping("user") public class UserController { @Autowired private UserService userService; @RequestMapping(value = { "{all}", "list{all}" }) @RequiresPermissions("user:list") public ModelAndView listPage(ModelAndView mv) { List<User> users = userService.getAll(new User()); mv.setViewName("admin/user_list"); mv.addObject("users", users); return mv; } @RequestMapping(value = { "add{all}" }) public ModelAndView addPage(ModelAndView mv) { mv.setViewName("admin/user_update"); return mv; } @RequestMapping(value = { "{uuid}/update{all}" }) public ModelAndView updatePage(@PathVariable("uuid") String uuid,ModelAndView mv) { User param = new User(); param.setUuid(uuid); mv.addObject("user",userService.getSigle(param)); mv.setViewName("admin/user_update"); return mv; } }
方案二:采用url攔截:即在authorizationInfo.addStringPermission中,設置permission中定義的url,這樣就不用在每個controller的方法級別上添加注解標簽了,減少工作量
只需要再寫一個Filter,filter中獲取用戶的請求地址 URI,通過 SecurityUtils.getSubject().isPermitted(URI)判斷是否有權限,有則放行,無責攔截
方案三:重寫filterchainfactorybean,並注入到shiroFilter,其實原理和方案二一樣
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager" /> <property name="loginUrl" value="/admin/login" /> <property name="successUrl" value="/admin/manager" /> <property name="unauthorizedUrl" value="/admin/defined" /> <property name="filterChainDefinitionMap" ref="filterChainFactoryBean" /> </bean> <bean id="filterChainFactoryBean" class="xiaochangwei.zicp.net.web.filter.FilterChainFactoryBean"> <property name="sessionFactory" ref="sessionFactory"></property> </bean>
這里比較粗粒度的進行了方法級別的權限控制,實際情況下,可以做到特別細的攔截,比如頁面上字段、按鈕等,通過引入shiro提供的標簽<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
進行細粒度控制,同時為了更好的用戶體驗,應該前后端一致,不能前端有刪除按鈕,點了之后提示沒權限,這不科學
