Shiro之身份驗證
principals即主體的標識屬性,可以是任何東西,如用戶名、郵箱等,唯一即可。credentials是證明/憑證,即只有主體知道的安全值,如密碼/數字證書等。最常見的principals和credentials組合就是用戶/密碼了。
下面我們來看一個認證的例子,由于我們是用maven構建的實例,所以需要在pom.xml中添加依賴:
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11-20120805-1225</version> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.1.3</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.2.3</version> </dependency>
另外,準備一些用戶微分憑據,shiro.ini:
[users] zhang=123 wang=123
測試用例:
package org.shiro; import junit.framework.Assert; 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; import org.junit.Test; public class ShiroTest1{ @Test public void shiro_test1(){ //獲取SecurityManager工廠,此處使用ini配置文件初始化SecurityManager Factory<org.apache.shiro.mgt.SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini"); //得到SecurityManager實例并綁定給SecurityUtils org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); //得到Subject及創建用戶名/密碼身份驗證Token(即用戶身份/憑證) Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken("zhang","123"); try { subject.login(token); } catch (AuthenticationException e) { System.err.println(e.getMessage()); } //斷言用戶已經登錄 Assert.assertEquals(true, subject.isAuthenticated()); //退出 subject.logout(); } }
從上例中,我們可以看到shiro的身份認證流程,如果還沒有明白,可以看看下圖:
首先調用Subject.login(token)進行登錄,其會自動委托給SecurityManager,調用之前必須通過SecurityUtils.setSecurityManager()設置
2. SecurityManager負責真正的身份驗證邏輯,它會委托給Authenticator進行身份驗證
3. Authenticator才是真正的身份驗證者,shiro API中核心的身份認證入口點,此處可以自定義插入自己的實現
4. Authenticator可能會委托給相應的AuthenticationStrategy進行多Realm身份驗證,默認ModularRealmAuthenticator會調用AuthenticationStrategy進行多Realm身份驗證。
5. Authenticator會把相應的token傳入Realm,從Realm獲取身份驗證信息,如果沒有返回/拋出異常表示身份驗證失敗了。此外可以配置多個Realm,將按照相應的順序及策略進行訪問。
Realm
Shiro從Realm獲取安全數據(如用戶、角色、權限),就是說SecurityManager要驗證用戶身份,那么它需要從Realm獲取相應的用戶進行比較以確定用戶身份是否合法。也需要從Realm得到用戶相應的角色/權限進行驗證用戶是否能進行操作。可以把Realm看成DataSource,即安全數據源。如我們前面的例子使用ini配置,它使用的是org.apache.shiro.realm.text.IniRealm。
org.apache.shiro.realm.Realm接口如下:
public interface Realm { String getName(); //返回一個唯一的Realm名字 boolean supports(AuthenticationToken token); //判斷此Realm是否支持此Token AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException; //根據Token獲取認證信息 }
單Realm配置實例如下:
MyRealm.java:
package org.shiro; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.realm.Realm; public class MyRealm implements Realm { public String getName() { return "myRealm"; } public boolean supports(AuthenticationToken token) { return token instanceof UsernamePasswordToken; } public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("myrealm~~~~~~~~~~~~~~~~~~~~~~~~~~"); //得到用戶名 String username = (String)token.getPrincipal(); //得到密碼 String password = new String((char[])token.getCredentials()); if(!"zhang".equals(username)) { //如果用戶名錯誤 throw new UnknownAccountException(); } if(!"123".equals(password)) { //如果密碼錯誤 throw new IncorrectCredentialsException(); } //如果身份認證驗證成功,返回一個AuthenticationInfo實現; return new SimpleAuthenticationInfo(username, password, getName()); } }
shiro-realm.ini:
myRealm=org.shiro.MyRealm securityManager.realms=$myRealm
ShiroTest2.java:
package org.shiro; import junit.framework.Assert; 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; import org.junit.Test; public class ShiroTest2{ @Test public void shiro_test1(){ //獲取SecurityManager工廠,此處使用ini配置文件初始化SecurityManager Factory<org.apache.shiro.mgt.SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-realm.ini"); //得到SecurityManager實例并綁定給SecurityUtils org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); //得到Subject及創建用戶名/密碼身份驗證Token(即用戶身份/憑證) Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken("zhang","123"); try { subject.login(token); } catch (AuthenticationException e) { System.err.println(e.getMessage()); } //斷言用戶已經登錄 Assert.assertEquals(true, subject.isAuthenticated()); //退出 subject.logout(); } }
多Realm配置實例如下:
MyRealm1.java
package org.shiro; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.realm.Realm; public class MyRealm1 implements Realm { public String getName() { return "myRealm1"; } public boolean supports(AuthenticationToken token) { return token instanceof UsernamePasswordToken; } public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("myRealm1--------------------"); //得到用戶名 String username = (String)token.getPrincipal(); //得到密碼 String password = new String((char[])token.getCredentials()); if(!"zhang".equals(username)) { //如果用戶名錯誤 throw new UnknownAccountException(); } if(!"123".equals(password)) { //如果密碼錯誤 throw new IncorrectCredentialsException(); } //如果身份認證驗證成功,返回一個AuthenticationInfo實現; return new SimpleAuthenticationInfo(username, password, getName()); } }
MyRealm2.java:
package org.shiro; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.realm.Realm; public class MyRealm2 implements Realm { public String getName() { return "myRealm2"; } public boolean supports(AuthenticationToken token) { return token instanceof UsernamePasswordToken; } public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("myRealm2--------------------"); //得到用戶名 String username = (String)token.getPrincipal(); //得到密碼 String password = new String((char[])token.getCredentials()); if(!"zhang".equals(username)) { //如果用戶名錯誤 throw new UnknownAccountException(); } if(!"123".equals(password)) { //如果密碼錯誤 throw new IncorrectCredentialsException(); } //如果身份認證驗證成功,返回一個AuthenticationInfo實現; return new SimpleAuthenticationInfo(username, password, getName()); } }
shiro-multi-realm.ini:
myRealm1=org.shiro.MyRealm1 myRealm2=org.shiro.MyRealm2 securityManager.realms=$myRealm1,$myRealm2
ShiroTest3.java
package org.shiro; import junit.framework.Assert; 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; import org.junit.Test; public class ShiroTest3{ @Test public void shiro_test1(){ //獲取SecurityManager工廠,此處使用ini配置文件初始化SecurityManager Factory<org.apache.shiro.mgt.SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-multi-realm.ini"); //得到SecurityManager實例并綁定給SecurityUtils org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); //得到Subject及創建用戶名/密碼身份驗證Token(即用戶身份/憑證) Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken("zhang","123"); try { subject.login(token); } catch (AuthenticationException e) { System.err.println(e.getMessage()); } //斷言用戶已經登錄 Assert.assertEquals(true, subject.isAuthenticated()); //退出 subject.logout(); } }
注意ini配置中,都是通過$name來引用自定義的Realm的。另外,securityManager會按照realms指定的順序進行身份認證。如果刪除了“securityManager.realms=$myRealm1,$myRealm2”,那么securityManager會按照realm聲明的順序進行使用(即無需設置realms屬性,其會自動發現)。當我們顯示指定realm后,其他沒有指定realm將被忽略。
shiro默認提供的Realm
以后一般繼承AuthorizingRealm即可,其繼承了AuthenticatingRealm(即身份驗證),而且也間接繼承了CachingRealm(帶有緩存實現)。
JDBC Realm使用
示例,我們先在pom.xml中添加兩年操作數據庫的依賴
<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>apl</groupId> <artifactId>shiro-test</artifactId> <version>0.0.1-SNAPSHOT</version> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11-20120805-1225</version> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.1.3</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.25</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>0.2.23</version> </dependency> </dependencies> </project>
shiro-jdbc-realm.ini
dataSource=com.alibaba.druid.pool.DruidDataSource dataSource.driverClassName=com.mysql.jdbc.Driver dataSource.url=jdbc:mysql://localhost:3306/shiro dataSource.username=root dataSource.password=root jdbcRealm.dataSource=$dataSource jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm securityManager.realms=$jdbcRealm
ShiroTest4.java測試用例:
public class ShiroTest4 { @Test public void testJDBCRealm() { //1、獲取SecurityManager工廠,此處使用Ini配置文件初始化SecurityManager Factory<org.apache.shiro.mgt.SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-jdbc-realm.ini"); //2、得到SecurityManager實例 并綁定給SecurityUtils org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); //3、得到Subject及創建用戶名/密碼身份驗證Token(即用戶身份/憑證) Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123"); try { //4、登錄,即身份驗證 subject.login(token); } catch (AuthenticationException e) { //5、身份驗證失敗 e.printStackTrace(); } Assert.assertEquals(true, subject.isAuthenticated()); //斷言用戶已經登錄 //6、退出 subject.logout(); } }