1、什麼是Shiro 官網:http://shiro.apache.org/html
Apache Shiro是一個強大易用的Java安全框架,提供了認證、受權、加密和會話管理等功能; 認證 - 用戶身份識別,常被稱爲用戶「登陸」; 受權 - 訪問控制; 密碼加密 - 保護或隱藏數據防止被偷窺; 會話管理 - 每用戶相關的時間敏感的狀態。 對於任何一個應用程序,Shiro均可以提供全面的安全管理服務。而且相對於其餘安全框架,Shiro要簡單的多。
2、Shiro的架構介紹 首先,來了解一下Shiro的三個核心組件:Subject, SecurityManager 和 Realms. 以下圖: java
Subject:即「當前操做用戶」。可是,在Shiro中,Subject這一律念並不只僅指人,也能夠是第三方進程、後臺賬戶(Daemon Account)或其餘相似事物。它僅僅意味着「當前跟軟件交互的東西」。但考慮到大多數目的和用途,你能夠把它認爲是Shiro的「用戶」概念。 Subject表明了當前用戶的安全操做,SecurityManager則管理全部用戶的安全操做。web
SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro經過SecurityManager來管理內部組件實例,並經過它來提供安全管理的各類服務。算法
Realm: Realm充當了Shiro與應用安全數據間的「橋樑」或者「鏈接器」。也就是說,當對用戶執行認證(登陸)和受權(訪問控制)驗證時,Shiro會從應用配置的Realm中查找用戶及其權限信息。 從這個意義上講,Realm實質上是一個安全相關的DAO:它封裝了數據源的鏈接細節,並在須要時將相關數據提供給Shiro。當配置Shiro時,你必須至少指定一個Realm,用於認證和(或)受權。配置多個Realm是能夠的,可是至少須要一個。spring
Shiro內置了能夠鏈接大量安全數據源(又名目錄)的Realm,如LDAP、關係數據庫(JDBC)、相似INI的文本配置資源以及屬性文件等。若是缺省的Realm不能知足需求,你還能夠插入表明自定義數據源的本身的Realm實現。數據庫
除前文所講Subject、SecurityManager 、Realm三個核心組件外,Shiro主要組件還包括:apache
Authenticator :認證就是覈實用戶身份的過程。這個過程的常見例子是你們都熟悉的「用戶/密碼」組合。多數用戶在登陸軟件系統時,一般提供本身的用戶名(當事人)和支持他們的密碼(證書)。若是存儲在系統中的密碼(或密碼錶示)與用戶提供的匹配,他們就被認爲經過認證。編程
Authorizer :受權實質上就是訪問控制 - 控制用戶可以訪問應用中的哪些內容,好比資源、Web頁面等等。緩存
SessionManager :在安全框架領域,Apache Shiro提供了一些獨特的東西:可在任何應用或架構層一致地使用Session API。即,Shiro爲任何應用提供了一個會話編程範式 - 從小型後臺獨立應用到大型集羣Web應用。這意味着,那些但願使用會話的應用開發者,沒必要被迫使用Servlet或EJB容器了。或者,若是正在使用這些容器,開發者如今也能夠選擇使用在任何層統一一致的會話API,取代Servlet或EJB機制。安全
CacheManager :對Shiro的其餘組件提供緩存支持。
3、代碼整合: 1)、pom中加入shiro所需jar ``` <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>${shiro.version}</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>${shiro.version}</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-aspectj</artifactId> <version>${shiro.version}</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-cas</artifactId> <version>${shiro.version}</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>${shiro.version}</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>${shiro.version}</version> </dependency>
```
2)、web.xml配置shiro攔截器及加載shiro配置文件:
<context-param> <param-name>contextConfigLocation</param-name> <param-value> classpath*:spring-context*.xml classpath:shiro-context.xml </param-value> </context-param> <!-- 確保Web項目中須要權限管理的URL均可以被Shiro攔截過濾 --> <filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
3)、編寫shiro配置文件:
<?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:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.2.xsd" default-lazy-init="true"> <!-- shiro --> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"></bean> <!-- 數據庫保存的密碼是使用MD5算法加密的,因此這裏須要配置一個密碼匹配對象 --> <!-- <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.Md5CredentialsMatcher"></bean> --> <!-- 緩存管理 --> <bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager"></bean> <!-- 使用Shiro自帶的JdbcRealm類 指定密碼匹配所須要用到的加密對象 指定存儲用戶、角色、權限許可的數據源及相關查詢語句 --> <!-- <bean id="jdbcRealm" class="org.apache.shiro.realm.jdbc.JdbcRealm"> <property name="credentialsMatcher" ref="credentialsMatcher"></property> <property name="permissionsLookupEnabled" value="true"></property> <property name="dataSource" ref="dataSource"></property> <property name="authenticationQuery" value="SELECT pswd FROM u_user WHERE mobileId = ?"></property> <property name="userRolesQuery" value="select u_role.name from u_role,u_user_role,u_user where u_role.id = u_user_role.rid and u_user_role.uid = u_user.id and u_user.mobileId = ?"></property> <property name="permissionsQuery" value="select u_permission.* from u_permission,u_role_permission,u_role where u_permission.id=u_role_permission.pid and u_role_permission.rid = u_role.id and u_role.`name` = ?"></property> </bean> --> <!-- Realm實現 --> <bean id="userRealm" class="com.company.project.common.shiro.UserRealm"></bean> <!-- Shiro安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="userRealm"></property> <property name="cacheManager" ref="cacheManager"></property> </bean> <!-- Shiro主過濾器自己功能十分強大,其強大之處就在於它支持任何基於URL路徑表達式的、自定義的過濾器的執行 Web應用中,Shiro可控制的Web請求必須通過Shiro主過濾器的攔截,Shiro對基於Spring的Web應用提供了完美的支持 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <!-- Shiro的核心安全接口,這個屬性是必須的 --> <property name="securityManager" ref="securityManager"></property> <!-- 要求登陸時的連接(登陸頁面地址),非必須的屬性,默認會自動尋找Web工程根目錄下的"/login.jsp"頁面 --> <property name="loginUrl" value="/company/login"></property> <!-- 登陸成功後要跳轉的鏈接(本例中此屬性用不到,由於登陸成功後的處理邏輯在LoginController裏硬編碼) --> <!-- <property name="successUrl" value="/" ></property> --> <!-- 用戶訪問未對其受權的資源時,所顯示的鏈接 --> <property name="unauthorizedUrl" value="/"></property> <property name="filterChainDefinitions"> <value> /company/login=anon /company/subLogin=anon /company/**=authc </value> </property> </bean> <!-- 開啓Shiro的註解(如@RequiresRoles,@RequiresPermissions),需藉助SpringAOP掃描使用Shiro註解的類, 並在必要時進行安全邏輯驗證 --> <!-- <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"></bean> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager"></property> </bean> --> </beans>
4)、編寫自定義Realm實現類,該類繼承自AuthorizingRealm,實現其接口
package com.company.project.common.shiro; import java.util.Date; import java.util.Set; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.DisabledAccountException; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.UnknownAccountException; 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 com.company.project.common.shiro.token.ShiroToken; import com.company.project.common.utils.Constants; import com.company.project.common.utils.DateUtils; import com.company.project.modules.user.entity.User; import com.company.project.modules.user.service.PermissionService; import com.company.project.modules.user.service.RoleService; import com.company.project.modules.user.service.UserService; /** * 自定義shiro的Realm * @author Sorin * */ public class UserRealm extends AuthorizingRealm{ @Autowired private UserService userService; @Autowired private PermissionService permissionService; @Autowired private RoleService roleService; /** * 該類的做用是爲shiro提供認證數據源 */ @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken authcToken) throws AuthenticationException { ShiroToken token = (ShiroToken) authcToken; User user = userService.subLogin(token.getUsername(), token.getPswd()); if(user == null){ throw new UnknownAccountException("賬號或密碼不正確!");// 沒找到賬號 }else if(Constants.MobileCode.ISTRUE== user.getStatus()){ throw new DisabledAccountException("賬號已經禁止登陸!"); }else{//更新最後登陸時間 user.setLastLoginTime(DateUtils.getFormattedString(new Date())); userService.update(user); } return new SimpleAuthenticationInfo(user.getMobileId(), // 用戶名 user.getPswd(), // 密碼 getName() // realm name ); } /** * 拼裝shiro的受權數據源 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String mobileId = (String)principals.getPrimaryPrincipal(); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); //角色 Set<String> findRoles = roleService.findRoles(mobileId); String roleName = null; for (String role : findRoles) { roleName = role; } authorizationInfo.setRoles(findRoles); //權限 authorizationInfo.setStringPermissions(permissionService.findPermission(roleName)); return authorizationInfo; } }
6)、自定義ShiroToken實體類,繼承UsernamePasswordToken
package com.company.project.common.shiro.token; import org.apache.shiro.authc.UsernamePasswordToken; public class ShiroToken extends UsernamePasswordToken implements java.io.Serializable{ private static final long serialVersionUID = -6451794657814516274L; public ShiroToken(String username, String pswd) { super(username,pswd); this.pswd = pswd ; } /** 登陸密碼[字符串類型] 由於父類是char[] ] **/ private String pswd ; public String getPswd() { return pswd; } public void setPswd(String pswd) { this.pswd = pswd; } }
7)、TokenManager類,調用shiro認證的service
public static String login(LoginVo user){ ShiroToken token = new ShiroToken(user.getMobileId(), user.getPswd()); token.setRememberMe(user.getIsRememberMe()); SecurityUtils.getSubject().login(token); return getToken(); } public static String getToken(){ return (String)SecurityUtils.getSubject().getPrincipal(); }
8)、LoginController類
//捕捉相應的異常進行處理便可 try{ String mobileId = TokenManager.login(loginVo); }catch (DisabledAccountException e) { e.printStackTrace(); return resultBase("賬號已經禁用。", Constants.ResponseResultFlag.ERROR); } catch (Exception e) { e.printStackTrace(); return resultBase("賬號或密碼錯誤。", Constants.ResponseResultFlag.ERROR); }
9)、shiro標籤使用:
<%--shiro 標籤 --%> <%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %> .... <%--擁有 角色(管理員) || (用戶中心)--%> <shiro:hasAnyRoles name='系統管理員,用戶中心'> <shiro:hasPermission name="/member/list.shtml"> <li><a href="/member/list.shtml">用戶列表</a></li> </shiro:hasPermission> <shiro:hasPermission name="/member/online.shtml"> <li><a href="/member/online.shtml">在線用戶</a></li> </shiro:hasPermission>