Shiro框架-----用來實現認證和授權的操作
Shiro框架
1.概念:shiro : 是一個許可權管理控制框架。主要提供了認證與授權的操作。
認證:使用者得登入成功後才能訪問頁面。 (這時是可以訪問所有頁面)
授權:授權訪問資源,是指使用者認證成功的情況下,再判斷模組(許可權)來訪問 可訪問的頁面。 (這時是隻能訪問又許可權的頁面)
2.shiro 和 spring security的比較
①shiro比spring security簡單
②shiro需要和spring整合,而spring security不需要
③spring security功能更加的強大
3.shiro的核心實現是---過濾器(攔截或過慮請求),用於使用者訪問頁面(URL)時進行許可權控制
4.shiro的主要功能模組有:
Authentication:身份認證/登入,------核心功能
Authorization:授權,即許可權驗證,驗證某個已認證的使用者是否擁有某個許可權 --------核心功能
Session Manager:會話管理,即使用者登入後就是一次會話,在沒有退出之前,它的所有資訊都在會話中
Cryptography:加密,保護資料的安全性,
Web Support:Web支援
Caching:快取
Concurrency:shiro支援多執行緒應用的併發驗證
記住一點,Shiro不會去維護使用者和維護許可權;這些需要我們自己去設計/提供;然後通過相應的介面注入給Shiro即可
5.shiro的三大元件
Subject:主體,代表了當前“使用者”,這個使用者不一定是一個具體的人,與當前應用互動的任何東西都是Subject,
儲存使用者資訊,負責認證和授權方法呼叫,只要調了方法就會到SecurityManager
SecurityManager:安全管理器;即所有與安全有關的操作都會與SecurityManager互動;它管理著所有Subject;可以看出它是Shiro的核心,負責認證和授權的管理
Realm:域,連線資料庫獲取認證和授權資訊的橋樑,Shiro從Realm獲取安全資料(如使用者、角色、許可權);
也就是說對於我們而言,最簡單的一個Shiro應用:
1、應用程式碼通過Subject來進行認證和授權,而Subject又委託給SecurityManager;
2、我們需要給Shiro的SecurityManager注入Realm,從而讓SecurityManager能得到合法的使用者及其許可權進行判斷。
從以上也可以看出,Shiro不提供維護使用者/許可權,而是通過Realm讓開發人員自己注入。
6.shiro的環境搭建
①專案新增shiro依賴 (父專案已經新增) (shiro通常是一個專案的一部分)
<!--shiro-->
<!--shiro和spring整合-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
<!--shiro核心包-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
②配置web.xml
<!--Spring整合Shiro的過濾器
作用: 接收所有需要交給Shiro過濾的請求
過濾器的名稱: filter-name就是Spring的bean的id
-->
<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>
③配置applicationContext-shiro.xml, Spring整合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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--1.Shiro處理認證授權邏輯的物件-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!--安全管理器-->
<property name="securityManager" ref="securityManager"/>
</bean>
<!--2.配置安全管理器-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!--配置Realm-->
<property name="realm" ref="myRealm"/>
</bean>
<!--3.配置Realm-->
<bean id="myRealm" class="cn.itcast.web.shiro.AuthRealm"/>
</beans>
④建立自定義realm類:AuthRealm,繼承AuthorizingRealm
public class AuthRealm extends AuthorizingRealm{
//授權
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
//認證
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
return null;
}
}
7.shiro的使用
前引:
一:與認證(登入)相關的過濾器的使用
過濾器:(先攔截,在判斷)
anon:匿名過濾器,代表不認證(登入)也可以訪問該請求,通過對靜態資源進行放行(指css,js,圖片都沒有了)
authc:認證過濾器,代表必須認證成功才可以訪問該資源,如果認證不成功或沒有認證,會自動跳轉到login.jsp頁面(自帶的),但是可以修改預設登入頁面,用<property name="loginUrl" value="/login.jsp"/>
用法:
/make/**=anon
/** = authc
anon一定要放在authc前,/** = authc表示攔截所有的請求,而在其上面的/make/**=anon表示這個放行不攔截
該攔截的資源用authc,該放行的資源用anon
又由於靜態資源也會被攔截,所以anon主要用來給靜態資源放行(css,js,圖片等)
/login.do因為是登入請求,必須使用anon過濾器放行!!!否則無法登入啦!
二與授權相關的過濾器
第一部分:
第一步:applicationContext-shiro.xml新增認證過濾器
<!--1.Shiro處理認證授權邏輯的物件--> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <!--安全管理器--> <property name="securityManager" ref="securityManager"/> <!--設定Shiro的過濾器--> <!-- 過濾的路徑問題: /index.jsp : 準確攔截/index.jsp頁面 /user/* : 模糊匹配,攔截user根目錄下的所有資源(代表一級目錄) /user/** : 模糊匹配,攔截user目錄下的所有資源(代表任意級目錄) 1. 認證相關的過濾器 anon: 匿名過濾器,不用認證也可以訪問該資源(必須放在authc的前面) authc:認證過濾器,必須認證成功才可以訪問該資源,如果認證不成功,會自動跳轉到login.jsp頁面(可以通過loginUrl屬性更新登入頁面) --> <property name="filterChainDefinitions"> <value> /css/**=anon /img/**=anon /make/**=anon /plugins/**=anon /login.do=anon /**=authc </value> </property> <!--修改shrio預設登入頁面--> <property name="loginUrl" value="/login.jsp"/> </bean>
第二步:修改LoginController,通過shiro實現登陸認證
/** * 登入方法 * 1)URL: http://localhost:8080/login.do * 2)引數:email=eric&password=123 * 3)返回:/WEB-INF/pages/home/main.jsp */ @RequestMapping("/login") public String login(String email,String password){ //1.判斷是否為空 if(StringUtils.isEmpty(email) || StringUtils.isEmpty(password)){ request.setAttribute("error","郵箱和密碼不能為空"); //手動轉發到login.jsp頁面 return "forward:/login.jsp"; } //-----------------------------------------------------一以下是正常做法-------------------------------------------------------- /*//2.判斷email是否存在 User loginUser = userService.findByEmail(email); //3 email不存在,提示"使用者名稱不存在" if(loginUser==null){ request.setAttribute("error","使用者名稱不存在"); //手動轉發到login.jsp頁面 return "forward:/login.jsp"; } //4 email存在,繼續判斷password是否正確 if(loginUser.getPassword().equals(password)){ //5. password正確,登入成功,儲存使用者資料到session域中,跳轉到主頁 session.setAttribute("loginUser",loginUser); //查詢當前使用者擁有的選單(模組) List<Module> menuList = moduleService.findModuleByUser(loginUser); //去除重複元素 removeDuplicate(menuList); //選單必須存入session域 session.setAttribute("menus",menuList); // 路徑: /WEB-INF/pages/home/main.jsp return "home/main"; }else{ //6.password不正確,提示"密碼錯誤" request.setAttribute("error","密碼錯誤"); //手動轉發到login.jsp頁面 return "forward:/login.jsp"; }*/ //---------------------------------------------------以上是正常做法--------------------------------------------------- //使用Shiro的登入認證邏輯 //1.獲取Subject物件 Subject subject = SecurityUtils.getSubject(); //2.UsernamePasswordToken 封裝需要認證的資訊(使用者名稱和密碼) UsernamePasswordToken token = new UsernamePasswordToken(email,password); //3.呼叫Subject的login方法進行認證 //4.判斷login方法有無異常,有異常代表認證失敗,無異常代表認證成功 try { subject.login(token); //無異常代表認證成功 //5.從Shiro的Subject中獲取登入使用者物件: subject.getPrincipal() User loginUser = (User)subject.getPrincipal(); //把登入使用者物件存入session域 (注意:這裡存入的loginUser物件,不是給Shiro使用的,是給我們自己業務使用的) session.setAttribute("loginUser",loginUser); //查詢當前使用者擁有的選單(模組) List<Module> menuList = moduleService.findModuleByUser(loginUser); //去除重複元素 //若是已經在sql語句那裡加了distinct去重,就不需要這個了 removeDuplicate(menuList); //選單必須存入session域 session.setAttribute("menus",menuList); //跳轉到主頁 return "home/main"; } catch (UnknownAccountException e) { //UnknownAccountException: 該異常代表使用者名稱不存在 request.setAttribute("error","Shrio:使用者名稱不存在"); return "forward:/login.jsp"; } catch (IncorrectCredentialsException e) { //IncorrectCredentialsException: 該異常代表密碼錯誤 request.setAttribute("error","Shrio:密碼輸入有誤"); return "forward:/login.jsp"; } catch (Exception e) { //其他異常 request.setAttribute("error","Shrio:伺服器錯誤"); return "forward:/login.jsp"; } }
第三步:編寫AuthRealm,實現登陸認證
/** * 自定義Realm */ public class AuthRealm extends AuthorizingRealm{ @Autowired private UserService userService; //授權 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("執行授權方法..."); return null; } //認證 (subject.login(token)方法固定執行doGetAuthenticationInfo方法) @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //System.out.println("執行認證方法..."); //1.判斷使用者名稱是否存在 //token傳給了AuthenticationToken UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken; String email = token.getUsername(); User loginUser = userService.findByEmail(email); if(loginUser==null){ //使用者名稱不存在 //我們只需要return null即可,Shiro底層判斷為null則丟擲UnKnowAccountException異常 return null; } //2.返回資料庫儲存密碼給Shiro,讓Shiro判斷密碼是否正確 /** * Shiro底層獲取SimpleAuthenticationInfo的引數,例如獲取password判斷和使用者輸入的密碼是否一致 * 1)如果密碼不一致,丟擲IncorrectCredentialsException異常 * 2)如果密碼一致, 把principal存入session域 */ //SimpleAuthenticationInfo: 封裝認證資料物件 /** * 引數一: principal 登入使用者物件,用於Subject.getPrincipal()方法獲取 * 引數二: password 資料庫的密碼 * 引數三: realm的別名, 在多個Realm的情況下使用,用於區分使用者表的。只有一張使用者表不需要別名 */ return new SimpleAuthenticationInfo(loginUser,loginUser.getPassword(),""); } }
總結:
Shiro登入認證的流程是怎樣的?
AuthRealm的認證方法,編寫判斷使用者名稱和返回資料庫密碼的邏輯,從資料庫獲得資料
8.加密
<!--建立Shiro自帶的憑證匹配器--> <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <!--指定需要加密的演算法名稱--> <property name="hashAlgorithmName" value="md5"/> </bean>
第二步:對資料庫密碼進行md5加密(select md5(123);),再直接修改資料庫的使用者密碼
請簡單說出Shiro加密判斷的執行流程
Subject.login(token) (未加密資料) -> AuthRealm的認證方法->返回資料庫密碼->先把token密碼使用演算法加密,再和資料庫的密碼匹配
步驟
1)編寫程式碼對密碼加鹽加密,檢視結果,把資料庫裡面的使用者密碼替換掉
public class Demo { public static void main(String[] args) { //1.原密碼 String password = "123"; //2.鹽 String salt = "[email protected]"; //3.加鹽加密 /** * 引數一:原密碼 * 引數二:鹽 * 引數三(可選):加密次數,預設1次 */ Md5Hash md5Hash = new Md5Hash(password,salt); System.out.println(md5Hash.toString()); } }
2)編寫自定義憑證匹配器
public class CustomCredentialsMatcher extends SimpleCredentialsMatcher{ /** * 完成密碼判斷的邏輯 * @param token 包含了使用者輸入的密碼 * @param info 包含了資料庫的密碼 * @return 密碼判斷結果 * true: 代表資料庫的密碼和使用者輸入的密碼一致的 * false:代表資料庫的密碼和使用者輸入的密碼不一致的 */ //打個do就能出來這個方法 @Override public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) { //1.獲取使用者輸入的密碼 UsernamePasswordToken userToken = (UsernamePasswordToken)token; //UsernamePasswordToken 是用來封裝使用者名稱和密碼,使用者名稱用字串封裝,密碼用字元陣列封裝 //userToken.getPassword()['1','2','3'] String userPassword = new String(userToken.getPassword()); // ['1','2','3'] //獲取使用者郵箱 String email = userToken.getUsername(); //2.對使用者輸入的密碼進行加鹽加密 Md5Hash md5Hash = new Md5Hash(userPassword,email); String encodePwd = md5Hash.toString(); //3.獲取資料庫的密碼 String dbPwd = (String)info.getCredentials(); //4.判斷資料庫的密 碼和第二步產生的密碼是否一致,一致返回true,不一致返回false return dbPwd.equals(encodePwd); } }
3)在applicationContext-shiro.xml,新增自定義憑證匹配器
<!--3.配置Realm--> <bean id="myRealm" class="cn.itcast.web.shiro.AuthRealm"> <!--配置憑證匹配器--> <property name="credentialsMatcher" ref="credentialsMatcher"/> </bean> <!--建立自定義憑證匹配器--> <bean id="credentialsMatcher" class="cn.itcast.web.shiro.CustomCredentialsMatcher"/>
4)測試
拓展注意:
1.新增使用者的時候要讓使用者的密碼加密,故再service實現類裡面寫新增使用者的方法是的業務邏輯是這樣的
@Override public void save(User user) { //生成主鍵 user.setId(UUID.randomUUID().toString()); //對密碼加鹽加密 Md5Hash md5Hash = new Md5Hash(user.getPassword(),user.getEmail()); user.setPassword(md5Hash.toString()); userDao.save(user); }
2.Shiro的登入登出程式碼實現
@RequestMapping("/logout") public String logout(){ //刪除session的登入數 session.removeAttribute("loginUser"); session.removeAttribute("menus"); //Shiro登入登出(底層:刪除之前Shiro存入的session的key) //防止瀏覽器記住登入認證 Subject subject = SecurityUtils.getSubject(); subject.logout(); return "redirect:/login.jsp"; }
第二部分:
-
登陸認證成功後,獲取使用者的許可權 (獲取許可權),就相當於買票
-
訪問資源時候,進行授權校驗:用訪問資源需要的許可權去使用者許可權(模組)列表查詢,如果存在,則有許可權訪問資源。(許可權攔截)。就相當於售票
.獲取許可權
實現自定義realm的doGetAuthorizationInfo()方法,返回使用者已經具有的許可權。
//授權 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { //System.out.println("執行授權方法..."); //1、獲取當前登入使用者擁有的許可權 //從Subject獲取當前登入使用者 Subject subject = SecurityUtils.getSubject(); User loginUser = (User)subject.getPrincipal(); List<Module> moduleList = moduleService.findModuleByUser(loginUser); //2、把當前登入使用者的許可權告訴Shiro框架 //2.1 建立授權物件 SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //2.2 把模組存入授權物件 if(moduleList!=null && moduleList.size()>0){ for(Module module:moduleList){ if(!StringUtils.isEmpty(module.getName())) { //往授權物件存入授權資訊(授權資訊存入什麼字串? 字串必須唯一的) info.addStringPermission(module.getName()); } } } return info; }
.授權校驗
shiro其實提供了四種方式實現了許可權校驗:硬編碼,過濾器,註解方法,jsp標籤。主要掌握註解方法,jsp標籤
1)註解方法
<!--====開啟Shiro的註解====--> <!-- Shiro註解藉助了Spring的AOP來實現授權校驗 --> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager"/> </bean>
2.開啟Aop自動代理(已經完成)
<!--6. 開啟Aop自動代理--> <aop:aspectj-autoproxy/>
3.在controller中使用@RequiresPermissions(“”)註解
2)再頁面上使用jsp標籤
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>測試Shiro的jsp標籤</title> </head> <body> <shiro:hasPermission name="企業管理"> <a href="">企業管理</a> </shiro:hasPermission> <hr/> <shiro:hasPermission name="使用者管理"> <a href="">使用者管理</a> </shiro:hasPermission> </body> </html>
最後,總結一下
1.shiro的關鍵程式碼:
認證: subject.login(token);
授權: subject.checkPermission("");
2.認證和授權的流程