2 Apache Shiro 身份认证(登录)

  • Post author:
  • Post category:其他


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

的实现原理还没有完全参透,等参透后再补充。



版权声明:本文为Silent_Paladin原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。