第四章:Shiro的身份认证(Authentication)——深入浅出学Shiro细粒度权限开发框架
Authentication概述
概述
Authentication 是指身份验证的过程——即证明一个用户实际上是不是他们所说的他们是谁。也就是说通过提交用户的身份和凭证给Shiro,以判断它们是否和应用程序预期的相匹配。
基本概念
1:Principals(身份):是Subject 的‘identifying attributes(标识属性)’。比如我们登录提交的用户名。
2:Credentials(凭证):通常是只被Subject 知道的秘密值,它用来作为一种起支持作用的证据,此证据事实上包含着所谓的身份证明。比如我们登录提供的密码
认证的基本步骤
1. 收集Subjects 提交的Principals(身份)和Credentials(凭证);
2. 提交Principals(身份)和Credentials(凭证)进行身份验证;
3. 如果提交成功,则允许访问,否则重新进行身份验证或者阻止访问。
认证样例
使用用户名/密码的样例
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
token.setRememberMe(true);
样例使用UsernamePasswordToken 来支持最常见的用户名/密码的身份验证方法。这是Shiro的org.apache.shiro.authc.AuthenticationToken 的接口,是Shiro 代表提交的Principals(身份)和Credentials(凭证)的身份验证系统所使用的基本接口的一个实现。
提交用户名/密码进行认证
Subject currentUser = SecurityUtils.getSubject();
currentUser.login(token);
处理认证成功和失败
如果认证成功,会没有返回,也没有例外,通过。
如果认证失败,会拋出例外,你可以在程序中捕获并处理,如下示例:
try {
currentUser.login(token);
} catch ( UnknownAccountException uae ) { …
} catch ( IncorrectCredentialsException ice ) { …
} catch (LockedAccountException lae ) { …
} catch (ExcessiveAttemptsException eae ) { …
} … catch your own …
logout(注销)
currentUser.logout();
当你调用logout,任何现有的Session 都将会失效,而且任何身份都将会失去关联(例如,在Web 应用程序中,RememberMe cookie 也将被删除)。在Subject 注销后,该Subject的实例被再次认为是匿名的,当然,除了Web 应用程序。
注意:由于在Web 应用程序记住身份往往是依靠Cookies,然而Cookies 只能在Response 被committed 之前被删除,所以强烈建议在调用subject.logout()后立即将终端用户重定向到一个新的视图或页面。
这样能够保证任何与安全相关的Cookies 都能像预期的一样被删除。这是HTTP cookies 的功能限制,而不是Shiro的。
Remembered和Authenticated
nRemembered(记住我)
一个记住我的Subject 不是匿名的,是有一个已知的身份ID(也就是subject.getPrincipals()是非空的)。但是这个被记住的身份ID 是在之前的session 中被认证的。如果subject.isRemembered()返回true,则Subject 被认为是被记住的。
Remembered(记住我)已认证)
一个已认证的Subject 是指在当前Session 中被成功地验证过了(也就是说,login方法被调用并且没有抛出异常)。如果subject.isAuthenticated()返回true 则认为Subject 已通过验证。
注意他们是互斥的
Remembered 和Authenticated 是互斥的——若其中一个为真则另一个为假,反之亦然
认证顺序
Step 1:应用程序代码调用Subject.login 方法,传递创建好的包含终端用户的Principals(身份)和Credentials(凭证)的AuthenticationToken 实例。
Step 2:Subject实例,通常是DelegatingSubject(或子类)委托应用程序的SecurityManager通过调用securityManager.login(token)开始真正的验证。
nStep3:SubjectManager 接收token 以及简单地委托给内部的Authenticator 实例通过调用authenticator.authenticate(token)。这通常是一个ModularRealmAuthenticator 实例,支持在身份验证中协调一个或多个Realm 实例。
Step 4:如果应用程序中配置了一个以上的Realm,ModularRealmAuthenticator 实例将利用配置好的AuthenticationStrategy 来启动Multi-Realm 认证尝试。在Realms 被身份验证调用之前,期间和以后,AuthenticationStrategy 被调用使其能够对每个Realm 的结果作出反应。
Step 5:每个配置的Realm 用来帮助看它是否支持提交的AuthenticationToken。如果支持,那么支持Realm 的getAuthenticationInfo 方法将会伴随着提交的token 被调用。getAuthenticationInfo 方法有效地代表一个特定Realm 的单一的身份验证尝试。
初识自定义 Realm
这里先来个例子,认识一下:
public class MyRealm extends AuthorizingRealm{
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String userName = (String) getAvailablePrincipal(principals);
//通过用户名去获得用户的所有资源,并把资源存入info中
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
Set<String> s = new HashSet<String>();
s.add("p1"); s.add("p2"); info.setStringPermissions(s);
Set<String> r = new HashSet<String>();
r.add("r1"); r.add("r2"); info.setRoles(r);
return info;}
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
//token中储存着输入的用户名和密码
UsernamePasswordToken upToken = (UsernamePasswordToken)token;
String username = upToken.getUsername();
String password = String.valueOf(upToken.getPassword());
//通常是与数据库中用户名和密码进行比对,这里就省略了
//比对成功则返回info,比对失败则抛出对应信息的异常AuthenticationException
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, password .toCharArray(),getName());
return info; }}
配置多个Realm
上面的例子可以作为第一个Realm
再复制一份,定义为MyRealm2,在返回user前添加抛出一个例外,表示认真没有通过,如下:
if(username.equals("javass")){
throw new AuthenticationException("MyRealm2 认证失败");
}
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username, password .toCharArray(),getName());
在配置文件里面添加Realm的定义
myRealm1=cn.javass.hello.MyRealm
myRealm2=cn.javass.hello.MyRealm2
由于有多个realm,一般就需要配置AuthenticationStrategy了,而AuthenticationStrategy是跟Authenticator(认证器)相关的。
最后把Authenticator设置给securityManagerategy
authenticator = org.apache.shiro.authc.pam.ModularRealmAuthenticator
authcStrategy = org.apache.shiro.authc.pam.AllSuccessfulStrategy
authenticator.authenticationStrategy = $authcStrategy
authenticator.realms=$myRealm2,$myRealm1
当然,你可以扩展并实现自己的Authenticator,一般没有必要
后把Authenticator设置给securityManager
securityManager.authenticator = $authenticator
关于AuthenticationStrategy的配置,有三种:
AtLeastOneSuccessfulStrategy :如果一个(或更多)Realm 验证成功,则整体的尝试被认为是成功的。如果没有一个验证成功,则整体尝试失败。
FirstSuccessfulStrategy 只有第一个成功地验证的Realm 返回的信息将被使用。所有进一步的Realm 将被忽略。如果没有一个验证成功,则整体尝试失败
AllSucessfulStrategy 为了整体的尝试成功,所有配置的Realm 必须验证成功。如果没有一个验证成功,则整体尝试失败。
ModularRealmAuthenticator 默认的是AtLeastOneSuccessfulStrategy
n自定义自己的AuthenticationStrategy,通常是扩展自AbstractAuthenticationStrategy,示例如下:
public class MyAuthenticationStrategy extends AbstractAuthenticationStrategy{
public AuthenticationInfo afterAttempt(Realm realm, AuthenticationToken token, AuthenticationInfo singleRealmInfo, AuthenticationInfo aggregateInfo, Throwable t) throws AuthenticationException {
if(realm.getName().equals("myRealm2")){
if(singleRealmInfo==null || singleRealmInfo.getPrincipals()==null){
throw new AuthenticationException("主战认证未通过");
}
}
return super.afterAttempt(realm, token, singleRealmInfo, aggregateInfo, t);
}
}
至于具体覆盖扩展什么方法,需要根据你具体的策略来定。
多个Realm的验证顺序
概述
非常重要的一点是:ModularRealmAuthenticator 将与Realm 实例以迭代的顺序进行交互。
在SecurityManager 中已经配置好了ModularRealmAuthenticator 对Realm实例的访问。当执行一个认证尝试时,它将会遍历该集合,并对每一个支持提交AuthenticationToken 的Realm 调用Realm 的getAuthenticationInfo 方法
隐式排列
当你配置多个realm的时候,处理的顺序默认就是你配置的顺序。
这种情况通常就是只定义了realm,而没有配置securityManager的realms
n显示排列
也就是显示的配置securityManager.realms,那么执行的顺序就是你配置该值的realm的顺序。
通常更推荐显示排列。
- 专知内容生产基石-数据爬取采集利器WebCollector 介绍
- python实现字符串模糊匹配
- 动态规划之01背包详解【解题报告】
- hihoCoder #1078 : 线段树的区间修改(线段树区间更新板子题)
- HDU 2546 饭卡(01背包裸题)
- 漫谈文件系统
- AI知识搜索利器:基于ElasticSearch构建专知实时高性能搜索系统
- 【深度干货】专知主题链路知识推荐#5-机器学习中似懂非懂的马尔科夫链蒙特卡洛采样(MCMC)入门教程01
- hihoCoder #1043 : 完全背包(板子题)
- 【深度干货】专知主题链路知识推荐#7-机器学习中似懂非懂的马尔科夫链蒙特卡洛采样(MCMC)入门教程02
- hihoCoder #1038 : 01背包(板子题)
- 最小二乘法多项式曲线拟合原理与实现
- HDU 1166 敌兵布阵(线段树单点更新,板子题)
- 一文看懂ovirt的supervdsmd服务
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- php和C#的yield迭代器实现方法对比分析
- Python字符串函数strip()原理及用法详解
- python中取绝对值简单方法总结
- Linux中将txt导入到mysql的办法教程
- 深入了解NumPy 高级索引
- CentOS7设置jar应用程序开机启动的办法
- linux系统 java环境变量的配置办法
- 在Linux中怎么轻松删除源安装的软件包
- Linux中selinux基础配置教程详解
- 怎么禁用 Ubuntu 服务器中终端欢迎消息中的广告
- Laravel5.1 框架响应基本用法实例分析
- 在Linux中怎么一次重命名多个文件详解
- python调用私有属性的方法总结
- PHP+MySQL实现在线测试答题实例
- Python异常处理机制结构实例解析