認證:系統提供的用於識別用戶身份的功能,一般登陸功能就是認證功能-----讓系統知道你是誰??css
受權:系統授予用戶能夠訪問哪些功能的許可(證書)----讓系統知道你能作什麼??html
【URL攔截權限控制】——底層基於攔截器或者過濾器實現前端
【方法註解權限控制】——底層基於代理技術實現,爲Action建立代理對象,由代理對象進行權限校驗java
Apache Shiro(發音爲「shee-roh」,日語「堡壘(Castle)」的意思)是一個強大易用的Java安全框架(官方網站:shiro.apache.org),提供了認證、受權、加密和會話管理功能,可爲任何應用提供安全保障 - 從命令行應用、移動應用到大型網絡及企業應用。mysql
如下是你能夠用 Apache Shiro所作的事情:web
Shiro的4大部分——身份驗證,受權,會話管理和加密:spring
除了以上功能,shiro還提供不少擴展:sql
工做流程是這樣的:前臺將用戶名/密碼經過Subject與shiro的核心管理器(shiro securitymanager)進行交互來獲取權限和認證,核心管理器從Realm中獲取用戶的權限信息。數據庫
shiro的優點,不須要在代碼裏面判斷是否登陸,是否有執行的權限,實現了從前端頁面到後臺代碼的權限的控制很是的靈活方便。apache
傳統的登陸認證方式是,從前端頁面獲取到用戶輸入的帳號和密碼以後,直接去數據庫查詢帳號和密碼是否匹配和存在,若是匹配和存在就登陸成功,沒有就提示錯誤。
而shiro的認證方式則是,從前端頁面獲取到用戶輸入的帳號和密碼以後,傳入給一個UsernamePasswordToken對象也就是令牌,而後再把令牌傳給subject,subject會調用自定義的 realm,realm作的事情就是用前端用戶輸入的用戶名,去數據庫查詢出一條記錄(只用用戶名去查,查詢拿到返回用戶名和密碼),而後再把兩個密碼進行對比,不一致就拋出異常。也就是說若是subject.login(token);沒有拋出異常,就表示用戶名和密碼是匹配的,表示登陸成功
【實現步驟】:
第一步:在父工程的pom.xml中引入shiro框架相關的jar
<!-- 引入shiro框架的依賴 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-all</artifactId> <version>1.2.2</version> </dependency>
第二步:在web.xml中配置spring框架提供的用於整合shiro框架的過濾器
<!--配置過濾器,用於整合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>
第三步:在spring配置文件中配置bean,id爲shiroFilter
<!--配置shiro框架的過濾器工廠bean--> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <!--shiro的核心安全接口--> <property name="securityManager" ref="securityManager"/> <!--沒有登陸的用戶請求須要登陸的頁面時自動跳轉到登陸頁面,不是必須的屬性,不輸入地址的話會自動尋找項目web項目的根目錄下的」/login.jsp」頁面。--> <property name="loginUrl" value="/login.jsp"/> <!--登陸成功默認跳轉頁面,不配置則跳轉至"/"。--> <property name="successUrl" value="/index.jsp"/> <!--未受權時跳轉的頁面--> <property name="unauthorizedUrl" value="unauthorized.jsp"/> <!--指定URL級別攔截策略--> <property name="filterChainDefinitions"> <value> /css/** = anon /js/** = anon /images/** = anon /login.jsp* = anon /validatecode.jsp* = anon /userAction_login = anon <!-- 攔截page_base_staff.action這個方法必須有staff-list權限才能使用 --> /page_base_staff.action = perms["staff-list"] /* = authc </value> </property> </bean>
shiro框架提供的過濾器:
anon:例子/admins/**=anon 沒有參數,表示能夠匿名使用,無需校驗權限。
authc:例如/admins/user/**=authc表示須要認證(登陸)才能使用,沒有參數。
perms:例如/page_base_staff.action=perms["staff-list"]表示須要有"staff-list"這個權限才能查看;參數能夠寫多個,多個時必須加上引號,而且參數之間用逗號分割,例如/admins/user/**=perms["user:add:*,user:modify:*"],當有多個參數時必須每一個參數都經過才經過,想當於isPermitedAll()方法。
roles:例子/admins/user/**=roles[admin],當前用戶是否有這個角色權限。
第四步:配置安全管理器
<!--配置安全管理器--> <bean id = "securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"/>
第五步:編寫登陸方法
/** * 用戶登陸 */ public String login(){ //從Session中獲取生成的驗證碼 String validatecode = (String) ServletActionContext.getRequest().getSession().getAttribute("key"); //校驗驗證碼是否輸入正確 if(StringUtils.isNotBlank(checkcode) && checkcode.equals(validatecode)){ //輸入的驗證碼正確 User user = userService.login(model); if(user != null){ //登陸成功,將user對象放入session,跳轉到首頁 ServletActionContext.getRequest().getSession().setAttribute("loginUser", user); return HOME; }else{ //登陸失敗,,設置提示信息,跳轉到登陸頁面 //輸入的驗證碼錯誤,設置提示信息,跳轉到登陸頁面 this.addActionError("用戶名或者密碼輸入錯誤!"); return LOGIN; } }else{ //輸入的驗證碼錯誤,設置提示信息,跳轉到登陸頁面 this.addActionError("輸入的驗證碼錯誤!"); return LOGIN; } }
shiro的登陸認證方法
/** * 用戶登陸,使用shiro框架提供的方式進行認證操做 */ public String login() { // 從session中獲取生成的驗證碼 String validateCode = (String) ServletActionContext.getRequest().getSession().getAttribute("key"); // 驗證驗證碼是否正確 if (StringUtils.isNotBlank(checkcode) && checkcode.equals(validateCode)) { // 輸入的驗證碼正確 // 使用shiro框架提供的方式進行認證 Subject subject = SecurityUtils.getSubject(); //得到當前登陸用戶對象,如今狀態爲"未認證" // 用生成令牌(傳入用戶輸入的帳號和密碼) AuthenticationToken token = new UsernamePasswordToken(model.getUsername(), MD5Utils.md5(model.getPassword())); // 認證登陸 try { // 這裏會加載自定義的realm subject.login(token); //把令牌放到login裏面進行查詢,若是查詢帳號和密碼時候匹配,若是匹配就把user對象獲取出來,失敗就拋異常 //獲取登陸成功的用戶對象(之前是直接去service裏面查) User user = (User) subject.getPrincipal(); ServletActionContext.getRequest().getSession().setAttribute("loginUser", user); return "home"; } catch (Exception e) { //認證登陸失敗拋出異常 this.addActionError("用戶名和密碼錯誤"); return LOGIN; } } else { // 輸入的驗證碼錯誤,設置提示信息,跳轉到登陸頁面 this.addActionError("輸入的驗證碼錯誤"); return LOGIN; } }
第六步:自定義realm
public class BOSRealm extends AuthorizingRealm { @Autowired private IUserDao userDao; // 受權方法 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { // TODO Auto-generated method stub return null; } //認證方法 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { // 獲取令牌 UsernamePasswordToken mytoken = (UsernamePasswordToken) token; // 獲得帳號和密碼 String username = mytoken.getUsername(); // 根據用戶名查詢數據庫是否存在用戶,若是存在返回對象(帳號和密碼都有的對象) User user = userDao.findUserByUsername(username); if (user == null) { // 用戶名不存在 return null; } // 若是能查詢到,再由框架對比數據庫中查詢到的密碼與頁面提交的密碼是否一致 // 參數1:用戶認證的對象(subject.getPrincipal();返回的對象) // 參數2.從數據庫根據用戶名查詢到的用戶的密碼 // 參數3.把當前自定義的realm對象傳給SimpleAuthenticationInfo,在配置文件須要注入 SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), this.getName()); return info; } }
第七步:在安全管理器裏面注入自定義的realm
<!--配置安全管理器--> <bean id = "securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <!--注入realm到安全管理器進行密碼匹配--> <property name="realm" ref="bosRealm"/> </bean> <!--註冊自定義realm--> <bean id = "bosRealm" class="cn.itcast.bos.realm.BOSRealm"/>
<!-- 配置URL攔截規則 --> <property name="filterChainDefinitions"> <value> /css/** = anon /js/** = anon /images/** = anon /validatecode.jsp* = anon /login.jsp* = anon /User_login.action= anon <!-- 攔截page_base_staff.action這個方法必須有staff權限才能使用 --> /page_base_staff.action = perms["staff"] /** = authc
</value>
</property>
第一步:在spring配置文件中開啓shiro註解支持
<!--開啓shiro框架註解支持--> <bean id="defaultAdvisorAutoProxyCreator" class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"> <!--必須使用cglib方式爲Action對象建立代理對象--> <property name="proxyTargetClass" value="true"/> </bean> <!--配置shiro框架提供的切面類,用於建立代理對象--> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"></bean>
第二步:在Action的方法上使用shiro註解
/** * 取派員批量刪除 * @return */ @RequiresPermissions("staff-delete") //執行這個方法,須要當前用戶具備staff-delete權限 public String deleteBatch() { staffService.deleteBatch(ids); return "list"; }
第三步:在struts.xml中配置全局異常捕獲,當shiro框架拋出權限不足異常時,跳轉到權限不足提示頁面
<!--全局結果集定義--> <global-results> <result name="login">/login.jsp</result> <result name="unauthorized">/unauthorized.jsp</result> </global-results> <global-exception-mappings> <exception-mapping exception="org.apache.shiro.authz.UnauthorizedException" result="unauthorized"></exception-mapping> </global-exception-mappings>
第一步:在jsp頁面中引入shiro的標籤庫
<%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro"%>
第二步:使用shiro的標籤控制頁面元素展現
<!--有staff-delete權限才能顯示此an按鈕--> <shiro:hasPermission name="staff-delete"> { id : 'button-delete', text : '刪除', iconCls : 'icon-cancel', handler : doDelete }, </shiro:hasPermission>
在要設置權限的代碼中添加一下兩行代碼就能夠了
//修改 public String edit() { Subject subject = SecurityUtils.getSubject(); subject.checkPermission("staff.edit");//要運行此方法下面的代碼,必需要擁有staff.edit的權限 //更新model staffService.update(model); return "staff"; }
手動受權和認證
由於要受權的權限太多,因此須要一張權限表
public class BOSRealm extends AuthorizingRealm { @Autowired private IUserDao userDao; // 受權方法 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); // 爲用戶受權 info.addStringPermission("staff");//爲page_base_staff.action請求受權staff權限 info.addStringPermission("staff.delete");//爲page_base_staff.action請求受權staff權限 info.addStringPermission("staff.edit"); return info; } //認證方法 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { // 獲取令牌 UsernamePasswordToken mytoken = (UsernamePasswordToken) token; // 獲得帳號和密碼 String username = mytoken.getUsername(); // 根據用戶名查詢數據庫是否存在用戶,若是存在返回對象(帳號和密碼都有的對象) User user = userDao.findUserByUsername(username); if (user == null) { // 用戶名不存在 return null; } // 若是能查詢到,再由框架對比數據庫中查詢到的密碼與頁面提交的密碼是否一致 // 參數1:用戶認證的對象(subject.getPrincipal();返回的對象) // 參數2.從數據庫根據用戶名查詢到的用戶的密碼 // 參數3.把當前自定義的realm對象傳給SimpleAuthenticationInfo,在配置文件須要注入 SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), this.getName()); return info; } }
根據當前登陸用戶查詢數據庫,獲取實際對應的權限
// 受權方法 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); // 獲取當前登陸用戶對象 User user = (User) SecurityUtils.getSubject().getPrincipal(); // 根據當前登陸用戶查詢數據庫,獲取實際對應的權限 List<Function> list = null; if (user.getUsername().equals("admin")) { DetachedCriteria detachedCriteria = DetachedCriteria.forClass(Function.class); // 超級管理員內置用戶,查詢全部權限 list = functionDao.findByCriteria(detachedCriteria); } else { list = functionDao.findFunctionByUserId(user.getId()); } for (Function function : list) { info.addStringPermission(function.getCode()); } return info; }
ehcache是專門緩存插件,能夠緩存Java對象,提升系統性能。
第一步:在pom.xml文件中引入ehcache的依賴
<!-- 引入ehcache的依賴 --> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache-core</artifactId> <version>2.6.6</version> </dependency>
第二步:在項目中提供ehcache的配置文件(ehcache.xml)
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd"> <diskStore path="java.io.tmpdir"/> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" maxElementsOnDisk="10000000" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU" /> </ehcache>
第三步:在spring配置文件中配置緩存管理器對象,並注入給安全管理器對象
<!--配置安全管理器--> <bean id = "securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <!--注入realm到安全管理器進行密碼匹配--> <property name="realm" ref="bosRealm"/> <!--注入緩存管理器--> <property name="cacheManager" ref="cacheManager"/> </bean> <!--註冊緩存管理器--> <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> <!--注入ehcache的配置文件--> <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/> </bean>
上面咱們已經配置好了緩存,那麼咱們怎麼證實緩存是否起做用了呢?咱們能夠經過給Realm打斷點的方式來測試:
沒配置ehcache緩存前,每次點擊查詢頁面,都會執行這個方法。
而配置了緩存後,只有當咱們第一次訪問這個查詢頁面的時候,纔會執行一次這個方法,即只用查詢一次數據庫,之後就不用查(權限數據)了。
那麼何時會再次查數據庫呢?咱們能夠經過ehcache.xml配置它的有效時間,默認是(從不操做開始)2分鐘後再次查詢,咱們能夠修改相關配置來肯定它的有效時間,(好比設置6秒後再次查詢數據庫):
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd"> <diskStore path="java.io.tmpdir"/> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="6" timeToLiveSeconds="6" overflowToDisk="true" maxElementsOnDisk="10000000" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU" /> </ehcache>
若是,咱們從新登陸用戶,有效時間也會從新生效。每次咱們從新登陸後,原先緩存的數據就沒了,哪怕你設置的時間還沒到。
參考:https://blog.csdn.net/liaomin416100569/article/details/78838900
https://baijiahao.baidu.com/s?id=1591438032280398708&wfr=spider&for=pc
https://www.cnblogs.com/AngeLeyes/p/7196956.html