2 Apache Shiro 身份认证(登录)

2.1 概述

身份认证通常需要提供“用户”身份ID和一些标识信息,如用户名、密码。
Shiro 中需要提供 principals(身份)和 credentials(凭证)用于验证用户身份。
principals
身份,即主体的标识属性,如用户名、邮箱、手机等,要求唯一。一个主体可以有多个 principals。
credentials
凭证(证明),即只有主体知道的安全数据,如密码、数字证书等。
最常见的 principals 和 credentials 组合就是用户名和密码。

2.2 项目依赖

使用 Maven 构建 Shiro 应用和管理依赖,POM.xml 基本配置如下:

<dependencies>
  <dependency>
    <groupId>junitgroupId>
    <artifactId>junitartifactId>
    <version>4.12version>
  dependency>
  <dependency>
    <groupId>commons-logginggroupId>
    <artifactId>commons-loggingartifactId>
    <version>1.2version>
  dependency>
  <dependency>
    <groupId>org.apache.shirogroupId>
    <artifactId>shiro-coreartifactId>
    <version>1.3.2version>
  dependency>
dependencies>

2.3 基础的登录和退出功能

1 首先准备一些用户身份和凭证,以 ini 配置文件(shiro.ini)为例,通过 [users] 指定了两个主体:Steve/001Tony/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 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 身份认证流程

2 Apache Shiro 身份认证(登录)_第1张图片

身份认证流程如下:
(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:域,SecurityManagerRealm 获取安全数据(如用户名、密码)进行比较以确定用户身份是否合法,还可以从 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 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 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
2 Apache Shiro 身份认证(登录)_第2张图片

一般继承 AuthorizingRealm(授权)即可,AuthorizingRealm 继承了 AuthenticatingRealm(即身份认证),而且也间接继承了 CachingRealm(缓存实现)。
主要默认实现如下:
* org.apache.shiro.realm.text.IniRealmini 配置文件中 [users] 部分指定用户名、密码及角色;[roles] 部分指定角色权限信息;
* org.apache.shiro.realm.text.PropertiesRealmuser.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.alibabagroupId>
  <artifactId>druidartifactId>
  <version>1.0.31version>
dependency>
<dependency>
  <groupId>mysqlgroupId>
  <artifactId>mysql-connector-javaartifactId>
  <version>5.1.42version>
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 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”
Realm1Realm2 代码在“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 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 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 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 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 的实现原理还没有完全参透,等参透后再补充。

你可能感兴趣的:(Apache,Shiro)