您的足迹:首页 > Apache Shiro系列 >【Apache Shiro】身份验证(二)

【Apache Shiro】身份验证(二)

身份验证,即在应用中谁能证明他就是他本人。一般提供如他们的身份ID一些标识信息来表明他就是他本人,如提供身份证,用户名/密码来证明。

shiro中,用户需要提供principals (身份)credentials(证明)shiro,从而应用能验证用户身份:

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

credentials:证明/凭证,即只有主体知道的安全值,如密码/数字证书等。

最常见的principals和credentials组合就是用户名/密码了。

登陆/退出

Demo

public static void test1(){
//1、获取SecurityManager工厂
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//2、获取SecurityManager实例
SecurityManager securityManager = factory.getInstance();
//3、将SecurityManager实例绑定到SecurityUtils
SecurityUtils.setSecurityManager(securityManager);
//4、获取Subject
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");
try {
// 登陆操作,验证权限
subject.login(token);
             //securityManager.login(subject, token);
System.out.println("ok");
} catch (AuthenticationException e) {
// 身份验证权限异常
e.printStackTrace();
}
}

Shiro.ini

[users]
zhang=123
san=456

运行截图

 

执行流程

1、通过new IniSecurityManager()并且指定一个ini的配置创建一个SecurityManager工厂,用于产生SecurityManager

2、通过SecurityManager工厂获取SecurityManager对象绑定到SecurityUtils(全局配置)

3、通过SecurityUtils获取Subject对象(自动绑定到当前的线程,如果在web环境在请求时需要解除绑定,然后获取身份验证的token,例如:账号/密码)

4、通过subject.login()或者securityManager.login(subject,token)发起登陆操作

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

6、最后有通过subject.logout()或者securityManager.logout(subject)来退出登陆

身份验证流程

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,将按照相应的顺序及策略进行访问。

Realm

Realm:域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。

 

org.apache.shiro.realm.Realm

 
    /**
     * 返回Realm的名字
     */
    String getName();
 
/**
 * 判断Realm是否支持此Token
     */
    boolean supports(AuthenticationToken token);
 
    /**
     * 根据token获取认证信息
     */
    AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;

单个realm

实现org.apache.shiro.realm.Realm接口

com.woo0nise.test.RealmTest1

package com.woo0nise.test;
 
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 RealmTest1 implements Realm {
 
/**
 * 获取realm的名字
 */
public String getName() {
return "RealmTest1";
}

/**
 * 
 */
public boolean supports(AuthenticationToken token) {
// 仅支持UsernamePasswordToken
return token instanceof UsernamePasswordToken;
}
/**
 * 根据token换取身份信息
 */
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("密码错误");
}
return new SimpleAuthenticationInfo(username, password,getName());
}
 
}

Demo测试

public static void test2(){
//1、获取SecurityManager工厂
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-RealmTest.ini");
//2、获取SecurityManager实例
SecurityManager securityManager = factory.getInstance();
//3、将SecurityManager实例绑定到SecurityUtils
SecurityUtils.setSecurityManager(securityManager);
//4、获取Subject
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");
try {
// 登陆操作,验证权限
subject.login(token);
//securityManager.login(subject, token);
System.out.println("ok");
} catch (AuthenticationException e) {
// 身份验证权限异常
e.printStackTrace();
}
subject.logout();
//securityManager.logout(subject);
}

shiro-RealmTest.ini

用于指定用户自定义realm

#声明一个realm
RealmTest1=com.woo0nise.test.RealmTest1
#指定securityManager的realms实现
securityManager.realms=$RealmTest1

运行截图

 

realm配置

#声明一个realm
RealmTest1=com.woo0nise.test.RealmTest1
RealmTest2=com.woo0nise.test.RealmTest2
#指定securityManager的realms实现
securityManager.realms=$RealmTest1,$RealmTest2

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

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

JDBC Realm

Shiro-jdbc-realm.ini

jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
dataSource=com.alibaba.pool.DruidDataSource
dataSource.driverClassName=com.mysql.jdbc.Driver  
dataSource.url=jdbc:mysql://localhost:3306/shiro  
dataSource.username=root  
dataSource.password=root
jdbcRealm.dataSource=$dataSource
securityManager.realms=$jdbcRealm

Demo

/**
 * JDBC-Realm
 * @Title: test3 
 * @Description: TODO
 * @return: void
 */
public static void test3(){
//1、获取SecurityManager工厂
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-jdbc-realm.ini");
//2、获取SecurityManager实例
SecurityManager securityManager = factory.getInstance();
//3、将SecurityManager实例绑定到SecurityUtils
SecurityUtils.setSecurityManager(securityManager);
//4、获取Subject
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");
try {
// 登陆操作,验证权限
subject.login(token);
//securityManager.login(subject, token);
System.out.println("ok");
} catch (AuthenticationException e) {
// 身份验证权限异常
e.printStackTrace();
}
subject.logout();
//securityManager.logout(subject);
}

运行截图

 

AuthenticatorAuthenticationStrategy

前面讲到authenticator主要是用来验证账号

Public AuthenticationInfo authenticate(AuthenticationToken token)throws AuthenticationException;

如果验证账号成功的话返回AuthencationInfo验证信息,该信息中存放的数据有身份及凭证,如果验证失败抛出相对于的异常

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

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

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

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

 

ModularRealmAuthenticator默认使用AtLeastOneSuccessfulStrategy策略。

假设我们有三个realm:

myRealm1 用户名/密码为zhang/123时成功,且返回身份/凭据为zhang@163.com/123,不同的是返回时的身份变了

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

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

 

初始化ini

/**
 * 初始化ini配置文件
 * @Title: init 
 * @Description: TODO
 * @param classpath
 * @return: void
 */
public static void init(String classpath){
Factory<SecurityManager> factory = new IniSecurityManagerFactory(classpath);
// 获取securityManager
SecurityManager securityManager = factory.getInstance();
// 绑定securityUtils
SecurityUtils.setSecurityManager(securityManager);
// 获取subject操作对象
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123");
// 登陆
subject.login(token);
}

AllSuccessfulStrategy:所有的Realm验证成功才算成功,并且返回所有验证成功的认证信息,若果其中一个验证失败的话全部失败

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

Demo

public static void test4(){
// 初始化
init("classpath:shiro-authenticator-all-success.ini");
Subject subject = SecurityUtils.getSubject();
// 身份集合
PrincipalCollection principalcollection = subject.getPrincipals();
int i = principalcollection.asList().size();
principalcollection.asList();
System.out.println(principalcollection);
// Assert.assertEquals(2, principalcollection.asList().size());
}

运行结果

 

AllSuccessfulStrategy验证失败

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
 
myRealm1=com.woo0nise.test.RealmTest1
myRealm2=com.woo0nise.test.RealmTest2
myRealm3=com.woo0nise.test.RealmTest3
securityManager.realms=$myRealm1,myRealm2

Demo

public static void test5(){
// 初始化
init("classpath:shiro-authenticator-all-fail.ini");
}

异常代码信息

Exception in thread "main" org.apache.shiro.authc.AuthenticationException:
 Authentication failed for token submission 
[org.apache.shiro.authc.UsernamePasswordToken - zhang, 
rememberMe=false].  Possible unexpected error? (Typical or expected 
login exceptions should extend from AuthenticationException).
at org.apache.shiro.authc.AbstractAuthenticator.authenticate(AbstractAuthenticator.java:214)
at org.apache.shiro.mgt.AuthenticatingSecurityManager.authenticate(AuthenticatingSecurityManager.java:106)
at org.apache.shiro.mgt.DefaultSecurityManager.login(DefaultSecurityManager.java:270)
at org.apache.shiro.subject.support.DelegatingSubject.login(DelegatingSubject.java:256)
at com.woo0nise.test.Test1.init(Test1.java:122)
at com.woo0nise.test.Test1.test5(Test1.java:149)
at com.woo0nise.test.Test1.main(Test1.java:17)
Caused by: java.lang.ClassCastException: java.lang.String cannot be cast to org.apache.shiro.realm.Realm
at org.apache.shiro.authc.pam.ModularRealmAuthenticator.doMultiRealmAuthentication(ModularRealmAuthenticator.java:208)
at org.apache.shiro.authc.pam.ModularRealmAuthenticator.doAuthenticate(ModularRealmAuthenticator.java:269)
at org.apache.shiro.authc.AbstractAuthenticator.authenticate(AbstractAuthenticator.java:198)
... 6 more

AtLeastOneSuccessfulStrategy测试

shiro-authenticator-atLeastOneSuccessfulStrategy.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
 
myRealm1=com.woo0nise.test.RealmTest1
myRealm2=com.woo0nise.test.RealmTest2
myRealm3=com.woo0nise.test.RealmTest3
securityManager.realms=$myRealm1,$myRealm3

Demo

public static void test6(){ init("classpath:shiro-authenticator-atLeastOneSuccessfulStrategy.ini");
Subject subject = SecurityUtils.getSubject();
System.out.println(subject.getPrincipals());
}

运行截图

 

FirstOneSuccessfulStrategyWithSuccess测试

shiro-authenticator-firstonesuccessfulstrategy.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
 
myRealm1=com.woo0nise.test.RealmTest1
myRealm2=com.woo0nise.test.RealmTest2
myRealm3=com.woo0nise.test.RealmTest3
securityManager.realms=$myRealm1,$myRealm3

Demo

public static void test7(){ init("classpath:shiro-authenticator-firstonesuccessfulstrategy.ini");
Subject subject = SecurityUtils.getSubject();
System.out.println(subject.getPrincipals());
}

运行截图

 

AsLeastOneSuccessfulStategyFirstOneSuccessfulStategyWithSuccess唯一不同点一个是返回所有验证成功的Realm的认证信息;另一个是只返回第一个验证成功的Realm的认证信息。

AuthenticationStrategy接口

//在所有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;

定义实现时一般继承org.apache.shiro.authc.pam.AbstractAuthenticationStrategy即可


本博客所有文章如无特别注明均为原创。作者:0nise复制或转载请以超链接形式注明转自 0nise's Blog ---1931sec TeAm!
原文地址《【Apache Shiro】身份验证(二)

相关推荐

  • blogger

发表评论

路人甲 表情
Ctrl+Enter快速提交

网友评论(2)

顶一下这个方法了,很不错的了。。
代理记账 8个月前 (2017-06-02) 回复
顶一下的呢,嘿嘿。。。
代理记帐 8个月前 (2017-05-27) 回复