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();
}
}