2.1 概述
身份认证通常需要提供“用户”身份ID和一些标识信息,如用户名、密码。
Shiro 中需要提供 principals(身份)和 credentials(凭证)用于验证用户身份。
principals
身份,即主体的标识属性,如用户名、邮箱、手机等,要求唯一。一个主体可以有多个 principals。
credentials
凭证(证明),即只有主体知道的安全数据,如密码、数字证书等。
最常见的 principals 和 credentials 组合就是用户名和密码。
2.2 项目依赖
使用 Maven 构建 Shiro 应用和管理依赖,POM.xml 基本配置如下:
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
</dependencies>
2.3 基础的登录和退出功能
1 首先准备一些用户身份和凭证,以 ini 配置文件(
shiro.ini
)为例,通过
[users]
指定了两个主体:
Steve/001
和
Tony/002
[users]
Steve=001
Tony=002
2 测试用例
import static org.junit.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.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.junit.Test;
public class ShiroTest {
@Test
public void testHelloShiro() {
// 1.获取SecurityManager工厂,使用ini配置文件初始化SecurityManager
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
// 2.获取SecurityManager实例
SecurityManager securityManager = factory.getInstance();
// 3.将SecurityManager实例绑定给SecurityUtils
SecurityUtils.setSecurityManager(securityManager);
// 4.通过SecurityUtils获取Subject
Subject subject = SecurityUtils.getSubject();
// 5.创建用户名、密码身份验证token
UsernamePasswordToken token = new UsernamePasswordToken("Tony", "002");
try {
// 6.使用用户名、密码身份验证token进行身份认证,即登录
subject.login(token);
} catch (AuthenticationException e) {
fail("身份认证失败");
}
// 7.判断用户处于“已登录”状态
assertTrue(subject.isAuthenticated());
// 8.退出登录
subject.logout();
}
}
测试用例说明:
(1) 首先通过
new IniSecurityManagerFactory
创建一个
SecurityManager
工厂,传入一个
ini
配置文件参数;
(2) 其次通过
SecurityManager
工厂获取一个
SecurityManager
实例并绑定到
SecurityUtils
,这是一个全局设置,只需设置一次;
(3) 通过
SecurityUtils
得到一个
Subject
主体,Shiro 会自动将此主体绑定到当前线程。如果处于 Web 环境中,则请求结束时需要解除绑定;
(4) 获取用于身份验证的 token,如用户名/密码;
(5) 调用
subject.login(token)
方法进行登录认证,会自动委托给
SecurityManager.login
方法进行登录认证;
(6) 如果身份认证失败请捕获
AuthenticationException
或其子类,常见子类包括:
*
DisabledAccountException
:禁用账号
*
LockedAccountException
:锁定账号
*
UnknownAccountException
:账号错误
*
ExcessiveAttemptsException
:登录失败次数超出限制
*
IncorrectCredentialsException
:凭证错误
*
ExpiredCredentialsException
:凭证过期
对于页面的错误消息展示,最好使用如“用户名/密码错误”而不是“用户名错误/密码错误”,防止一些恶意用户非法扫描账号库;
(7) 最后调用
subject.logout()
退出,会自动委托给
SecurityManager.logout
方法退出。
从以上代码可以看出身份验证的步骤:
(1) 收集用户身份和凭证,如用户名和密码;
(2) 调用
subject.login(token)
进行登录认证,如果失败会发生对应的
AuthenticationException
异常,通过异常类型提示登录认证失败原因,若无异常则登录成功;
(3) 最后调用
subject.logout()
执行退出登录操作。
以上测试代码的问题:
(1) 用户名和密码硬编码在
ini
配置文件中,需要修改为数据库存储且密码需要加密;
(2) 用户身份 token 可能不仅仅是用户名和密码,可能还有其他信息,如登录时允许用户名/邮箱/手机号同时登录。
2.4 身份认证流程
身份认证流程如下:
(1) 首先调用
subject.login(token)
进行登录认证,自动委托给
SecurityManager
,调用前必须获取
SecurityManager
实例且要将此实例绑定给
SecurityUtils
;
(2)
SecurityManager
负责身份认证逻辑,会委托给
Authenticator
进行身份认证;
(3)
Authenticator
才是真正的身份认证负责人,是 Shiro API 中核心的身份认证入口点,可以自定义插入自己的身份认证实现逻辑;
(4)
Authenticator
可能会委托给相应的
AuthenticationStrategy
进行多
Realm
身份认证,默认
ModularRealmAuthenticator
会调用
AuthenticationStrategy
进行多
Realm
身份认证;
(5)
Authenticator
会把相应的 token 传入
Realm
,从
Realm
获取身份认证信息,如果抛出异常表示身份认证失败。可以配置多个
Realm
,将按照对应的顺序及策略进行访问。
2.5 Realm
Realm
:域,
SecurityManager
从
Realm
获取安全数据(如用户名、密码)进行比较以确定用户身份是否合法,还可以从
Realm
得到用户相应的角色和权限信息验证用户是否可以进行某种操作。可以将
Realm
看成安全数据的数据源。如 ini 配置文件方式会使用
org.apache.shiro.realm.text.IniRealm
。
org.apache.shiro.realm.Realm 接口定义如下:
package org.apache.shiro.realm;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
public interface Realm {
/**
* 返回一个唯一的Realm名字
*/
String getName();
/**
* 判断是否支持此token
*/
boolean supports(AuthenticationToken token);
/**
* 根据token获取认证信息
*/
AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;
}
2.5.1 单
Realm
配置
(1) 自定义
Realm
实现
package shiro.realm;
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 SingleRealm implements Realm {
@Override
public String getName() {
return "Single Realm";
}
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof UsernamePasswordToken;
}
@Override
public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
// 1.从token中获取用户名
String username = (String) token.getPrincipal();
// 2.从token中获取密码
String password = new String((char[]) token.getCredentials());
// 3.验证用户名,如果不匹配抛出UnknownAccountException异常
if (!"Hulk".equals(username)) {
throw new UnknownAccountException();
}
// 4.验证密码,如果不匹配抛出IncorrectCredentialsException异常
if (!"003".equals("003")) {
throw new IncorrectCredentialsException();
}
// 5.如果身份认证通过,返回一个AuthenticationInfo实现
return new SimpleAuthenticationInfo(username, password, getName());
}
}
(2) 在
ini
配置文件(shiro-realm.ini)中指定自定义的 Realm 实现
#声明一个Realm
singleRealm=shiro.realm.SingleRealm
#指定securityManager的realms实现
securityManager.realms=$singleRealm
(3) 测试用例
import static org.junit.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.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.junit.Test;
public class ShiroTest {
@Test
public void testCustomSingleRealm() {
// 1.获取SecurityManager工厂,使用ini配置文件初始化SecurityManager
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-realm.ini");
// 2.获取SecurityManager实例
SecurityManager securityManager = factory.getInstance();
// 3.将SecurityManager实例绑定给SecurityUtils
SecurityUtils.setSecurityManager(securityManager);
// 4.通过SecurityUtils获取Subject
Subject subject = SecurityUtils.getSubject();
// 5.创建用户名、密码身份验证token
UsernamePasswordToken token = new UsernamePasswordToken("Hulk", "003");
try {
// 6.使用用户名、密码身份验证token进行身份验证,即登录
subject.login(token);
} catch (AuthenticationException e) {
fail("身份认证失败");
}
// 7.判断用户处于“已登录”状态
assertTrue(subject.isAuthenticated());
}
}
2.5.2 多
Realm
配置
(1) 自定义 Realm1
package shiro.realm;
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 CustomRealm1 implements Realm {
@Override
public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
// 获取用户名
String username = (String) token.getPrincipal();
// 获取密码
String password = new String((char[]) token.getCredentials());
// 如果用户名错误
if (!"Barton".equals(username)) {
throw new UnknownAccountException();
}
// 如果密码错误
if (!"004".equals(password)) {
throw new IncorrectCredentialsException();
}
// 如果身份认证验证成功,返回一个AuthenticationInfo接口实现
return new SimpleAuthenticationInfo(username, password, getName());
}
@Override
public String getName() {
return "Custom Realm 1";
}
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof UsernamePasswordToken;
}
}
(2) 自定义 Realm2
package shiro.realm;
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 CustomRealm2 implements Realm {
@Override
public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
// 获取用户名
String username = (String) token.getPrincipal();
// 获取密码
String password = new String((char[]) token.getCredentials());
// 如果用户名错误
if (!"Thor".equals(username)) {
throw new UnknownAccountException();
}
// 如果密码错误
if (!"005".equals(password)) {
throw new IncorrectCredentialsException();
}
// 如果身份认证验证成功,返回一个AuthenticationInfo接口实现
return new SimpleAuthenticationInfo(username, password, getName());
}
@Override
public String getName() {
return "Custom Realm 2";
}
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof UsernamePasswordToken;
}
}
(3) 在
ini
配置文件(shiro-multi-realm.ini)中指定多个自定义的 Realm 实现
#声明多个Realm
customRealm1=shiro.realm.CustomRealm1
customRealm2=shiro.realm.CustomRealm2
#指定securityManager的realms实现
securityManager.realms=$customRealm1,$customRealm2
(4) 测试用例
import static org.junit.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.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.junit.Test;
public class ShiroTest {
@Test
public void testCustomMultiRealm() {
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-multi-realm.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("Barton", "004");
try {
subject.login(token);
} catch (AuthenticationException e) {
fail("身份认证失败");
}
assertTrue(subject.isAuthenticated());
subject.logout();
token = new UsernamePasswordToken("Thor", "005");
try {
subject.login(token);
} catch (AuthenticationException e) {
fail("身份认证失败");
}
assertTrue(subject.isAuthenticated());
subject.logout();
}
}
2.5.3 Shiro 默认提供的
Realm
一般继承
AuthorizingRealm
(授权)即可,
AuthorizingRealm
继承了
AuthenticatingRealm
(即身份认证),而且也间接继承了
CachingRealm
(缓存实现)。
主要默认实现如下:
*
org.apache.shiro.realm.text.IniRealm
:
ini
配置文件中
[users]
部分指定用户名、密码及角色;
[roles]
部分指定角色权限信息;
*
org.apache.shiro.realm.text.PropertiesRealm
:
user.username=password,role1,role2
指定用户名、密码及角色;
role.role1=permission1,permission2
指定角色权限信息;
*
org.apache.shiro.realm.jdbc.JdbcRealm
:通过 SQL 查询相应的用户名、密码、角色、权限等信息。
2.5.4
JdbcRealm
使用
(1) 使用 MySQL 数据库和阿里巴巴 druid 连接池,添加 Maven 依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.31</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.42</version>
</dependency>
(2) 新建数据库
shiro
,并在
shiro
数据库中新建3张表:
*
users
:存放用户名和密码
*
user_roles
:存放用户和角色
*
roles_permissions
:存放角色和权限
在 users 中添加一个用户记录,用户名
Fury
,密码
000
drop database if exists shiro;
create database shiro;
use shiro;
create table users (
id bigint auto_increment,
username varchar(100),
password varchar(100),
password_salt varchar(100),
constraint pk_users primary key(id)
) charset=utf8 ENGINE=InnoDB;
create unique index idx_users_username on users(username);
create table user_roles(
id bigint auto_increment,
username varchar(100),
role_name varchar(100),
constraint pk_user_roles primary key(id)
) charset=utf8 ENGINE=InnoDB;
create unique index idx_user_roles on user_roles(username, role_name);
create table roles_permissions(
id bigint auto_increment,
role_name varchar(100),
permission varchar(100),
constraint pk_roles_permissions primary key(id)
) charset=utf8 ENGINE=InnoDB;
create unique index idx_roles_permissions on roles_permissions(role_name, permission);
insert into users(username,password)values('Fury','000');
(3) 创建
ini
配置文件(shiro-jdbc-realm.ini)
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/shiro
dataSource.username=root
dataSource.password=123456
jdbcRealm.dataSource=$dataSource
securityManager.realms=$jdbcRealm
ini
配置文件说明:
* 变量名=全限定类名:自动创建一个类实例
* 变量名.属性=值:自动调用相应的 setter 方法进行赋值
* $变量名:引用之前的一个对象实例
(4) 测试用例
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.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.junit.Test;
public class ShiroTest {
@Test
public void testJdbcRealm() {
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-jdbc-realm.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("Fury", "000");
try {
subject.login(token);
} catch (AuthenticationException e) {
e.printStackTrace();
fail("身份验证失败");
}
assertTrue(subject.isAuthenticated());
}
}
2.6 Authenticator 和 AuthenticationStrategy
authenticator
的职责是验证用户账号,是 Shiro API 中身份认证核心的入口点:
public interface Authenticator {
public AuthenticationInfo authenticate(AuthenticationToken authenticationToken)
throws AuthenticationException;
}
如果验证通过,返回
AuthenticationInfo
认证信息,其中包含了用户身份和凭证;如果验证失败会抛出相应的
AuthenticationException
实现。
SecurityManager
接口继承了
authenticator
,另外还有一个
ModularRealmAuthenticator
实现,委托给多个
Realm
进行认证,认证规则通过
AuthenticationStrategy
接口指定,默认提供的实现:
*
FirstSuccessfulStrategy
:只要有一个
Realm
验证成功即可,只返回第一个验证成功的
Realm
的认证信息,其他的忽略;
*
AtLeastOneSuccessfulStrategy
:只要有一个
Realm
验证成功即可,和
FirstSuccessfulStrategy
不同,返回所有验证成功的
Realm
的认证信息;
*
AllSuccessfulStrategy
:所有
Realm
验证成功才算成功,如果有一个验证失败就算失败,验证成功返回所有成功的
Realm
的认证信息。
ModularRealmAuthenticator
默认使用
AtLeastOneSuccessfulStrategy
策略。
假设有3个 Realm:
*
Realm1
:用户名/密码为“Barton/004”时成功,返回身份/凭证为“Barton/004”
*
Realm2
:用户名/密码为“Thor/005”时成功,返回身份/凭证为“Thor/005”
*
Realm3
:用户名/密码为“Barton/004”时成功,和
Realm1
不同之处在于返回的身份/凭证变为“Clint Barton/004”
Realm1
和
Realm2
代码在“2.5.2 多
Realm
配置”章节中已经展示,
Realm3
代码如下:
package shiro.realm;
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 CustomRealm3 implements Realm {
@Override
public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
// 获取用户名
String username = (String) token.getPrincipal();
// 获取密码
String password = new String((char[]) token.getCredentials());
// 如果用户名错误
if (!"Barton".equals(username)) {
throw new UnknownAccountException();
}
// 如果密码错误
if (!"004".equals(password)) {
throw new IncorrectCredentialsException();
}
// 如果身份认证验证成功,返回一个AuthenticationInfo接口实现
return new SimpleAuthenticationInfo("Clint " + username, password, getName());
}
@Override
public String getName() {
return "Custom Realm 3";
}
@Override
public boolean supports(AuthenticationToken token) {
return token instanceof UsernamePasswordToken;
}
}
2.6.1 测试
FirstSuccessfulStrategy
(1) 创建
ini
配置文件(shiro-authenticator-first-success.ini)
#指定securityManager的authenticator实现
authenticator=org.apache.shiro.authc.pam.ModularRealmAuthenticator
securityManager.authenticator=$authenticator
#指定securityManager.authenticator的authenticationStrategy
firstSuccessfulStrategy=org.apache.shiro.authc.pam.FirstSuccessfulStrategy
securityManager.authenticator.authenticationStrategy=$firstSuccessfulStrategy
customRealm1=shiro.realm.CustomRealm1
customRealm2=shiro.realm.CustomRealm2
customRealm3=shiro.realm.CustomRealm3
securityManager.realms=$customRealm3,$customRealm2,$customRealm1
(2) 测试用例
import static org.junit.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.mgt.SecurityManager;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.junit.Test;
public class ShiroTest {
@Test
public void testFirstSuccessfulStrategyWithSuccess() {
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-authenticator-first-success.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("Barton", "004");
try {
subject.login(token);
} catch (AuthenticationException e) {
e.printStackTrace();
fail("身份验证失败");
}
assertTrue(subject.isAuthenticated());
// 获取身份集合,包含Realm验证成功的身份信息
PrincipalCollection principalCollection = subject.getPrincipals();
assertEquals(1, principalCollection.asList().size());
System.out.println(principalCollection.asList().get(0));
}
}
测试结果打印:
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
Clint Barton
2.6.2 测试
AtLeastOneSuccessfulStrategy
(1) 创建
ini
配置文件(shiro-authenticator-atLeastOne-success.ini)
#指定securityManager的authenticator实现
authenticator=org.apache.shiro.authc.pam.ModularRealmAuthenticator
securityManager.authenticator=$authenticator
#指定securityManager.authenticator的authenticationStrategy
atLeastOneSuccessfulStrategy=org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy
securityManager.authenticator.authenticationStrategy=$atLeastOneSuccessfulStrategy
customRealm1=shiro.realm.CustomRealm1
customRealm2=shiro.realm.CustomRealm2
customRealm3=shiro.realm.CustomRealm3
securityManager.realms=$customRealm1,$customRealm2,$customRealm3
(2) 测试用例
import static org.junit.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.mgt.SecurityManager;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.junit.Test;
public class ShiroTest {
@Test
public void testAtLeastOneSuccessfulStrategyWithSuccess() {
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-authenticator-atLeastOne-success.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("Barton", "004");
try {
subject.login(token);
} catch (AuthenticationException e) {
e.printStackTrace();
fail("身份验证失败");
}
assertTrue(subject.isAuthenticated());
// 获取身份集合,包含Realm验证成功的身份信息
PrincipalCollection principalCollection = subject.getPrincipals();
assertEquals(2, principalCollection.asList().size());
for (Object principal : principalCollection.asList()) {
System.out.println(principal);
}
}
}
测试结果打印:
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
Barton
Clint Barton
2.6.3 测试
AllSuccessfulStrategy
(1) 创建
ini
配置文件(shiro-authenticator-all-success.ini)
#指定securityManager的authenticator实现
authenticator=org.apache.shiro.authc.pam.ModularRealmAuthenticator
securityManager.authenticator=$authenticator
#指定securityManager.authenticator的authenticationStrategy
allSuccessfulStrategy=org.apache.shiro.authc.pam.AllSuccessfulStrategy
securityManager.authenticator.authenticationStrategy=$allSuccessfulStrategy
customRealm1=shiro.realm.CustomRealm1
customRealm2=shiro.realm.CustomRealm2
customRealm3=shiro.realm.CustomRealm3
securityManager.realms=$customRealm1,$customRealm3
(2) 测试用例
import static org.junit.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.mgt.SecurityManager;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.junit.Test;
public class ShiroTest {
@Test
public void testAllSuccessfulStrategyWithSuccess() {
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-authenticator-all-success.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("Barton", "004");
try {
subject.login(token);
} catch (AuthenticationException e) {
e.printStackTrace();
fail("身份验证失败");
}
assertTrue(subject.isAuthenticated());
// 获取身份集合,包含Realm验证成功的身份信息
PrincipalCollection principalCollection = subject.getPrincipals();
assertEquals(2, principalCollection.asList().size());
for (Object principal : principalCollection.asList()) {
System.out.println(principal);
}
}
}
测试结果打印:
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
Barton
Clint Barton
以上是认证成功的测试用例,以下再给出一个认证失败的测试用例:
(1) 创建
ini
配置文件(shiro-authenticator-all-fail.ini)
#指定securityManager的authenticator实现
authenticator=org.apache.shiro.authc.pam.ModularRealmAuthenticator
securityManager.authenticator=$authenticator
#指定securityManager.authenticator的authenticationStrategy
allSuccessfulStrategy=org.apache.shiro.authc.pam.AllSuccessfulStrategy
securityManager.authenticator.authenticationStrategy=$allSuccessfulStrategy
customRealm1=shiro.realm.CustomRealm1
customRealm2=shiro.realm.CustomRealm2
customRealm3=shiro.realm.CustomRealm3
securityManager.realms=$customRealm1,$customRealm2
(2) 测试用例
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.junit.Test;
public class ShiroTest {
@Test(expected = UnknownAccountException.class)
public void testAllSuccessfulStrategyWithFail() {
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-authenticator-all-fail.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("Barton", "004");
subject.login(token);
}
}
2.6.3 自定义
AuthenticationStrategy
原文中
AuthenticationStrategy
的实现原理还没有完全参透,等参透后再补充。