基於java的開源安全管理框架(其實就是RBAC框架),能夠完成認證,受權,會話管理,加密,緩存等功能;前端
相同的還有Spring security,可是security依賴spring。shiro能夠在java se和java ee中使用,獨立性強。java
記住一點,Shiro 不會去維護用戶、維護權限;這些須要咱們本身去設計/提供;而後經過 相應的接口注入給 Shiro 便可。mysql
身份認證/登陸,驗證用戶是否是擁有相應的身份;web
受權,即權限驗證,驗證某個已認證的用戶是否擁有某個權限;即判斷用 戶是否能作事情,常見的如:驗證某個用戶是否擁有某個角色。算法
或者細粒度的驗證某個用 戶對某個資源是否具備某個權限;spring
會話管理,即用戶登陸後就是一次會話,在沒有退出以前,它的全部信 息都在會話中;會話能夠是普通 JavaSE 環境的,也能夠是如 Web 環境的;sql
加密,保護數據的安全性,如密碼加密存儲到數據庫,而不是明文存儲; 數據庫
Web 支持,能夠很是容易的集成到 Web 環境;apache
緩存,好比用戶登陸後,其用戶信息、擁有的角色/權限沒必要每次去查,這樣能夠提升效率;api
shiro 支持多線程應用的併發驗證,即如在一個線程中開啓另外一個線程,能把權限自動傳播過去;
提供測試支持;
容許一個用戶僞裝爲另外一個用戶(若是他們容許)的身份進行訪問;
記住我,這個是很是常見的功能,即一次登陸後,下次再來的話不用登陸 了。
應用代碼直接交互的對象是Subject,也就是說Shiro 的對外API核心就是Subject; 其每一個 API 的含義:
主體,表明了當前「用戶」,這個用戶不必定是一個具體的人,與當前應用交互 的任何東西都是 Subject,如網絡爬蟲,機器人等;
即一個抽象概念;全部 Subject 都綁定 到 SecurityManager,與 Subject 的全部交互都會委託給 SecurityManager;
能夠把 Subject 認 爲是一個門面;SecurityManager 纔是實際的執行者;
安全管理器;即全部與安全有關的操做都會與 SecurityManager 交互; 且它管理着全部 Subject;
能夠看出它是 Shiro 的核心,它負責與後邊介紹的其餘組件進行 交互,若是學習過 SpringMVC,你能夠把它當作 DispatcherServlet 前端控制器;
域,Shiro 從從 Realm 獲取安全數據(如用戶、角色、權限),就是說 SecurityManager要驗證用戶身份,
那麼它須要從 Realm 獲取相應的用戶進行比較以肯定用戶身份是否合法;也須要從 Realm 獲得用戶相應的角色/權限進行驗證用戶是否能進行操做;
能夠把 Realm 當作 DataSource,即安全數據源。
主體,能夠看到主體能夠是任何能夠與應用交互的「用戶」;
相 當 於 SpringMVC 中 的 DispatcherServlet 或 者 Struts2 中 的 FilterDispatcher;是 Shiro 的心臟;全部具體的交互都經過 SecurityManager 進行控制;
它管 理着全部 Subject、且負責進行認證和
受權、及會話、緩存的管理。
認證器,負責主體認證的,這是一個擴展點,若是用戶以爲 Shiro 默認的 很差,能夠自定義實現;
其須要認證策略(Authentication Strategy),即什麼狀況下算用戶認證經過了;
受權器,或者訪問控制器,用來決定主體是否有權限進行相應的操做;即控制 着用戶能訪問應用中的哪些功能;
能夠有 1 個或多個 Realm,能夠認爲是安全實體數據源,即用於獲取安全實體的; 能夠是 JDBC 實現,也能夠是 LDAP 實現,或者內存實現等等;
由用戶提供;注意:Shiro不知道你的用戶/權限存儲在哪及以何種格式存儲;因此咱們通常在應用中都須要實現本身 的 Realm;
若是寫過 Servlet 就應該知道 Session 的概念,Session 呢須要有人去管理 它的生命週期,這個組件就是 SessionManager;
而 Shiro 並不只僅能夠用在 Web 環境,也 能夠用在如普通的 JavaSE 環境、EJB 等環境;
全部呢,Shiro 就抽象了一個本身的 Session 來管理主體與應用之間交互的數據;
這樣的話,好比咱們在 Web 環境用,剛開始是一臺 Web 服務器;接着又上了臺 EJB 服務器;
這時想把兩臺服務器的會話數據放到一個地方, 這個時候就能夠實現本身的分佈式會話(如把數據放到 Memcached 服務器)
DAO 你們都用過,數據訪問對象,用於會話的 CRUD,好比咱們想把 Session 保存到數據庫,那麼能夠實現本身的 SessionDAO,經過如JDBC 寫到數據庫;
好比想把Session到 Memcached 中,能夠實現本身的 Memcached SessionDAO;另外 SessionDAO中可使用 Cache 進行緩存,以提升性能;
緩存控制器,來管理如用戶、角色、權限等的緩存的;由於這些數據基本 上不多去改變,放到緩存中後能夠提升訪問的性能
密碼模塊,Shiro 提升了一些常見的加密組件用於如密碼加密/解密的
1、 應用代碼經過 Subject 來進行認證和受權,而 Subject 又委託給 SecurityManager;
2、 咱們須要給 Shiro 的 SecurityManager 注入 Realm,從而讓 SecurityManager 能獲得合法的用戶及其權限進行判斷。
Shiro 不提供維護用戶/權限,而是經過 Realm 讓開發人員本身注入。
身份驗證:
即在應用中誰能證實他就是他本人。通常提供如他們的身份 ID 一些標識信息來代表他就是他本人,如提供身份證,用戶名/密碼來證實。
在 shiro 中,用戶須要提供 principals (身份)和 credentials(證實)給 shiro,從而應用能驗證用戶身份:
principals:身份,
用戶的身份信息,是Subject的標識屬性,可以惟一標識Subject。
即主體的標識屬性,能夠是任何東西,如用戶名、郵箱等,惟一便可。
一個主體能夠有多個 principals,但只有一個 Primary principals,通常是用戶名/密碼/手機號。
credentials:證實/憑證,
即只有主體知道的安全值,如密碼/數字證書等。
最多見的 principals 和 credentials 組合就是用戶名/密碼了。
Subject:主體
Realm:驗證主體的數據源。
流程:
數據庫表:
shiro.ini
[users]
zhangsan=111
lisi=222
pom.xml <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-all</artifactId> <version>1.2.3</version> </dependency>
package com.xpl; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; //這個要看一下由於java.lang包有可能導進來 import org.apache.shiro.mgt.SecurityManager; public class AuthenticationDemo { public static void main(String[] args) { // 1.建立SecurityManager工廠,加載ini配置文件 Factory<SecurityManager> securityFac = new IniSecurityManagerFactory("classpath:test.ini"); // 2.經過factory獲取SecurityManager實例 SecurityManager securityManager = securityFac.getInstance(); // 將SecurityManager實例設置進運行環境裏邊 SecurityUtils.setSecurityManager(securityManager); // 獲取Subject Subject subject = SecurityUtils.getSubject(); // 生成token注意:這裏的euserName和pwd是傳過來須要驗證的 // 而ini文件中存放username和pwd至關於數據庫的username、和pwd UsernamePasswordToken token = new UsernamePasswordToken("li三", "admin"); // 判斷token try { subject.login(token); if(subject.isAuthenticated()){ System.out.println("驗證成功!!"); } }catch (AuthenticationException e){ System.out.println("用戶名或密碼錯誤!!!"); } subject.logout(); } }
2.一、首先經過 new IniSecurityManagerFactory 並指定一個 ini 配置文件來建立一個SecurityManager 工廠;
2.二、接着獲取 SecurityManager 並綁定到 SecurityUtils,這是一個全局設置,設置一次便可;
2.三、經過 SecurityUtils 獲得 Subject,其會自動綁定到當前線程;若是在 web 環境在請求結束時須要解除綁定;而後獲取身份驗證的 Token,如用戶名/密碼;
2.四、調用 subject.login 方法進行登陸,其會自動委託給 SecurityManager.login 方法進行登陸;
2.五、如 果 身份驗證失敗請捕 獲 AuthenticationException 或 其 子 類 , 常 見 的 如 :
DisabledAccountException(禁用的賬號)、
LockedAccountException(鎖定的賬號)、
UnknownAccountException(錯誤的賬號)、
ExcessiveAttemptsException(登陸失敗次數過多)、
IncorrectCredentialsException (錯誤的憑證)、ExpiredCredentialsException(過時的憑證)等
對於頁面的錯誤消息展現,最好使用如「用戶名/密碼錯誤」而不是「用戶名錯誤」/「密碼錯誤」,防止一些惡意用戶非法掃描賬號庫;
2.六、最後能夠調用 subject.logout 退出,其會自動委託給 SecurityManager.logout 方法退出。
一、收集用戶身份/憑證,即如用戶名/密碼;
二、調用 Subject.login 進行登陸,若是失敗將獲得相應的 AuthenticationException 異常,根據異常提示用戶錯誤信息;不然登陸成功;
三、最後調用 Subject.logout 進行退出操做。
一、首先調用 Subject.login(token)進行登陸,其會自動委託給 Security Manager,調用以前必須經過 SecurityUtils. setSecurityManager()設置;
二、SecurityManager 負責真正的身份驗證邏輯;它會委託給 Authenticator 進行身份驗證;
三、Authenticator 纔是真正的身份驗證者,Shiro API 中核心的身份認證入口點,此處能夠自定義插入本身的實現;
四、Authenticator 可能會委託給相應的 AuthenticationStrategy 進行多 Realm 身份驗證,默認ModularRealmAuthenticator 會調用 AuthenticationStrategy 進行多 Realm 身份驗證;
五、Authenticator 會把相應的 token 傳入 Realm,從 Realm 獲取身份驗證信息,若是沒有返回/拋出異常表示身份驗證失敗了。此處能夠配置多個 Realm,將按照相應的順序及策略進行訪問。
<dependencies> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-all</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.10</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.20</version> </dependency> <dependency> <groupId>com.mchange</groupId> <artifactId>mchange-commons-java</artifactId> <version>0.2.3.4</version> </dependency> </dependencies> </project>
[main]
#採用第三方JdbcRealm鏈接數據庫
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
#實例化數據源
dataSource=com.alibaba.druid.pool.DruidDataSource
#設置參數
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql://localhost:3306/test
dataSource.username=root
dataSource.password=root
#將數據源設置到realm中
jdbcRealm.dataSource=$dataSource
jdbcRealm.permissionsLookupEnabled=true
securityManager.realms=$jdbcRealm
package com.xpl; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; //這個要看一下由於java.lang包有可能導進來 import org.apache.shiro.mgt.SecurityManager; public class AuthenticationDemo { public static void main(String[] args) { // 1.建立SecurityManager工廠,加載ini配置文件 Factory<SecurityManager> securityFac = new IniSecurityManagerFactory("classpath:test.ini"); // 2.經過factory獲取SecurityManager實例 SecurityManager securityManager = securityFac.getInstance(); // 將SecurityManager實例設置進運行環境裏邊 SecurityUtils.setSecurityManager(securityManager); // 獲取Subject Subject subject = SecurityUtils.getSubject(); // 生成token注意:這裏的euserName和pwd是傳過來須要驗證的 // 而ini文件中存放username和pwd至關於數據庫的username、和pwd UsernamePasswordToken token = new UsernamePasswordToken("li三", "admin"); // 判斷token try { subject.login(token); if(subject.isAuthenticated()){ System.out.println("驗證成功!!"); } }catch (AuthenticationException e){ System.out.println("用戶名或密碼錯誤!!!"); } subject.logout(); } }
有三種策略:
只要有一個Realm驗證成功便可,只返回第一個Realm身份驗證成功的認證信息,其餘的忽略。
只要有一個Realm驗證成功便可,和FirstSuccessfulStrategy不一樣,將返回全部Realm身份校驗成功的認證信息。
全部Realm驗證成功纔算成功,且返回全部Realm身份認證成功的認證信息,若是有一個失敗就失敗了。
ModularRealmAuthenticator默認是AtLeatOneSuccessfulStrategy
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.xpl</groupId> <artifactId>shiroDemo</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>commons-beanutils</groupId> <artifactId>commons-beanutils</artifactId> <version>1.9.2</version> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.10</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.17</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.7</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.5</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-all</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.10</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.20</version> </dependency> <dependency> <groupId>com.mchange</groupId> <artifactId>mchange-commons-java</artifactId> <version>0.2.3.4</version> </dependency> </dependencies> </project>
[main]
#採用第三方JdbcRealm鏈接數據庫
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
#實例化數據源
dataSource=com.alibaba.druid.pool.DruidDataSource
#設置參數
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql://localhost:3306/test
dataSource.username=root
dataSource.password=root
#將數據源設置到realm中
jdbcRealm.dataSource=$dataSource
securityManager.realms=$jdbcRealm
#設置驗證器(驗證策略)
authenticationStrategy= org.apache.shiro.authc.pam.AllSuccessfulStrategy
securityManager.authenticator.authenticationStrategy=$authenticationStrategy
package com.xpl; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; //這個要看一下由於java.lang包有可能導進來 import org.apache.shiro.mgt.SecurityManager; public class AuthenticationDemo { public static void main(String[] args) { // 1.建立SecurityManager工廠,加載ini配置文件 Factory<SecurityManager> securityFac = new IniSecurityManagerFactory("classpath:test.ini"); // 2.經過factory獲取SecurityManager實例 SecurityManager securityManager = securityFac.getInstance(); // 將SecurityManager實例設置進運行環境裏邊 SecurityUtils.setSecurityManager(securityManager); // 獲取Subject Subject subject = SecurityUtils.getSubject(); // 生成token注意:這裏的euserName和pwd是傳過來須要驗證的 // 而ini文件中存放username和pwd至關於數據庫的username、和pwd UsernamePasswordToken token = new UsernamePasswordToken("li三", "admin"); // 判斷token try { subject.login(token); if(subject.isAuthenticated()){ System.out.println("驗證成功!!"); } }catch (AuthenticationException e){ System.out.println("用戶名或密碼錯誤!!!"); } subject.logout(); } }
jdbcRealm已經實現從數據庫中獲取用戶信息並驗證的能力,但其靈活性差,若是須要實現特殊需求時將不能支持,此時可經過自定義Realm來實現身份認證;
Realm是一個接口,其中定義了根據token獲取認證信息的方法。好比:AuthenticatingRealm實現了獲取身份信息的功能,AuthorizingRealm實現了獲取權限i信息的功能。
自定義Realm須要繼承AuthorizingRealm,這樣既提供了身份認證的自定義方法,也能夠實現受權的自定義方法;
package com.xpl; 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.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; public class AuthenticationDemo extends AuthorizingRealm { @Override public String getName() { return "myRealm"; } //受權 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { return null; } //驗證 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { //獲取輸入的用戶名 String username = (String) token.getPrincipal(); // 這裏省略根據上邊獲取的用戶名查詢數據庫步驟而直接使用變量模擬 String pwd="admin"; // 將用戶信息封裝到simpleAuthenticationInfo中並返回 SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username, pwd, getName()); return simpleAuthenticationInfo; } }
[main]
#這個名字無所謂隨便起
myrealm= com.xpl.AuthenticationDemo
securityManager.realm=$myrealm
受權,也叫訪問控制,即在應用中控制誰能訪問哪些資源(如訪問頁面/編輯數據/頁面操做等)。
在受權中需瞭解的幾個關鍵對象:主體(Subject)、資源(Resource)、權限(Permission)、角色(Role)。
主體,即訪問應用的用戶,在 Shiro 中使用 Subject 表明該用戶。用戶只有受權後才容許訪問相應的資源。
在應用中用戶能夠訪問的任何東西,好比訪問 JSP 頁面、查看/編輯某些數據、訪問某個業務方法、打印文本等等都是資源。用戶只要受權後才能訪問。
安全策略中的原子受權單位,經過權限咱們能夠表示在應用中用戶有沒有操做某個資源的權力。即權限表示在應用中用戶能不能訪問某個資源,如:
訪問用戶列表頁面
查看/新增/修改/刪除用戶數據(即不少時候都是 CRUD(增查改刪)式權限控制)
打印文檔等等。。。
如上能夠看出,權限表明了用戶有沒有操做某個資源的權利,即反映在某個資源上的操做允不容許,不反映誰去執行這個操做。因此後續還須要把權限賦予給用戶,即定義哪一個用戶
容許在某個資源上作什麼操做(權限),Shiro 不會去作這件事情,而是由實現人員提供。
Shiro 支持粗粒度權限(如用戶模塊的全部權限)和細粒度權限(操做某個用戶的權限,即實例級別的),後續部分介紹。
角色表明了操做集合,能夠理解爲權限的集合,通常狀況下咱們會賦予用戶角色而不是權限,即這樣用戶能夠擁有一組權限,賦予權限時比較方便。典型的如:項目經理、技術
總監、CTO、開發工程師等都是角色,不一樣的角色擁有一組不一樣的權限。
即直接經過角色來驗證用戶有沒有操做權限,如在應用中 CTO、技術總監、開發工程師可使用打印機,假設某天不容許開發工程師使用打印機,此時須要從應用中刪除相
應代碼;再如在應用中 CTO、技術總監能夠查看用戶、查看權限;忽然有一天不容許技術總監查看用戶、查看權限了,須要在相關代碼中把技術總監角色從判斷邏輯中刪除
掉;即粒度是以角色爲單位進行訪問控制的,粒度較粗;若是進行修改可能形成多處代碼修改。
在程序中經過權限控制誰能訪問某個資源,角色聚合一組權限集合;這樣假設哪一個角色不能訪問某個資源,只須要從角色表明的權限集合中移除便可;無須修改多處代碼;
即粒度是以資源/實例爲單位的;粒度較細。
請 google 搜索「RBAC」和「RBAC 新解」分別瞭解「基於角色的訪問控制」「基於資源的訪問控制(Resource-Based Access Control)」。
[users]
zhangsan=admin,role1
lisi=admin,role2
[rolers]
roler1=user:add
roler2=user:*
roler3=user:update,user:delete
package com.xpl; 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.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource; public class AuthenticationDemo extends AuthorizingRealm { @Override public String getName() { return "myRealm"; } //受權 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { return null; } //驗證 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { //獲取輸入的用戶名 String username = (String) token.getPrincipal(); // 這裏省略根據上邊獲取的用戶名查詢數據庫步驟而直接使用變量模擬 System.out.println(username); String pwd="admin"; String salt="xpl"; // 將用戶信息封裝到simpleAuthenticationInfo中並返回 SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username, pwd, getName()); return simpleAuthenticationInfo; } }
package com.xpl; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authz.UnauthorizedException; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; //這個要看一下由於java.lang包有可能導進來 import org.apache.shiro.mgt.SecurityManager; import org.junit.Assert; import java.sql.SQLOutput; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class Test { public static void main(String[] args) { // 1.建立SecurityManager工廠,加載ini配置文件 Factory<SecurityManager> securityFac = new IniSecurityManagerFactory("classpath:test.ini"); // 2.經過factory獲取SecurityManager實例 SecurityManager securityManager = securityFac.getInstance(); // 將SecurityManager實例設置進運行環境裏邊 SecurityUtils.setSecurityManager(securityManager); // 獲取Subject Subject subject = SecurityUtils.getSubject(); // 生成token注意:這裏的euserName和pwd是傳過來須要驗證的 // 而ini文件中存放username和pwd至關於數據庫的username、和pwd UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "admin"); // 判斷token try { subject.login(token); if(subject.isAuthenticated()){ System.out.println("驗證成功!!"); } }catch (AuthenticationException e){ System.out.println("用戶名或密碼錯誤!!!"); } try { // 此基於角色的訪問控制(即隱式角色) // 基於角色的兩種方式:區別在於checkRole會拋出異常而hasRole不會 // //判斷擁有角色:role1 // Assert.assertTrue(subject.hasRole("role1")); // //判斷擁有角色:role1 and role2 // Assert.assertTrue(subject.hasAllRoles(Arrays.asList("role1", "role2"))); // //判斷擁有角色:role1 and role2 and !role3 // boolean[] result = subject.hasRoles(Arrays.asList("role1", "role2", "role3")); // Assert.assertEquals(true, result[0]); // Assert.assertEquals(true, result[1]); // Assert.assertEquals(false, result[2]); //斷言擁有角色:role1 // subject().checkRole("role1"); //斷言擁有角色:role1 and role3 失敗拋出異常 // subject().checkRoles("role1", "role3"); }catch (UnauthorizedException e){ System.out.println("受權失敗!!!"); } // subject.logout(); } }
這種方式的缺點就是若是不少地方進行了角色判斷,可是有一天不須要了那麼就須要修改相應代碼把全部相關的地方進行刪除;這就是粗粒度形成的問題。
package com.xpl; 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.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource; public class AuthenticationDemo extends AuthorizingRealm { @Override public String getName() { return "myRealm"; } //受權 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { return null; } //驗證 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { //獲取輸入的用戶名 String username = (String) token.getPrincipal(); // 這裏省略根據上邊獲取的用戶名查詢數據庫步驟而直接使用變量模擬 System.out.println(username); String pwd="admin"; String salt="xpl"; // 將用戶信息封裝到simpleAuthenticationInfo中並返回 SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username, pwd, getName()); return simpleAuthenticationInfo; } }
package com.xpl; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authz.UnauthorizedException; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; //這個要看一下由於java.lang包有可能導進來 import org.apache.shiro.mgt.SecurityManager; import org.junit.Assert; import java.sql.SQLOutput; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class Test { public static void main(String[] args) { // 1.建立SecurityManager工廠,加載ini配置文件 Factory<SecurityManager> securityFac = new IniSecurityManagerFactory("classpath:test.ini"); // 2.經過factory獲取SecurityManager實例 SecurityManager securityManager = securityFac.getInstance(); // 將SecurityManager實例設置進運行環境裏邊 SecurityUtils.setSecurityManager(securityManager); // 獲取Subject Subject subject = SecurityUtils.getSubject(); // 生成token注意:這裏的euserName和pwd是傳過來須要驗證的 // 而ini文件中存放username和pwd至關於數據庫的username、和pwd UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "admin"); // 判斷token try { subject.login(token); if(subject.isAuthenticated()){ System.out.println("驗證成功!!"); } }catch (AuthenticationException e){ System.out.println("用戶名或密碼錯誤!!!"); } try { // 此基於資源的訪問控制(即顯示角色) //判斷擁有權限:user:create Assert.assertTrue(subject.isPermitted("user:create")); //判斷擁有權限:user:update and user:delete Assert.assertTrue(subject.isPermittedAll("user:update", "user:delete")); //判斷沒有權限:user:view Assert.assertFalse(subject.isPermitted("user:view")); //斷言擁有權限:user:create subject.checkPermission("user:create"); //斷言擁有權限:user:delete and user:update subject.checkPermissions("user:delete", "user:update"); //斷言擁有權限:user:view 失敗拋出異常 subject.checkPermissions("user:view"); }catch (UnauthorizedException e){ System.out.println("受權失敗!!!"); } // subject.logout(); } }
也能夠叫基於權限的訪問控制,這種方式的通常規則是「資源標識符:操做」,便是資源級別的粒度;這種方式的好處就是若是要修改基本都是一個資源級別的修改,不會對其餘模塊代碼產生影響,粒度小。可是實
現起來可能稍微複雜點,須要維護「用戶——角色,角色——權限(資源:操做)」之間的關係。
在涉及到密碼存儲問題上,應該加密/生成密碼摘要存儲,而不是存儲明文密碼。
Shiro 提供了 base64 和 16 進制字符串編碼/解碼的 API 支持,方便一些編碼解碼操做。Shiro內部的一些數據的存儲/表示都使用了 base64 和 16 進制字符串。
↓base64 編碼/解碼操做
String str = "hello"; String base64Encoded = Base64.encodeToString(str.getBytes()); String str2 = Base64.decodeToString(base64Encoded); Assert.assertEquals(str, str2);
↓16 進制字符串編碼/解碼操做
String str = "hello"; String base64Encoded = Hex.encodeToString(str.getBytes()); String str2 = new String(Hex.decode(base64Encoded.getBytes())); Assert.assertEquals(str, str2);
還有一個可能常常用到的類 CodecSupport,提供了 toBytes(str, "utf-8") / toString(bytes, "utf-8")用於在 byte 數組/String 之間轉換。
散列算法通常用於生成數據的摘要信息,是一種不可逆的算法,通常適合存儲密碼之類的數據,常見的散列算法如 MD五、SHA 等。通常進行散列時最好提供一個 salt(鹽),好比加
密密碼「admin」,產生的散列值是「21232f297a57a5a743894a0e4a801fc3」,能夠到一些 md5 解密網站很容易的經過散列值獲得密碼「admin」,即若是直接對密碼進行散列相對來講破
解更容易,此時咱們能夠加一些只有系統知道的干擾數據,如用戶名和 ID(即鹽);這樣散列的對象是「密碼+用戶名+ID」,這樣生成的散列值相對來講更難破解。
[main]
credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher credentialsMatcher.hashAlgorithmName=md5 credentialsMatcher.hashIterations=1
#這個名字無所謂隨便起
myrealm= com.xpl.AuthenticationDemo
myrealm.credentialsMatcher=$credentialsMatcher
securityManager.realm=$myrealm
package com.xpl; 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.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource; public class AuthenticationDemo extends AuthorizingRealm { @Override public String getName() { return "myRealm"; } //受權 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { return null; } //驗證 protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { //獲取輸入的用戶名 String username = (String) token.getPrincipal(); // 這裏省略根據上邊獲取的用戶名查詢數據庫步驟而直接使用變量模擬 System.out.println(username); String pwd="a092bbb2935adc0661da625dd17ad2a5"; String salt="xpl"; // 將用戶信息封裝到simpleAuthenticationInfo中並返回 SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username, pwd, ByteSource.Util.bytes(salt), getName()); return simpleAuthenticationInfo; } }
package com.xpl; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.config.IniSecurityManagerFactory; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.Factory; //這個要看一下由於java.lang包有可能導進來 import org.apache.shiro.mgt.SecurityManager; public class Test { public static void main(String[] args) { // 1.建立SecurityManager工廠,加載ini配置文件 Factory<SecurityManager> securityFac = new IniSecurityManagerFactory("classpath:test.ini"); // 2.經過factory獲取SecurityManager實例 SecurityManager securityManager = securityFac.getInstance(); // 將SecurityManager實例設置進運行環境裏邊 SecurityUtils.setSecurityManager(securityManager); // 獲取Subject Subject subject = SecurityUtils.getSubject(); // 生成token注意:這裏的euserName和pwd是傳過來須要驗證的 // 而ini文件中存放username和pwd至關於數據庫的username、和pwd UsernamePasswordToken token = new UsernamePasswordToken("li三", "admin"); // 判斷token try { subject.login(token); if(subject.isAuthenticated()){ System.out.println("驗證成功!!"); } }catch (AuthenticationException e){ System.out.println("用戶名或密碼錯誤!!!"); } subject.logout(); } }