Apache Shiro 认证--官网

Apache Shiro 认证

ShiroFeatures_Authentication.png

身份验证,即证明用户证明确实是他们自己。为了让用户证明自己的身份,需要他们提供一些身份信息以及系统理解和信任的身份证明。这是通过向Shiro提交用户的principalscredentials,以查看它们是否与应用程序的期望相匹配来完成的

  • Principals:是用户的身份标识,可以是姓名,社工号,但最好是唯一表示用户的某个东西,如邮箱地址等等,这种能唯一标识用户的Principal称为Primary Principal
  • Credentials:一个只有用户才知道或拥有的安全凭据,常见的就是密码,指纹等等

最常见的Principal/Credential就是用户名和密码了,只有用户提交密码在应用程序比对通过后,才能认证成功

认证中的Subject

Subject进行身份验证的过程可以有效地分为三个不同的步骤:

  1. 收集Subject中的principalscredentials
  2. 提交这些principalscredentials进行认证
  3. 如果认证通过,则允许访问,否则重新进行认证或者不允许访问

下面详细介绍这些步骤

步骤1:将principals和credentials表示为AuthenticationToken

//Example using most common scenario of username/password pair:
UsernamePasswordToken token = new UsernamePasswordToken(username, password);

//"Remember Me" built-in: 
token.setRememberMe(true);

这里我们使用的是UsernamePasswordToken,这是最常用的通过用户名密码认证的方法,UsernamePasswordToken实现了Shiro的org.apache.shiro.authc.AuthenticationToken接口,这个接口是Shiro专门用来表示用户提交的principalscredentials

需要注意的是,Shiro并不关心你如何获取这些用户提交的信息,不管是从HTML表单提交还是其他方式,Shiro并不关心,这样处理是为了通过AuthenticationToken这个概念,把从终端用户收集信息这个过程与认证解耦

你可以按照自己的喜好来构造和表示AuthenticationToken,它是与协议无关的

这个例子的最后一行代码展示了如何通过Shiro执行“记住我”服务

步骤2:提交principals和credentials

在收集principalscredentials并将其表示为AuthenticationToken实例之后,我们需要将这个Token提交给Shiro以执行认证

Subject currentUser = SecurityUtils.getSubject();

currentUser.login(token);

首先我们先从Shiro中获取一个Subject,然后调用login方法,传入AuthenticationToken的实例,即完成了一次认证请求

步骤3:处理认证结果

如果认证通过,那么login方法不会有任何回应,现在这个Subject就是已经通过认证了的,应用程序的线程可以继续往下执行,并且之后调用SecurityUtils.getSubject()方法都会返回这个已经认证过的Subject,任何调用subject.isAuthenticated()方法的返回值都是true

如果认证失败,login方法将会抛出一个运行时的AuthenticationException或其子类的异常,通过抛出异常的类型可以判断处认证失败的原因

try {
    currentUser.login(token);
} catch ( UnknownAccountException uae ) { ...
} catch ( IncorrectCredentialsException ice ) { ...
} catch ( LockedAccountException lae ) { ...
} catch ( ExcessiveAttemptsException eae ) { ...
} ... catch your own ...
} catch ( AuthenticationException ae ) {
    //unexpected error?
}

//No problems, continue on as expected...

如果这些已经存在的异常类型不能满足你的需求,可以自定义一个AuthenticationException来表示一个特定的登录失败的场景

如果你的程序需要给用户展示一个提示信息,告诉用户登录失败,最好是使用同一个提示信息,如“用户名或密码错误”,这样可以避免给想要攻击你的应用程序的黑客提供有价值的信息

已记住与已认证

正如上面的例子,Shiro支持“记住我”的概念,注意,在Shiro中,已记住和已认证有着非常明确的区别

  • 已记住:一个已记住的Subject是一个已知身份的,即调用subject.getPrincipals()会返回一个非空值,但这个身份是从来源于之前的会话中的认证,只有当调用subject.isRemembered()方法返回true时,这个Subject才是Remembered

  • Authenticated:一个Authenticated Subject是一个在当前会话中通过了认证的Subject,即调用了login()方法而没有抛出异常,只有当调用subject.isAuthenticated()方法返回true时,这个Subject才是Authenticated

一个Subject的Remembered状态和Authenticated状态是互斥的

为什么有这个区别

“认证”这个术语有很强的证明的含义,即保证Subject是它所声明的用户。当一个用户仅仅是已记住状态时,只能说明这个用户很大可能是真正的用户,但不一定是。而一旦认证通过,也就不再认为这个用户是已记住的了,因为已经在当前会话中验证通过了

所以当执行一些安全性不高的操作时(如获取用户自定义的设置),只要Subject是已记住的状态即可,但是,如果涉及到一些比较敏感的操作(如查看账户余额),则需要Subject是已认证的状态

举个例子

就像你登录到淘宝,添加了一些东西到你的购物车里面,这时候你临时有点事离开了,但是你并没有登出系统。

几天后你再一次进入淘宝,此时淘宝“记住了”你是谁,会给你个性化推荐一些书籍,此时对于淘宝来说,subject.isRemembered()就是true

如果你这时候想要修改你的银行卡或者修改身份认证信息,这时候“已记住”的状态就是不够的了,因为它不能保证你是你,可能是其他的人在使用你的设备。

所以在执行敏感操作的时候,淘宝会要求你登录,以确保你的真实性,在你登录之后,subject.isAuthenticated()就是true

登出

Subject完成了所有和应用的交互后,可以通过subject.logout()方法来使得认证信息失效

currentUser.logout(); //removes all identifying information and invalidates their session too.

当调用logout后,当前的Session和身份将会失效,在Web应用中,RememberMecookie也会被删除

当登出后,执行logout方法的Subject实例将会变成匿名的,如果需要,可以再次用于登录,Web应用程序除外

因为web应用程序中“记住我”的功能通常是通过cookie来持久化的,而cookie的删除只能在响应之后(详情可查后端如何删除Cookie),因此强烈建议在调用subject.logout()之后立即将终端用户重定向到一个新的视图或页面。这保证了任何与安全相关的cookie都会按照预期被删除。这是HTTP cookie功能的限制,而不是Shiro的限制

认证的流程

现在来详细介绍Shiro是如何进行认证的。下面的架构图中,与认证相关的组件都进行了高亮显示,每一个序号代表认证过程中的每一步

ShiroAuthenticationSequence.png

第一步:应用程序代码调用Subject.login方法,并传入构造好的AuthenticationToken实例,代表终端用户的principalscredentials

第二步Subject实例(通常是DelegatingSubject或其子类)委托应用程序的SecurityManager,调用SecuirtyManager.login()方法进行真正的认证工作

第三步SecurityManager接收token,将其交给它内部的Authenticator实例,调用authenticator.authenticate(token)方法,通常这个Authenticator实例是ModularRealmAuthenticator的实例,它支持在认证期间协调多个Realm的实例共同工作。ModularRealmAuthenticator实质上为Apache Shiro提供了一种PAM风格的范式(其中每个RealmPAM术语中都是一个模块)

第四步:如果在应用中配置了多个Realm,则ModularRealmAuthenticator将会利用配置的AuthenticationStrategy来进行一次multi-Realm的认证。在调用Realm进行身份验证之前、期间和之后,AuthenticationStrategy将会被调用来响应每个Realm的结果

如果只有一个Realm,它将会被直接调用,在单Realm的应用程序中,不需要AnthenticationStrategy

第五步:轮询每一个Realm,检查它是否支持被提交的AuthenticationToken,如果支持,则会调用RealmgetAuthenticationInfo()方法,getAuthenticationInfo方法表示了对单个Realm的认证

Authenticator

如前所述,Shiro的SecurityManager实现默认使用的是ModularRealmAuthenticator实例,ModularRealmAuthenticator支持单Realm应用,也支持多Realm应用

在单Realm应用中,ModularRealmAuthenticator将直接调用这个Realm,而在多Realm中,将会使用AuthenticationStrategy来协调这些Realm工作

如果希望使用自定义的Authenticator,可以做如下配置

[main]
...
authenticator = com.foo.bar.CustomAuthenticator

securityManager.authenticator = $authenticator

但是在实践中,ModularRealmAuthenticator已经可以满足大多数需求了

AuthenticationStrategy

当应用中有多个Realm时,ModularRealmAuthenticator依赖于它内部的AuthenticationStrategy组件来决定一次认证请求是否通过

例如,如果仅仅只有一个Realm认证通过,其他认证失败,那这次认证是成功了还是失败了?是否需要所有的Realm全部认证通过,这次认证才算通过?如果一个Realm认证通过了,是否还需要继续通过其他的Realm进行认证。AuthenticationStrategy根据应用程序的需要做出适当的决策

一个AuthenticationStrategy是一个无状态的组件,它将会在认证中被调用4次(在这4次的交互中,任何必要的状态都会被以方法参数的形式给出):

  1. 在任何Realm被调用之前
  2. 在每个Realm的getAuthenticationInfo方法被调用之前
  3. 在每个Realm的getAuthenticaitonInfo方法被调用之后
  4. 所有的Realm都处理之后

一个AuthenticationStrategy也负责汇总每个认证成功的Realm的结果,并将结果绑定到一个AuthenticationInfo中,最后汇总而成的AuthenticationInfo实例即为Authenticator的返回值,Shiro将会用它的信息作为Subject的数据来源,如principals

Shiro中有3个可用的AuthenticationStrategy的实现:

AuthenticationStrategy类 描述
AtLeastOneSuccessfulStrategy 只要有至少一个Realm认证成功了,则认为这次认证通过了。如果全都失败了,则认证失败
FirstSuccessfulStrategy 只有第一个认证成功的Realm的信息会被使用,之后的所有Realm将会被忽略。如果全部都失败了,则认证失败
AllSuccessfulStrategy 所有的Realm都认证成功,这次认证才算成功,只要有一个认证失败的,都会使得这次认证失败

ModularRealmAuthenticator默认使用的是AtLeastOneSuccessfulStrategy,这符合大多数的需求,当然你也可以配置其他策略

[main]
...
authcStrategy = org.apache.shiro.authc.pam.FirstSuccessfulStrategy

securityManager.authenticator.authenticationStrategy = $authcStrategy

...

如果想要自定义AuthenticationStrategy,可以通过继承org.apache.shiro.authc.pam.AbstractAuthenticationStrategy类,该类实现了汇总每个Realm返回的AuthenticationInfo的功能

Realm 认证的顺序

注意,ModularRealmAuthenticator与Realm交互是通过迭代的方式,当进行认证时,ModularRealmAuthenticator将会迭代访问每个Realm,调用他们getAuthenticationInfo()方法,传入AuthenticationToken参数

隐式声明顺序

在INI配置中,Realm的顺序时按照它们在INI文件中出现的顺序来的

blahRealm = com.company.blah.Realm
...
fooRealm = com.company.foo.Realm
...
barRealm = com.company.another.Realm

如上面的配置,ModularRealmAuthenticator在迭代Realm时,顺序是blahRealmfooRealmbarRealm

也可以通过下面的方式,来达到同样的效果

securityManager.realms = $blahRealm, $fooRealm, $barRealm

这种隐式声明的方式,你可以不需要显式的指定securityManagerrealms的属性

显示声明顺序

如果你想显式声明这个realm的顺序,而不管它们在shiro.ini中出现的顺序,则可以通过设置securityManagerrealms属性来完成

blahRealm = com.company.blah.Realm
...
fooRealm = com.company.foo.Realm
...
barRealm = com.company.another.Realm
securityManager.realms = $fooRealm, $barRealm, $blahRealm
...

这样,ModularAuthenticationStrategy的迭代顺序就是fooRealmbarRealmblahRealm

如果采用显式声明SecurityManager.realms属性值的方式,则只有被设置的realm会被使用,比如,如果在INI文件中定义了5个realm,但是你只使用了其中3个设置realms属性值,那么只有这3个会生效。这和隐式声明是不一样的

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容

  • 前言 上一章主要对Shiro功能,运行原理,架构设计进行了介绍,这一章我们主要学习Shiro的身份验证。本章的代码...
    卑微幻想家阅读 2,051评论 0 12
  • Apache Shiro Apache Shiro 是一个强大而灵活的开源安全框架,它干净利落地处理身份认证,授权...
    罗志贇阅读 3,214评论 1 49
  • 认证(Authentication)是校验用户身份的过程,典型的三步操作是: 收集用户身份信息(principal...
    又语阅读 234评论 0 0
  • 1 Shiro 架构 (Shiro外部来看) • 从外部来看Shiro ,即从应用程序角度的来观察如何使用 Shi...
    21号新秀_邓肯阅读 544评论 0 0
  • 前言 这几天因为项目要用到鉴权,很久以前就听说过shiro了,但是之前一直要求用的是Spring Security...
    Martain阅读 1,313评论 0 3