一。 shiro簡介
Apache Shiro(發音爲「shee-roh」,日語「堡壘(Castle)」的意思)是一個強大易用的Java安全框架,提供了認證、授權、加密和會話管理功能,可爲任何應用提供安全保障 - 從命令行應用、移動應用到大型網絡及企業應用。
Shiro爲解決下列問題(我喜歡稱它們爲應用安全的四要素)提供了保護應用的API:
- 認證 - 用戶身份識別,常被稱爲用戶「登錄」;
- 授權 - 訪問控制;
- 密碼加密 - 保護或隱藏數據防止被偷窺;
- 會話管理 - 每用戶相關的時間敏感的狀態。
Shiro還支持一些輔助特性,如Web應用安全、單元測試和多線程,它們的存在強化了上面提到的四個要素
從2003年至今,框架選擇方面的情況已經改變了不少,但今天仍有令人信服的理由讓你選擇Shiro。其實理由相當多,Apache Shiro:
- 易於使用 - 易用性是這個項目的最終目標。應用安全有可能會非常讓人糊塗,令人沮喪,並被認爲是「必要之惡」【譯註:比喻應用安全方面的編程。】。若是能讓它簡化到新手都能很快上手,那它將不再是一種痛苦了。
- 廣泛性 - 沒有其他安全框架可以達到Apache Shiro宣稱的廣度,它可以爲你的安全需求提供「一站式」服務。
- 靈活性 - Apache Shiro可以工作在任何應用環境中。雖然它工作在Web、EJB和IoC環境中,但它並不依賴這些環境。Shiro既不強加任何規範,也無需過多依賴。
- Web能力 - Apache Shiro對Web應用的支持很神奇,允許你基於應用URL和Web協議(如REST)創建靈活的安全策略,同時還提供了一套控制頁面輸出的JSP標籤庫。
- 可插拔 - Shiro乾淨的API和設計模式使它可以方便地與許多的其他框架和應用進行集成。你將看到Shiro可以與諸如Spring、Grails、Wicket、Tapestry、Mule、Apache Camel、Vaadin這類第三方框架無縫集成。
支持 - Apache Shiro是Apache軟件基金會成員,這是一個公認爲了社區利益最大化而行動的組織。項目開發和用戶組都有隨時願意提供幫助的友善成員。像Katasoft這類商業公司,還可以給你提供需要的專業支持和服務。
核心概念:Subject,SecurityManager和Realms
Subject
Subject一詞是一個安全術語,其基本意思是「當前的操作用戶」。稱之爲「用戶」並不準確,因爲「用戶」一詞通常跟人相關。在安全領域,術語「Subject」可以是人,也可以是第三方進程、後臺帳戶(Daemon Account)或其他類似事物。它僅僅意味着「當前跟軟件交互的東西」。但考慮到大多數目的和用途,你可以把它認爲是Shiro的「用戶」概念。在代碼的任何地方,你都能輕易的獲得Shiro Subject,
SecurityManager
Subject的「幕後」推手是SecurityManager。Subject代表了當前用戶的安全操作,SecurityManager則管理所有用戶的安全操作。它是Shiro框架的核心,充當「保護傘」,引用了多個內部嵌套安全組件,它們形成了對象圖。但是,一旦SecurityManager及其內部對象圖配置好,它就會退居幕後,應用開發人員幾乎把他們的所有時間都花在Subject API調用上。
那麼,如何設置SecurityManager呢?嗯,這要看應用的環境。例如,Web應用通常會在Web.xml中指定一個Shiro Servlet Filter,這會創建SecurityManager實例,如果你運行的是一個獨立應用,你需要用其他配置方式,但有很多配置選項。
一個應用幾乎總是隻有一個SecurityManager實例。它實際是應用的Singleton(儘管不必是一個靜態Singleton)。跟Shiro裏的幾乎所有組件一樣,SecurityManager的缺省實現是POJO,而且可用POJO兼容的任何配置機制進行配置 - 普通的Java代碼、Spring XML、YAML、.properties和.ini文件等。基本來講,能夠實例化類和調用JavaBean兼容方法的任何配置形式都可使用。
爲此,Shiro藉助基於文本的INI配置提供了一個缺省的「公共」解決方案。INI易於閱讀、使用簡單並且需要極少依賴。你還能看到,只要簡單地理解對象導航,INI可被有效地用於配置像SecurityManager那樣簡單的對象圖。注意,Shiro還支持Spring XML配置及其他方式,但這裏只我們只討論INI。
Realms
Shiro的第三個也是最後一個概念是Realm。Realm充當了Shiro與應用安全數據間的「橋樑」或者「連接器」。也就是說,當切實與像用戶帳戶這類安全相關數據進行交互,執行認證(登錄)和授權(訪問控制)時,Shiro會從應用配置的Realm中查找很多內容。
從這個意義上講,Realm實質上是一個安全相關的DAO:它封裝了數據源的連接細節,並在需要時將相關數據提供給Shiro。當配置Shiro時,你必須至少指定一個Realm,用於認證和(或)授權。配置多個Realm是可以的,但是至少需要一個。
Shiro內置了可以連接大量安全數據源(又名目錄)的Realm,如LDAP、關係數據庫(JDBC)、類似INI的文本配置資源以及屬性文件等。如果缺省的Realm不能滿足需求,你還可以插入代表自定義數據源的自己的Realm實現
文字摘自 (http://www.infoq.com/cn/articles/apache-shiro)
shiro架構
除前面所講Subject、SecurityManager 、Realm三個核心組件外,Shiro主要組件還包括:
- Authenticator :認證就是覈實用戶身份的過程。這個過程的常見例子是大家都熟悉的「用戶/密碼」組合。多數用戶在登錄軟件系統時,通常提供自己的用戶名(當事人)和支持他們的密碼(證書)。如果存儲在系統中的密碼(或密碼錶示)與用戶提供的匹配,他們就被認爲通過認證。
- Authorizer :授權實質上就是訪問控制 - 控制用戶能夠訪問應用中的哪些內容,比如資源、Web頁面等等。
- SessionManager :在安全框架領域,Apache Shiro提供了一些獨特的東西:可在任何應用或架構層一致地使用Session API。即,Shiro爲任何應用提供了一個會話編程範式 - 從小型後臺獨立應用到大型集羣Web應用。這意味着,那些希望使用會話的應用開發者,不必被迫使用Servlet或EJB容器了。或者,如果正在使用這些容器,開發者現在也可以選擇使用在任何層統一一致的會話API,取代Servlet或EJB機制。
- CacheManager :對Shiro的其他組件提供緩存支持。
- Cryptography 提供安全的支持
常用的操作
ini配置權限信息(參考http://shiro.apache.org/configuration.html)
- # =======================
- # Shiro INI configuration
- # =======================
-
- [main]
-
- [users]
- # 設置用戶信息
- # 語法是 username = password, roleName1, roleName2, …, roleNameN
- jiaozi = 123456,role1
-
- [roles]
- # 角色信息和角色擁有的權限
- #語法是 rolename = permissionDefinition1, permissionDefinition2, …, permissionDefinitionN
- #權限的語法 * 表示所有權限 一般語法是 權限類型.權限動作.權限的資源id 比如 user:delete:1 表示擁有刪除1號用戶的權限 user:delete:*表示刪除所有用戶權限
- admin = *
- role1 = user:query:*, user:delete:1
-
- [urls]
- # web中的url過濾
maven項目添加支持
- <dependency>
- <groupId>org.apache.shiro</groupId>
- <artifactId>shiro-core</artifactId>
- <version>1.4.0</version>
- </dependency>
- <!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
- <dependency>
- <groupId>commons-logging</groupId>
- <artifactId>commons-logging</artifactId>
- <version>1.2</version>
- </dependency>
測試驗證權限過程(參考http://shiro.apache.org/10-minute-tutorial.html)
- package shiro;
-
- import org.apache.shiro.SecurityUtils;
- import org.apache.shiro.authc.UsernamePasswordToken;
- import org.apache.shiro.config.IniSecurityManagerFactory;
- import org.apache.shiro.session.Session;
- import org.apache.shiro.subject.Subject;
- import org.apache.shiro.util.Factory;
-
- public class TestShiro {
- public static void main(String[] args) {
- testIni();
- }
- public static void testIni() {
- //從配置文件中讀取用戶的權限信息
- Factory<org.apache.shiro.mgt.SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
- org.apache.shiro.mgt.SecurityManager securityManager = (org.apache.shiro.mgt.SecurityManager)factory.getInstance();
- SecurityUtils.setSecurityManager(securityManager);
- //獲取登錄用戶信息
- Subject currentUser = SecurityUtils.getSubject();
- Session session = currentUser.getSession();
- session.setAttribute( "保存數據的鍵", "保存數據的值" );
- /**
- * 用戶包括兩部分
- // * principals and credentials
- * principals(本人)表示用戶的標識信息 比如用戶名 用戶地址等
- * credentials(憑證)表示用戶用於登錄的憑證 比如密碼 證書等
- */
- //isAuthenticated判斷是否登錄過
- if ( !currentUser.isAuthenticated() ) {
- //令牌 用戶名和密碼 其實就是credentials
- UsernamePasswordToken token = new UsernamePasswordToken("jiaozi", "123456");
- token.setRememberMe(true);
- //開始登錄操作 操作後 isAuthenticated就是true
- currentUser.login(token);
- System.out.println("登錄成功");
- //判斷是否有某個角色
- if(currentUser.hasRole("role1")){
- System.out.println("擁有角色role1");
- }
- if(currentUser.isPermitted("user:query:1")){
- System.out.println("擁有查詢1號用戶權限");
- }
- }
-
- currentUser.logout();
-
- }
-
- }
一般權限信息存在於是數據庫 我這裏模擬放在內存變量map中
添加realm的實現類
- package shiro;
-
- import java.util.HashMap;
- import java.util.HashSet;
- import java.util.Map;
- 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.SimpleAccount;
- 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;
- /**
- * 自定義realm的實現
- * @author jiaozi
- *
- */
- public class MyRealm extends AuthorizingRealm {
- //用於存放用戶信息
- static Map<String,String> userList=null;
- //用於存放角色信息
- static Map<String,String> roleList=null;
- //每個realm都有一個名字
- static String REALM_NAME="myrealm";
- static{
- //這裏也可以從數據庫讀取
- //模擬用戶
- userList=new HashMap();
- userList.put("zs", "123456,role2,role3");
- //模擬權限
- roleList=new HashMap();
- roleList.put("role2","user:query:*");
- roleList.put("role3", "user:*");
- }
- /**
- * 支持哪種令牌
- */
- @Override
- public boolean supports(AuthenticationToken token) {
- // TODO Auto-generated method stub
- return token instanceof UsernamePasswordToken;
- }
- /**
- * 獲取權限過程
- */
- @Override
- protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
- //獲取用戶名
- String userName=principals.getPrimaryPrincipal().toString();
- //構建權限的類
- SimpleAuthorizationInfo sai=new SimpleAuthorizationInfo();
- Set<String> proleList=new HashSet<String>();
- Set<String> stringPermissions=new HashSet<String>();
- if(userList.containsKey(userName)){
- String[] roles=userList.get(userName).toString().split(",");
- for(int i=1;i<roles.length;i++){
- proleList.add(roles[i]);
- String pp=roleList.get(roles[i]);
- String[] ppArry=pp.split(",");
- for(int j=0;j<ppArry.length;j++){
- stringPermissions.add(ppArry[j]);
- }
- }
- }
- sai.setRoles(proleList);
- sai.setStringPermissions(stringPermissions);
- return sai;
- }
- /**
- * 認證過程
- */
- @Override
- protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
- UsernamePasswordToken upt=(UsernamePasswordToken)token;
- String userName=token.getPrincipal().toString();
- String password=String.valueOf(upt.getPassword());
- if(userList.containsKey(userName)){
- String realPwd=userList.get(userName).toString().split(",")[0];
- if(realPwd.equals(password)){
- SimpleAccount sa=new SimpleAccount(userName,password,"REALM_NAME");
- return sa;
- }
- }
- return null;
- }
-
- }
ini配置文件中配置該realm main的部分添加
- [main]
- # 定義securityManager, Realms and 其他
- myRealm= shiro.MyRealm
- securityManager.realms=$iniRealm , $myRealm
- #iniRealm是內置的默認realm讀取ini文件的傳入 如果自定義了realm該realm被替代 如果還想使用可以直接引用
- #如果有多個realm 策略是所有的realm都要驗證通過 還是隻需要一個驗證通過 我這裏只需要一個驗證通過 默認是所有
- #參考http://shiro.apache.org/authentication.html
- authcStrategy = org.apache.shiro.authc.pam.FirstSuccessfulStrategy
- securityManager.authenticator.authenticationStrategy = $authcStrategy
同上測試 只要ini或者自定義ream存在的用戶都能登錄
二。 shiro web
集成web集成 參考 http://shiro.apache.org/web.html#programmatic-support
在之前例子基礎上進行拓展 添加war項目 添加shiro-web依賴
- <dependency>
- <groupId>org.apache.shiro</groupId>
- <artifactId>shiro-web</artifactId>
- <version>1.4.0</version>
- </dependency>
web.xml添加shiro支持的過濾器和ini文件路徑配置參數
- <context-param>
- <param-name>shiroConfigLocations</param-name>
- <param-value>/WEB-INF/shiro.ini</param-value>
- </context-param>
- <listener>
- <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
- </listener>
- <filter>
- <filter-name>ShiroFilter</filter-name>
- <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
- </filter>
- <filter-mapping>
- <filter-name>ShiroFilter</filter-name>
- <url-pattern>/*</url-pattern>
- <dispatcher>REQUEST</dispatcher>
- <dispatcher>FORWARD</dispatcher>
- <dispatcher>INCLUDE</dispatcher>
- <dispatcher>ERROR</dispatcher>
- </filter-mapping>
添加幾個html和jsp用於測試
login.html添加用於登錄的表單
- <form action="loginServlet" method="post">
- 用戶名 :<input type="text" name="userName"/>
- 密碼:<input type="text" name="password"/>
- <input type="submit">
- </form>
添加query.jsp用於當登錄成功後跳轉的頁面
添加add.jsp用於測試擁有某個角色才能進入的頁面
添加一個檢測沒有角色失敗頁面 un.jsp
t添加一個登錄的servlet
web.xml
- <servlet>
- <description></description>
- <display-name>LoginServlet</display-name>
- <servlet-name>LoginServlet</servlet-name>
- <servlet-class>cn.et.web.LoginServlet</servlet-class>
- </servlet>
- <servlet-mapping>
- <servlet-name>LoginServlet</servlet-name>
- <url-pattern>/loginServlet</url-pattern>
- </servlet-mapping>
servlet類
- package cn.et.web;
-
- import java.io.IOException;
-
- import javax.servlet.ServletException;
- import javax.servlet.http.HttpServlet;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
-
- import org.apache.shiro.SecurityUtils;
- import org.apache.shiro.authc.AuthenticationException;
- import org.apache.shiro.authc.UsernamePasswordToken;
- import org.apache.shiro.subject.Subject;
-
- public class LoginServlet extends HttpServlet {
- private static final long serialVersionUID = 1L;
-
- /**
- * 登錄進入方法
- */
- protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
- Subject subject = SecurityUtils.getSubject();
- UsernamePasswordToken upt=new UsernamePasswordToken(request.getParameter("userName"), request.getParameter("password"));
- try {
- subject.login(upt);
- request.getRequestDispatcher("/query.jsp").forward(request, response);
- } catch (AuthenticationException e) {
- request.getRequestDispatcher("/login.html").forward(request, response);
- }
- }
-
- /**
- * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
- */
- protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
- // TODO Auto-generated method stub
- doGet(request, response);
- }
-
- }
修改shiro.ini添加路徑過濾
- # =======================
- # Shiro INI configuration
- # =======================
-
- [main]
- # 定義securityManager, Realms and 其他
- myRealm= shiro.MyRealm
- securityManager.realms=$iniRealm , $myRealm
- #如果有多個realm 策略是所有的realm都要驗證通過 還是隻需要一個驗證通過 我這裏只需要一個驗證通過 默認是所有
- authcStrategy = org.apache.shiro.authc.pam.FirstSuccessfulStrategy
- securityManager.authenticator.authenticationStrategy = $authcStrategy
- authc.loginUrl = /login.html
- #如果沒有登錄 會先跳轉到loginUrl 如果登錄了沒權限 跳轉到unauthorizedUrl指定頁面
- roles.loginUrl= /login.html
- roles.unauthorizedUrl= /un.html
-
- [users]
- # 設置用戶信息
- # 語法是 username = password, roleName1, roleName2, …, roleNameN
- jiaozi = 123456,role1
-
- [roles]
- # 角色信息和角色擁有的權限
- #語法是 rolename = permissionDefinition1, permissionDefinition2, …, permissionDefinitionN
- #權限的語法 * 表示所有權限 一般語法是 權限類型.權限動作.權限的資源id 比如 user:delete:1 表示擁有刪除1號用戶的權限 user:delete:*表示刪除所有用戶權限
- admin = *
- role1 = user:query:*, user:delete:1
-
- [urls]
- # web中的url過濾
- #語法是 某個路徑 = 怎麼樣過濾1,過濾2 常用的過濾有
- # anon 匿名用戶
- # authc 表示用戶和密碼驗證過濾 類 org.apache.shiro.web.filter.authc.FormAuthenticationFilter 沒有登錄自動跳轉到登錄頁
- # perms 是否擁有某些權限過濾 類 org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter 用法 perms["remote:invoke"]
- # roles是否擁有某個角色 org.apache.shiro.web.filter.authz.RolesAuthorizationFilter 用法roles[administrator]
- # user 是否是某個用戶 org.apache.shiro.web.filter.authc.UserFilter
- # 也可以在main中自定義filter url就可以應用 參考http://shiro.apache.org/web.html#programmatic-support
-
- /login.html = anon
- /loginServlet = anon
- /query.jsp = authc
- /add.jsp = roles[role2]
測試不登錄直接訪問 query.jsp或者add.jsp直接跳轉到login.html中
測試登錄ini中的jiaozi用戶登錄成功後可以訪問query.jsp訪問add.jsp自動跳轉到un.jsp 因爲jiaozi用戶沒有role2這個juese
測試登錄自定義relam的zs用戶 發現都可以進入
shiro通過自定義過濾器 定製自己的過濾規則 比如url信息全部位於數據庫 需要所有的除了控制層的路徑都經過自定義的url過濾可以添加類 繼承自AuthorizationFilter實現isAccessAllowed方法
- package cn.et.conf;
-
- import javax.servlet.ServletRequest;
- import javax.servlet.ServletResponse;
-
- import org.apache.shiro.web.filter.authz.AuthorizationFilter;
-
- public class MyFilter extends AuthorizationFilter {
-
- /*
- * 返回true表示允許訪問 false表示不允許訪問
- */
- @Override
- protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)
- throws Exception {
- return false;
- }
-
- }
在main中定義該filter 直接在url中引用即可
- myfilter=cn.et.conf.MyFilter
- /** myfilter
當用戶訪問了某個頁面 通過authc過濾跳轉到登錄頁面 當用戶登錄後 希望自動跳轉到之前的頁面 如果是直接進入的登錄頁就進入默認的頁面 的實現過程
偶爾 有些無法跳轉 結果下載了fav.ico 因爲有時一個請求到頁面 會順帶一個獲取圖標的請求 如果圖標請求在後面 shiro記錄的前面的途徑就是這個圖標的請求 此時在html添加 禁止網頁圖標的請求
- <link rel="icon" href="data:image/ico;base64,aWNv">
登錄實現
- @RequestMapping("loginBlog")
- public String login(String userName,String password,HttpServletRequest request){
- Subject subject = SecurityUtils.getSubject();
- UsernamePasswordToken upt=new UsernamePasswordToken(userName,password);
- try {
- subject.login(upt);
- subject.getSession().setAttribute("userInfo", userDao.queryByContent(userName, password).get(0));
- //關鍵代碼 shiro自動保存了上一次請求的地址 只需要獲取並重新跳轉即可
- //可以使用WebUtils.issueRedirect重定向跳轉
- SavedRequest savedRequest = WebUtils.getSavedRequest(request);
- if(savedRequest!=null)
- return "redirect:"+savedRequest.getRequestURI()+"?"+savedRequest.getQueryString();
- return "/queryBlog";
- } catch (AuthenticationException e) {
- return "redirect:/login.html";
- }
-
- }
三。 shiro集成springboot
參考官網 spring集成 只是換成springboot (http://shiro.apache.org/spring.html)
添加maven的jsp支持和shiro-spring和springboot
- <parent>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-parent</artifactId>
- <version>1.5.9.RELEASE</version>
- </parent>
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
- <dependency>
- <groupId>org.apache.tomcat.embed</groupId>
- <artifactId>tomcat-embed-jasper</artifactId>
- </dependency>
- <dependency>
- <groupId>org.apache.shiro</groupId>
- <artifactId>shiro-core</artifactId>
- <version>1.4.0</version>
- </dependency>
- <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-web -->
- <dependency>
- <groupId>org.apache.shiro</groupId>
- <artifactId>shiro-web</artifactId>
- <version>1.4.0</version>
- </dependency>
- <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-spring -->
- <dependency>
- <groupId>org.apache.shiro</groupId>
- <artifactId>shiro-spring</artifactId>
- <version>1.4.0</version>
- </dependency>
-
- <!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
- <dependency>
- <groupId>commons-logging</groupId>
- <artifactId>commons-logging</artifactId>
- <version>1.2</version>
- </dependency>
-
- </dependencies>
html和realm還是之前的 ini文件被替換成在spring中配置 添加配置的bean
將之前servlet替換成springmvc的controller
- package cn.et.boot;
-
- import org.apache.shiro.SecurityUtils;
- import org.apache.shiro.authc.AuthenticationException;
- import org.apache.shiro.authc.UsernamePasswordToken;
- import org.apache.shiro.subject.Subject;
- import org.springframework.stereotype.Controller;
- import org.springframework.web.bind.annotation.RequestMapping;
- @Controller
- public class LoginController {
- @RequestMapping("loginServlet")
- public String login(String userName,String password){
- Subject subject = SecurityUtils.getSubject();
- UsernamePasswordToken upt=new UsernamePasswordToken(userName,password);
- try {
- subject.login(upt);
- return "/query.jsp";
- } catch (AuthenticationException e) {
- return "redirect:/login.html";
- }
-
- }
- }
添加一個springboot啓動類 just run
- package cn.et.boot;
-
- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
-
- @SpringBootApplication
- public class Main {
-
- public static void main(String[] args) {
- SpringApplication.run(Main.class, args);
- }
-
- }
通過 http://localhost:8080/login.html 訪問
測試 輸入錯誤的密碼
測試使用zs登錄 訪問 add.jsp發現跳轉到un.jsp
四。自定義filter實現動態配置url攔截
如果在spring中配置靜態的url攔截 添加一個新的路徑 每次都需要修改配置文件 如果使用springboot 每次都需要修改urls這個map 這個map是初始化bean
的時候創建 後面再往裏面加是沒有效果的 所以需要在攔截時自己手工調用對應的filter邏輯
- @Bean
- public ShiroFilterFactoryBean shiroFilter(@Autowired org.apache.shiro.mgt.SecurityManager securityManager){
- ShiroFilterFactoryBean sffb=new ShiroFilterFactoryBean();
- sffb.setSecurityManager(securityManager);
- sffb.setLoginUrl("/login.html");
- sffb.setUnauthorizedUrl("/un.jsp");
- Map<String, String> urls=new HashMap<String, String>();
- /*
- * 定義url
- /login.html = anon
- /loginServlet = anon
- /query.jsp = authc
- /add.jsp = roles[role2]
- * */
-
- urls.put("/login.html", "anon");
- urls.put("/loginServlet", "anon");
- urls.put("/query.jsp", "authc");
- urls.put("/add.jsp", "roles[role1]");
- sffb.setFilterChainDefinitionMap(urls);
- return sffb;
- }
自定義過濾器的代碼是 注意添加到spring容器中 添加@Component註解
- package cn.et.conf;
-
- import java.util.HashMap;
- import java.util.Map;
- import java.util.Set;
- import java.util.regex.Pattern;
-
- import javax.servlet.Filter;
- import javax.servlet.ServletRequest;
- import javax.servlet.ServletResponse;
- import javax.servlet.http.HttpServletRequest;
-
- import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
- import org.apache.shiro.subject.Subject;
- import org.apache.shiro.util.CollectionUtils;
- import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
- import org.apache.shiro.web.filter.authz.AuthorizationFilter;
- import org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter;
- import org.apache.shiro.web.filter.authz.RolesAuthorizationFilter;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.stereotype.Component;
- @Component
- public class MyFilter extends AuthorizationFilter {
- static Map<String, String> urls=new HashMap<String, String>();
-
- @Autowired
- private ShiroFilterFactoryBean sffb;
- /**
- * 匹配指定過濾器規則的url
- * @param regex
- * @param url
- * @return
- */
- public static boolean matchUrl(String regex,String url){
- regex=regex.replaceAll("/+", "/");
- if(regex.equals(url)){
- return true;
- }
- regex=regex.replaceAll("\\.", "\\\\.");
- // /login.html /l*.html
- regex=regex.replaceAll("\\*", ".*");
- // /**/login.html /a/b/login.html
- if(regex.indexOf("/.*.*/")>=0){
- regex=regex.replaceAll("/\\.\\*\\.\\*/", "((/.*/)+|/)");
- }
- System.out.println(regex+"----"+url);
- return Pattern.matches(regex, url);
- }
- /**
- * 測試
- * @param args
- */
- public static void main(String[] args) {
- System.out.println(matchUrl("/**/s*.html","/t/g/login.html"));
- }
- /**
- * 在map中模擬 這個也可以將來定義在數據庫中
- */
- static{
- urls.put("/login.html", "anon");
- urls.put("/loginBlog", "anon");
- urls.put("/un.jsp", "anon");
- urls.put("/queryBlogByToken", "anon");
- urls.put("/query.jsp", "authc");
- urls.put("/add.jsp", "roles[role1]");
- }
- /**
- * isAccessAllowed用於判斷當前url的請求是否能驗證通過 如果驗證失敗 調用父類的onAccessDenied決定跳轉到登錄失敗頁還是授權失敗頁面
- */
- @Override
- protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)
- throws Exception {
- HttpServletRequest req=(HttpServletRequest)request;
- String url=req.getRequestURI();
- //通過url獲取授權類型
- String urlAuth=urls.get(url);
- if(urlAuth==null){
- return false;
- }
- //配置的過濾器是anon 直接放過
- if(urlAuth.startsWith("anon")){
- return true;
- }
- //配置的是authc 判斷當前用戶是否認證通過
- Subject subject = getSubject(request, response);
- if(urlAuth.startsWith("authc")){
- return subject.isAuthenticated();
- }
- //授權認證 也需要判斷是否登錄 沒有登錄返回 登錄繼續下面的驗證
- boolean ifAuthc=subject.isAuthenticated();
- if(!ifAuthc)
- return ifAuthc;
- //如果是定義的roles過濾器 獲取所有的roles 一般是roles[a,b]
- if(urlAuth.startsWith("roles")){
- String[] rolesArray=urlAuth.split("roles\\[")[1].split("\\]")[0].split(",");
- if (rolesArray == null || rolesArray.length == 0) {
- return true;
- }
- Set<String> roles = CollectionUtils.asSet(rolesArray);
- return subject.hasAllRoles(roles);
- }
- if(urlAuth.startsWith("perms")){
- String[] perms=urlAuth.split("perms\\[")[1].split("\\]")[0].split(",");
- boolean isPermitted = true;
- if (perms != null && perms.length > 0) {
- if (perms.length == 1) {
- if (!subject.isPermitted(perms[0])) {
- isPermitted = false;
- }
- } else {
- if (!subject.isPermittedAll(perms)) {
- isPermitted = false;
- }
- }
- }
-
- return isPermitted;
- }
- return false;
- }
-
- }
修改springboot的Config類 將所有的請求都交給自定義的過濾器處理
- @Bean
- public ShiroFilterFactoryBean shiroFilter(@Autowired org.apache.shiro.mgt.SecurityManager securityManager){
- ShiroFilterFactoryBean sffb=new ShiroFilterFactoryBean();
- sffb.setSecurityManager(securityManager);
- sffb.setLoginUrl("/login.html");
- sffb.setUnauthorizedUrl("/un.jsp");
- Map<String, String> urls=new HashMap<String, String>();
- urls.put("/**", "myFilter");
- sffb.setFilterChainDefinitionMap(urls);
- return sffb;
- }
添加一個過濾匹配的策略 定義多個relms 只要有一個realm的數據通過則通過 (這裏只有一個realm不配置也可 留着做記錄 將來怕要用)
- /**
- * <span style="font-size:13.3333px;">多個relms 只要有一個realm的數據通過則通過</span>
- * @return
- */
- @Bean
- public ModularRealmAuthenticator authentictor(){
- org.apache.shiro.authc.pam.ModularRealmAuthenticator mra=new org.apache.shiro.authc.pam.ModularRealmAuthenticator();
- mra.setAuthenticationStrategy(new org.apache.shiro.authc.pam.FirstSuccessfulStrategy());
- return mra;
- }
- /**
- * 定義默認的securityManager
- * @return
- */
- @Bean
- public DefaultWebSecurityManager securityManager(@Autowired Realm myRealm,@Autowired ModularRealmAuthenticator authentictor){
- DefaultWebSecurityManager dwm=new DefaultWebSecurityManager();
- dwm.setRealm(myRealm);
- authentictor.setRealms(dwm.getRealms());
- dwm.setAuthenticator(authentictor);
- return dwm;
- }