第2章身份验证

Authentication(身份验证):在应用中证明其合法性,一般提需提供身份ID、一些标识信息来证明其身份(如提供身份证、用户名/密码来证明),在shiro中,用户需要提供principals (身份)和credentials(证明)给shiro,从而应用才能验证用户身份:

Principals(身份):即主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。一个主体可以有多个principals,但只有一个Primary principals,一般是用户名/密码/手机号。

Credentials(证明/凭证):即主体的安全值,如密码/数字证书等。

最常见的principals和credentials组合就是用户名/密码了。接下来先进行一个基本的身份认证,另外两个相关的概念是之前提到的SubjectRealm,分别是主体及验证主体的数据源。

2.2  环境准备

本文使用Maven构建,因此需要一点Maven知识。首先准备环境依赖:

<dependencies>

<dependency>

<groupId>junit</groupId>

<artifactId>junit</artifactId>

<version>4.10</version>

<scope>test</scope>

</dependency>

<dependency>

<groupId>commons-logging</groupId>

<artifactId>commons-logging-api</artifactId>

<version>1.1</version>

</dependency>

<dependency>

<groupId>org.slf4j</groupId>

<artifactId>slf4j-log4j12</artifactId>

<version>1.6.4</version>

</dependency>

<dependency>

<groupId>org.apache.shiro</groupId>

<artifactId>shiro-core</artifactId>

<version>1.2.3</version>

</dependency>

</dependencies>

<dependencies>

<dependency>

<groupId>junit</groupId>

<artifactId>junit</artifactId>

<version>4.9</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.2</version>

</dependency>

</dependencies>

添加junit、common-logging及shiro-core依赖即可。需要说明的是,Shiro 依赖于 SLFJ 日志框架,而 SLFJ 只是一个接口,并没有提供具体的实现,您可以选择 Log4J 作为它的实现,正好 SLFJ 也提供了一个 slf4j-log4j12 的 Artifact,所以这就用上了。 下面是 hello 项目的 Maven 依赖图:

既然使用了 Log4J,那么就应该在 classpath 下提供一个 log4j.properties 文件:

log4j.rootLogger = INFO, console

log4j.appender.console = org.apache.log4j.ConsoleAppender

log4j.appender.console.layout = org.apache.log4j.PatternLayout

log4j.appender.console.layout.ConversionPattern = %-5p %c(%L) - %m%n

通过上面的配置将日志输出到控制台上,并配置了日志输出格式。

2.3  登录/退出

1、准备一些用户身份/凭据(shiro.ini)

[users]

zhang=123

wang=123

[users]

zhang=123

wang=123

此处使用ini配置文件,通过[users]指定了两个主体:zhang/123、wang/123。

2、测试用例(com.github.zhangkaitao.shiro.chapter2.LoginLogoutTest)

package com.github.zbh.shiro.chapter2;

import org.apache.shiro.subject.*;

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.util.Factory;

import org.junit.Test;

public class LoginLogoutTest {

@Test

public void testHelloworld() {

// 1、获取SecurityManager工厂,此处使用Ini配置文件初始化SecurityManager

Factory<org.apache.shiro.mgt.SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.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、身份验证失败}

// 断言用户已经登录

Assert.assertEquals(true, subject.isAuthenticated());

// 6、退出

subject.logout();}

1)通过new IniSecurityManagerFactory并指定一个ini配置文件来创建一个SecurityManager工厂;

2)获取SecurityManager并绑定到SecurityUtils,这是一个全局设置,设置一次即可;

3)通过SecurityUtils得到Subject,其会自动绑定到当前线程,如果在web环境在请求结束时需要解除绑定,然后获取身份验证的Token,如用户名/密码;

4)调用subject.login进行登录,其会自动委托给SecurityManager.login方法进行登录;

5)如果身份验证失败请捕获AuthenticationException或其子类(常见的子类有DisabledAccountException禁用的帐号、LockedAccountException锁定的帐号、UnknownAccountException错误的帐号、 ExcessiveAttemptsException登录失败次数过多、IncorrectCredentialsException错误的凭证、ExpiredCredentialsException过期的凭证等),具体请查看其继承关系;对于页面的错误消息展示,最好使用如“用户名/密码错误”而不是“用户名错误”/“密码错误”,防止一些恶意用户非法扫描帐号库;

6)最后可以调用subject.logout退出,其会自动委托给SecurityManager.logout方法退出。

3、代码可总结出身份验证的步骤

1)收集用户身份/凭证,即如用户名/密码;

2)调用Subject.login进行登录,如果失败将得到相应的AuthenticationException异常,根据异常提示用户错误信息;否则登录成功;

3)最后调用Subject.logout进行退出操作。

4、测试的几个问题

1)用户名/密码硬编码在ini配置文件,以后需要改成如数据库存储,且密码需要加密存储;

2)用户身份Token可能不仅仅是用户名/密码,也可有其他信息,如用户名/邮箱/手机号同时登录。

2.4  Authenticate身份认证流程

流程如下:

1)调用Subject.login(token)进行登录,其会委托给Security Manager,调用之前必须通过

SecurityUtils. setSecurityManager()设置;

2)SecurityManager负责真正的身份验证逻辑,它会委托给Authenticator进行身份验证;

3)Authenticator才是真正的身份验证者,Shiro API身份认证入口点的核心,此处可以自定义自己的实现;

4)Authenticator可能会委托给相应的AuthenticationStrategy进行多Realm身份验证,

默认ModularRealmAuthenticator会调用AuthenticationStrategy进行多Realm身份验证;

5)Authenticator会把相应的token传入Realm,从Realm获取身份验证信息,如果没有返回/抛出异常表示身

份验证失败了,此处可以配置多个Realm,将按照相应的顺序及策略进行访问。

2.5  Realm

Realm(域):Shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要通过Realm获取相应的用户进行比较以确定用户身份是否合法;当验证通过后需要从Realm得到用户相应的角色/权限进行授权;可以把Realm看成DataSource,即安全数据源。之前的ini配置方式使用的是org.apache.shiro.realm.text.IniRealm。

org.apache.shiro.realm.Realm接口如下:

//返回一个唯一的Realm名字

String getName(); 

//判断此Realm是否支持此Token

Boolean supports(AuthenticationToken token);

//根据Token获取认证信息

AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException; 

2.5.1 Realm配置

1、自定义Realm实现(com.github.zhangkaitao.shiro.chapter2.realm.MyRealm1):

public  class  MyRealm1  implements  Realm { 

@Override

    public String getName() { 

        return "myrealm1";      } 

@Override

    public boolean supports(AuthenticationToken token) {          

//仅支持UsernamePasswordToken类型的Token

        return token instanceof UsernamePasswordToken;    } 

@Override

    public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { 

//得到用户名

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

    } 

}   public class MyRealm1 implements Realm {

@Override

public String getName() {

return "myrealm1";

}

@Override

public boolean supports(AuthenticationToken token) {

//仅支持UsernamePasswordToken类型的Token

return token instanceof UsernamePasswordToken;

}

@Override

public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

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

}

}

2、ini配置文件指定自定义Realm实现(shiro-realm.ini) 

#声明一个realm 

myRealm1=com.github.zhangkaitao.shiro.chapter2.realm.MyRealm1 

#指定securityManagerrealms实现

securityManager.realms=$myRealm1  

通过$name来引入之前的realm定义

3、测试用例请参考com.github.zhangkaitao.shiro.chapter2.LoginLogoutTest的testCustomRealm测试方

法,只需要把之前的shiro.ini配置文件改成shiro-realm.ini即可。

2.5.2Realm配置

1、自定义Realm实现(com.github.zhangkaitao.shiro.chapter2.realm.MyRealm2):

public class MyRealm2 implements Realm {

@Override

public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token)

throws AuthenticationException {

String username = (String) token.getPrincipal(); // 得到用户名

String password = new String((char[]) token.getCredentials()); // 得到密码

if (!"wang".equals(username)) {

throw new UnknownAccountException(); // 如果用户名错误

}

if (!"123".equals(password)) {

throw new IncorrectCredentialsException(); // 如果密码错误

}

// 如果身份认证验证成功,返回一个AuthenticationInfo实现;

return new SimpleAuthenticationInfo(username, password, getName());

}

@Override

public String getName() {

return "MyRealm2";

}

@Override

public boolean supports(AuthenticationToken token) {

return token instanceof UsernamePasswordToken;

}

}

2、ini配置文件(shiro-multi-realm.ini)

#声明一个realm 

myRealm1=com.github.zhangkaitao.shiro.chapter2.realm.MyRealm1 

myRealm2=com.github.zhangkaitao.shiro.chapter2.realm.MyRealm2 

#指定securityManager的realms实现

securityManager.realms=$myRealm1,$myRealm2  

securityManager会按照realms指定的顺序进行身份认证。此处我们使用显示指定顺序的方式指定了Realm的顺序,如果删除“securityManager.realms=$myRealm1,$myRealm2”,那么securityManager会按照realm声明的顺序进行使用(即无需设置realms属性,其会自动发现),当我们显示指定realm后,其他没有指定的realm将被忽略,如“securityManager.realms=$myRealm1”,那么myRealm2不会被设置进去。

3、测试用例请参考com.github.zhangkaitao.shiro.chapter2.LoginLogoutTest的testCustomMultiRealm测试方法。

Shiro默认提供的Realm

以后一般继承AuthorizingRealm(授权)即可,其继承了AuthenticatingRealm(即身份验证),而且也间接继承了CachingRealm(带有缓存实现),其中主要默认实现如下:

org.apache.shiro.realm.text.IniRealm[users]部分指定用户名/密码及其角色,[roles]部分指定角色即权限信息;

org.apache.shiro.realm.text.PropertiesRealm user.username=password,role1,role2指定用户名/密码及其角色,role.role1=permission1,permission2指定角色及权限信息;

org.apache.shiro.realm.jdbc.JdbcRealm通过sql查询相应的信息,如:

“select password from users where username = ?”获取用户密码,

“select password, password_salt from users where username = ?”获取用户密码及盐,

“select role_name from user_roles where username = ?”获取用户角色,

“select permission from roles_permissions where role_name = ?”获取角色对应的权限信息;

也可以调用相应的api进行自定义sql;

[浅谈密码加SALT原理 我们知道,如果直接对密码进行散列,那么黑客可以对通过获得这个密码散列值,然后通过查散列值字典(例如MD5密码破解网站),得到某用户的密码。加Salt可以一定程度上解决这一问题。所谓加Salt方法,就是加点“佐料”。其基本想法是这样的:当用户首次提供密码时(通常是注册时),由系统自动往这个密码里撒一些“佐料”,然后再散列。而当用户登录时,系统为用户提供的代码撒上同样的“佐料”,然后散列,再比较散列值,已确定密码是否正确。这里的“佐料”被称作“Salt值”,这个值是由系统随机生成的,并且只有系统知道。这样,即便两个用户使用了同一个密码,由于系统为它们生成的salt值不同,他们的散列值也是不同的。即便黑客可以通过自己的密码和自己生成的散列值来找具有特定密码的用户,但这个几率太小了(密码和salt值都得和黑客使用的一样才行)]

2.5.3 JDBC Realm使用

1、数据库及依赖

<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>  

<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>

本文将使用mysql数据库及druid连接池;

2、到数据库shiro下建三张表:users(用户名/密码)、user_roles(用户/角色)、roles_permissions(角色/权限),具体请参照shiro-example-chapter2/sql/shiro.sql;并添加一个用户记录,用户名/密码为zhang/123;

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(‘zhang‘,‘123‘);

3、ini配置(shiro-jdbc-realm.ini)

[main]

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= 

jdbcRealm.dataSource=$dataSource 

securityManager.realms=$jdbcRealm  

1)变量名=类 根据类自动创建一个类实例

2)变量名.属性=值 自动调用相应的setter方法进行赋值

3)$变量名 引用之前的一个对象实例

4)测试代码请参照com.github.zhangkaitao.shiro.chapter2.LoginLogoutTest的testJDBCRealm方法和之前的没什么区别。

2.6  AuthenticatorAuthenticationStrategy (认证器及认证策略)

Authenticator的职责是验证用户帐号,是Shiro API中身份验证核心的入口点:

public AuthenticationInfo authenticate(AuthenticationToken authenticationToken)   throws AuthenticationException;

如果验证成功将返回AuthenticationInfo验证信息(此信息中包含了身份及凭证),如果验证失败将抛出相应的AuthenticationException。

SecurityManager接口继承了Authenticator,同时还实现了ModularRealmAuthenticator,ModularRealmAuthenticator委托多个Realm进行验证,验证规则通过AuthenticationStrategy接口指定,默认提供的实现有:

FirstSuccessfulStrategy:只要有一个Realm验证成功即可,只返回第一个Realm身份验证成功的认证信息,其他的忽略;

AtLeastOneSuccessfulStrategy:只要有一个Realm验证成功即可,和FirstSuccessfulStrategy不同,返回所有Realm身份验证成功的认证信息;

AllSuccessfulStrategy:所有Realm验证成功才算成功,且返回所有Realm身份验证成功的认证信息,如果有一个失败就失败了。

ModularRealmAuthenticator默认使用AtLeastOneSuccessfulStrategy策略, 假设我们有三个realm:

myRealm1: 用户名/密码为zhang/123时成功,且返回身份/凭据为zhang/123;

myRealm2: 用户名/密码为wang/123时成功,且返回身份/凭据为wang/123;

myRealm3: 用户名/密码为zhang/123时成功,且返回身份/凭据为[email protected]/123,和myRealm1不同的是返回时的身份变了;

1、自定义Realm实现(com.github.zhangkaitao.shiro.chapter2.realm.MyRealm3):

public class MyRealm3 implements Realm {

@Override

public String getName() {

return "myrealm3";

}

@Override

public boolean supports(AuthenticationToken token) {

return token instanceof UsernamePasswordToken; //仅支持UsernamePasswordToken类型的Token }

@Override

public AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

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 + "@163.com", password, getName()); }

}

2、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 

#指定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

myRealm1=com.github.zhangkaitao.shiro.chapter2.realm.MyRealm1 

myRealm2=com.github.zhangkaitao.shiro.chapter2.realm.MyRealm2 

myRealm3=com.github.zhangkaitao.shiro.chapter2.realm.MyRealm3 

securityManager.realms=$myRealm1,$myRealm3 

myRealm1=com.github.zhangkaitao.shiro.chapter2.realm.MyRealm1

myRealm2=com.github.zhangkaitao.shiro.chapter2.realm.MyRealm2

myRealm3=com.github.zhangkaitao.shiro.chapter2.realm.MyRealm3

securityManager.realms=$myRealm1,$myRealm3

3、测试代码(com.github.zhangkaitao.shiro.chapter2.AuthenticatorTest)

3.1)首先通用化登录逻辑

private void login(String configFile) { 

    //1、获取SecurityManager工厂,此处使用Ini配置文件初始化SecurityManager 

    Factory<org.apache.shiro.mgt.SecurityManager> factory = new IniSecurityManagerFactory(configFile);   

    //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");   

    subject.login(token); 

}   private void login(String configFile) {

//1、获取SecurityManager工厂,此处使用Ini配置文件初始化SecurityManager

Factory<org.apache.shiro.mgt.SecurityManager> factory =

new IniSecurityManagerFactory(configFile);

//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");

subject.login(token);

}

3.2)测试AllSuccessfulStrategy成功:

@Test 

public void testAllSuccessfulStrategyWithSuccess() { 

    login("classpath:shiro-authenticator-all-success.ini"); 

    Subject subject = SecurityUtils.getSubject();   

    //得到一个身份集合,其包含了Realm验证成功的身份信息

    PrincipalCollection principalCollection = subject.getPrincipals(); 

    Assert.assertEquals(2, principalCollection.asList().size()); 

}  

@Test

public void testAllSuccessfulStrategyWithSuccess() {

login("classpath:shiro-authenticator-all-success.ini");

Subject subject = SecurityUtils.getSubject();

//得到一个身份集合,其包含了Realm验证成功的身份信息

PrincipalCollection principalCollection = subject.getPrincipals();

Assert.assertEquals(2, principalCollection.asList().size());

}

即PrincipalCollection包含了zhang和[email protected]身份信息。

3.3)测试AllSuccessfulStrategy失败:

@Test(expected = UnknownAccountException.class) 

    public void testAllSuccessfulStrategyWithFail() { 

        login("classpath:shiro-authenticator-all-fail.ini"); 

        Subject subject = SecurityUtils.getSubject(); }  

@Test(expected = UnknownAccountException.class)

public void testAllSuccessfulStrategyWithFail() {

login("classpath:shiro-authenticator-all-fail.ini");

Subject subject = SecurityUtils.getSubject();

}

shiro-authenticator-all-fail.ini与shiro-authenticator-all-success.ini不同的配置是使用了securityManager.realms=$myRealm1,$myRealm2;即myRealm验证失败。对于AtLeastOneSuccessfulStrategy和FirstSuccessfulStrategy的区别,请参照testAtLeastOneSuccessfulStrategyWithSuccess和testFirstOneSuccessfulStrategyWithSuccess测试方法。唯一不同点一个是返回所有验证成功的Realm的认证信息;另一个是只返回第一个验证成功的Realm的认证信息。自定义AuthenticationStrategy实现,首先看其API:

//在所有Realm验证之前调用

AuthenticationInfo beforeAllAttempts( 

Collection<? extends Realm> realms, AuthenticationToken token)  

throws AuthenticationException; 

//在每个Realm之前调用

AuthenticationInfo beforeAttempt( 

Realm realm, AuthenticationToken token, AuthenticationInfo aggregate)  

throws AuthenticationException; 

//在每个Realm之后调用

AuthenticationInfo afterAttempt( 

Realm realm, AuthenticationToken token,  

AuthenticationInfo singleRealmInfo, AuthenticationInfo aggregateInfo, Throwable t) 

throws AuthenticationException; 

//在所有Realm之后调用

AuthenticationInfo afterAllAttempts( 

AuthenticationToken token, AuthenticationInfo aggregate)  

throws AuthenticationException;  

//在所有Realm验证之前调用

AuthenticationInfo beforeAllAttempts(

Collection<? extends Realm> realms, AuthenticationToken token)

throws AuthenticationException;

//在每个Realm之前调用

AuthenticationInfo beforeAttempt(

Realm realm, AuthenticationToken token, AuthenticationInfo aggregate)

throws AuthenticationException;

//在每个Realm之后调用

AuthenticationInfo afterAttempt(

Realm realm, AuthenticationToken token,

AuthenticationInfo singleRealmInfo, AuthenticationInfo aggregateInfo, Throwable t)

throws AuthenticationException;

//在所有Realm之后调用

AuthenticationInfo afterAllAttempts(

AuthenticationToken token, AuthenticationInfo aggregate)

throws AuthenticationException;

因为每个AuthenticationStrategy实例都是无状态的,所有每次都通过接口将相应的认证信息传入下一次流程;通过如上接口可以进行如合并/返回第一个验证成功的认证信息。自定义实现时一般继承org.apache.shiro.authc.pam.AbstractAuthenticationStrategy即可,具体可以参考代码com.github.zhangkaitao.shiro.chapter2.authenticator.strategy包下OnlyOneAuthenticatorStrategy 和AtLeastTwoAuthenticatorStrategy。 到此基本的身份验证就搞定了,对于AuthenticationToken 、AuthenticationInfo和Realm的详细使用后续章节再陆续介绍。

[Authenticator(认证器) Authentication(证明) AuthenticationStrategy(验证策略)]

public class LoginLogoutTest {

@Test

public void testHelloworld() {

// 1、获取SecurityManager工厂,此处使用Ini配置文件初始化SecurityManager

Factory<org.apache.shiro.mgt.SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.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、身份验证失败

}

Assert.assertEquals(true, subject.isAuthenticated()); // 断言用户已经登录

// 6、退出

subject.logout();

}

@Test

public void testMyRealm1(){

Factory<org.apache.shiro.mgt.SecurityManager> factory = new IniSecurityManagerFactory(

"classpath:shiro-realm.ini");

org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();

SecurityUtils.setSecurityManager(securityManager);

Subject subject = SecurityUtils.getSubject();

UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");

try {

subject.login(token);

} catch (AuthenticationException e) {

}

Assert.assertEquals(true, subject.isAuthenticated());

}

@Test

public void testCustomMultiRealm(){

Factory<org.apache.shiro.mgt.SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-multi-realm.ini");

org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();

SecurityUtils.setSecurityManager(securityManager);

Subject subject = SecurityUtils.getSubject();

UsernamePasswordToken token = new UsernamePasswordToken("wang", "123");

try {

subject.login(token);

} catch (AuthenticationException e) {

e.printStackTrace();

}

Assert.assertEquals(true, subject.isAuthenticated());

subject.logout();

}

@Test

public void testJDBCRealm(){

Factory<org.apache.shiro.mgt.SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-jdbc-realm.ini");

org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();

SecurityUtils.setSecurityManager(securityManager);

Subject subject = SecurityUtils.getSubject();

UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");

try {

subject.login(token);

} catch (AuthenticationException e) {

e.printStackTrace();

}

Assert.assertEquals(true, subject.isAuthenticated());

subject.logout();

}

@After

public void tearDown() throws Exception {

ThreadContext.unbindSubject();//退出时请解除绑定Subject到线程 否则对下次测试造成影响

}

//**********************

}//end class

public class AuthenticatorTest {

@Test

public void testAllSuccessfulStrategyWithSuccess() {

login("classpath:shiro-authenticator-all-success.ini");

Subject subject = SecurityUtils.getSubject();

//得到一个身份集合,其包含了Realm验证成功的身份信息

PrincipalCollection principalCollection = subject.getPrincipals();

Assert.assertEquals(2, principalCollection.asList().size());

}

@Test(expected = UnknownAccountException.class)

public void testAllSuccessfulStrategyWithFail() {

login("classpath:shiro-authenticator-all-fail.ini");

}

@Test

public void testAtLeastOneSuccessfulStrategyWithSuccess() {

login("classpath:shiro-authenticator-atLeastOne-success.ini");

Subject subject = SecurityUtils.getSubject();

//得到一个身份集合,其包含了Realm验证成功的身份信息

PrincipalCollection principalCollection = subject.getPrincipals();

Assert.assertEquals(2, principalCollection.asList().size());

}

@Test

public void testFirstOneSuccessfulStrategyWithSuccess() {

login("classpath:shiro-authenticator-first-success.ini");

Subject subject = SecurityUtils.getSubject();

//得到一个身份集合,其包含了第一个Realm验证成功的身份信息

PrincipalCollection principalCollection = subject.getPrincipals();

Assert.assertEquals(1, principalCollection.asList().size());

}

@Test

public void testAtLeastTwoStrategyWithSuccess() {

login("classpath:shiro-authenticator-atLeastTwo-success.ini");

Subject subject = SecurityUtils.getSubject();

//得到一个身份集合,因为myRealm1和myRealm4返回的身份一样所以输出时只返回一个

PrincipalCollection principalCollection = subject.getPrincipals();

Assert.assertEquals(1, principalCollection.asList().size());

}

@Test

public void testOnlyOneStrategyWithSuccess() {

login("classpath:shiro-authenticator-onlyone-success.ini");

Subject subject = SecurityUtils.getSubject();

//得到一个身份集合,因为myRealm1和myRealm4返回的身份一样所以输出时只返回一个

PrincipalCollection principalCollection = subject.getPrincipals();

Assert.assertEquals(1, principalCollection.asList().size());

}

private void login(String configFile) {

//1、获取SecurityManager工厂,此处使用Ini配置文件初始化SecurityManager

Factory<org.apache.shiro.mgt.SecurityManager> factory = new IniSecurityManagerFactory(configFile);

//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");

subject.login(token);

}

@After

public void tearDown() throws Exception {

ThreadContext.unbindSubject();//退出时请解除绑定Subject到线程 否则对下次测试造成影响

}

}

Shiro.int

[users]

zhang=123

wang=123

shiro-authenticator-all-success.ini

[main]

#指定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

myRealm1=com.github.zbh.shiro.chapter2.realm.MyRealm1

myRealm2=com.github.zbh.shiro.chapter2.realm.MyRealm2

myRealm3=com.github.zbh.shiro.chapter2.realm.MyRealm3

securityManager.realms=$myRealm1,$myRealm3

shiro-authenticator-all-fail.ini

[main]

#指定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

myRealm1=com.github.zbh.shiro.chapter2.realm.MyRealm1

myRealm2=com.github.zbh.shiro.chapter2.realm.MyRealm2

myRealm3=com.github.zbh.shiro.chapter2.realm.MyRealm3

securityManager.realms=$myRealm1,$myRealm2

shiro-authenticator-atLeastOne-success.ini

[main]

#指定securityManager的authenticator实现

authenticator=org.apache.shiro.authc.pam.ModularRealmAuthenticator

securityManager.authenticator=$authenticator

#指定securityManager.authenticator的authenticationStrategy

allSuccessfulStrategy=org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy

securityManager.authenticator.authenticationStrategy=$allSuccessfulStrategy

myRealm1=com.github.zhangkaitao.shiro.chapter2.realm.MyRealm1

myRealm2=com.github.zhangkaitao.shiro.chapter2.realm.MyRealm2

myRealm3=com.github.zhangkaitao.shiro.chapter2.realm.MyRealm3

securityManager.realms=$myRealm1,$myRealm2,$myRealm3

shiro-authenticator-first-success.ini

[main]

#指定securityManager的authenticator实现

authenticator=org.apache.shiro.authc.pam.ModularRealmAuthenticator

securityManager.authenticator=$authenticator

#指定securityManager.authenticator的authenticationStrategy

allSuccessfulStrategy=org.apache.shiro.authc.pam.FirstSuccessfulStrategy

securityManager.authenticator.authenticationStrategy=$allSuccessfulStrategy

myRealm1=com.github.zhangkaitao.shiro.chapter2.realm.MyRealm1

myRealm2=com.github.zhangkaitao.shiro.chapter2.realm.MyRealm2

myRealm3=com.github.zhangkaitao.shiro.chapter2.realm.MyRealm3

securityManager.realms=$myRealm1,$myRealm2,$myRealm3

shiro-authenticator-atLeastTwo-success.ini

[main]

#指定securityManager的authenticator实现

authenticator=org.apache.shiro.authc.pam.ModularRealmAuthenticator

securityManager.authenticator=$authenticator

#指定securityManager.authenticator的authenticationStrategy

allSuccessfulStrategy=com.github.zhangkaitao.shiro.chapter2.authenticator.strategy.AtLeastTwoAuthenticatorStrategy

securityManager.authenticator.authenticationStrategy=$allSuccessfulStrategy

myRealm1=com.github.zhangkaitao.shiro.chapter2.realm.MyRealm1

myRealm2=com.github.zhangkaitao.shiro.chapter2.realm.MyRealm2

myRealm3=com.github.zhangkaitao.shiro.chapter2.realm.MyRealm3

myRealm4=com.github.zhangkaitao.shiro.chapter2.realm.MyRealm4

securityManager.realms=$myRealm1,$myRealm2,$myRealm4

shiro-authenticator-onlyone-success.ini

[main]

#指定securityManager的authenticator实现

authenticator=org.apache.shiro.authc.pam.ModularRealmAuthenticator

securityManager.authenticator=$authenticator

#指定securityManager.authenticator的authenticationStrategy

allSuccessfulStrategy=com.github.zhangkaitao.shiro.chapter2.authenticator.strategy.OnlyOneAuthenticatorStrategy

securityManager.authenticator.authenticationStrategy=$allSuccessfulStrategy

myRealm1=com.github.zhangkaitao.shiro.chapter2.realm.MyRealm1

myRealm2=com.github.zhangkaitao.shiro.chapter2.realm.MyRealm2

myRealm3=com.github.zhangkaitao.shiro.chapter2.realm.MyRealm3

myRealm4=com.github.zhangkaitao.shiro.chapter2.realm.MyRealm4

securityManager.realms=$myRealm1,$myRealm2

时间: 2024-10-15 13:45:44

第2章身份验证的相关文章

第二章 身份验证——《跟我学Shiro》

身份验证,即在应用中谁能证明他就是他本人.一般提供如他们的身份ID一些标识信息来表明他就是他本人,如提供身份证,用户名/密码来证明. 在shiro中,用户需要提供principals (身份)和credentials(证明)给shiro,从而应用能验证用户身份: principals:身份,即主体的标识属性,可以是任何东西,如用户名.邮箱等,唯一即可.一个主体可以有多个principals,但只有一个Primary principals,一般是用户名/密码/手机号. credentials:证明

第二章 身份验证——跟我学习springmvc shiro mybatis

身份验证,即在应用中谁能证明他就是他本人.一般提供如他们的身份ID一些标识信息来表明他就是他本人,如提供身份证,用户名/密码来证明. 在shiro中,用户需要提供principals (身份)和credentials(证明)给shiro,从而应用能验证用户身份: principals:身份,即主体的标识属性,可以是任何东西,如用户名.邮箱等,唯一即可.一个主体可以有多个principals,但只有一个Primary principals,一般是用户名/密码/手机号. credentials:证明

跟开涛老师学shiro -- 身份验证

身份验证,即在应用中谁能证明他就是他本人.一般提供如他们的身份ID一些标识信息来表明他就是他本人,如提供身份证,用户名/密码来证明. 在shiro中,用户需要提供principals (身份)和credentials(证明)给shiro,从而应用能验证用户身份: principals:身份,即主体的标识属性,可以是任何东西,如用户名.邮箱等,唯一即可.一个主体可以有多个principals,但只有一个Primary principals,一般是用户名/密码/手机号. credentials:证明

shiro身份验证

身份验证,即在应用中谁能证明他就是他本人.一般提供如他们的身份ID一些标识信息来表明他就是他本人,如提供身份证,用户名/密码来证明. 在shiro中,用户需要提供principals (身份)和credentials(证明)给shiro,从而应用能验证用户身份: principals:身份,即主体的标识属性,可以是任何东西,如用户名.邮箱等,唯一即可.一个主体可以有多个principals,但只有一个Primary principals,一般是用户名/密码/手机号. credentials:证明

第17章 使用PHP和MySQL实现身份验证

1.对密码进行加密:sha1(string str,bool raw_output) //将返回一个40个字符的伪随机字符串,若raw_output为true,着得到一个20个字符的二进制字符串数据 //故对应数据库列的宽度不可小于40个字符 2.HTTP基本身份验证: ①编写PHP脚本: ②使用mod_auth_mysql;

ASP.NET Identity 身份验证和基于角色的授权

ASP.NET Identity 身份验证和基于角色的授权 阅读目录 探索身份验证与授权 使用ASP.NET Identity 身份验证 使用角色进行授权 初始化数据,Seeding 数据库 小结 在前一篇文章中,我介绍了ASP.NET Identity 基本API的运用并创建了若干用户账号.那么在本篇文章中,我将继续ASP.NET Identity 之旅,向您展示如何运用ASP.NET Identity 进行身份验证(Authentication)以及联合ASP.NET MVC 基于角色的授权

与身份验证HTTP服务器通信(Communicating with Authenticating HTTP Servers)

本文描述了如何利用CFHTTPAuthentication API与需要身份验证的HTTP服务器通信.它解释了如何找到匹配的验证对象和证书,并将它们应用到HTTP请求,然后存储以供以后使用. 一般来说,如果一个HTTP服务器返回一个401或407响应你的HTTP请求,这表明服务器进行身份验证需要证书.在CFHTTPAuthentication API中,每个证书组存储在CFHTTPAuthentication 对象中.因此,每个不同的身份认证服务器和每个不同用户连接的服务器需要一个单独的CFHT

「深入 Exchange 2013」02 CAS的身份验证方法

在上一篇咱们聊了一下CAS的架构,这一章就来聊聊CAS的验证方法 很多管理员从未纠结过客户端的验证问题,因为Exchange的默认设置在单一环境中完全够用了.当拓扑变得更复杂,环境中开始出现一些其他版本的Exchange时候,就得重视起这个玩意. 选择验证方法的重要性在于,其他的服务器都指望着着CAS角色发送过来的请求符合特定的验证方法.如果用户的邮箱处于Exchange2013的MBX上,那么CAS可以直接将请求代理给HTTP代理终结点,如果处于Exchange2007的或者是Exchange

ASP.NET没有魔法——ASP.NET MVC使用Oauth2.0实现身份验证

原文:ASP.NET没有魔法--ASP.NET MVC使用Oauth2.0实现身份验证 随着软件的不断发展,出现了更多的身份验证使用场景,除了典型的服务器与客户端之间的身份验证外还有,如服务与服务之间的(如微服务架构).服务器与多种客户端的(如PC.移动.Web等),甚至还有需要以服务的形式开放给第三方的,身份验证这一功能已经演化为一个服务,很多大型应用中都有自己的身份验证服务器甚至集群,所以普通的身份验证方式已经不能满足需求. 在.Net领域中也有一些开源的身份验证服务器组件,如Identit