【Shiro学习之二】身份认证
Apche shiro:1.6.0
一、重要组件
1、安全管理器-securityManager
DefaultSecurityManager:默认安全管理器,用于我们的javaSE安全管理,一般而言用到的少。
DefaultWebSecurityManager:默认web安全管理器,用于我们的web安全管理;一般而言,我们的应用中初始化此安全管理器。
2、Subject:与应用程序交互的用户,比如封装输入的用户名和密码信息,然后Subject委托给安全管理器-securityManager。
对于 Subject 我们一般这么使用:
(1)身份验证(login)
(2)授权(hasRole*/isPermitted*或 checkRole*/checkPermission*)
(3)将相应的数据存储到会话(Session)
(4)切换身份(RunAs)/多线程身份传播
(5)退出
3、Realm:Shiro从Realm 获取安全数据(如用户、 角色、 权限),就是说 SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户与Subject进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色/权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource,即安全数据源。
Shiro提供的Realm清单:
org.apache.shiro.realm.SimpleAccountRealm 使用配置好的账户进行验证
org.apache.shiro.realm.text.TextConfigurationRealm 使用文本文件配置的用户权限进行验证
org.apache.shiro.realm.text.PropertiesRealm 使用.properties文件配置的用户权限进行验证
org.apache.shiro.realm.text.IniRealm 使用ini文件配置的用户权限进行验证
org.apache.shiro.realm.ldap.DefaultLdapRealm 使用Ldap进行用户权限验证
org.apache.shiro.realm.jdbc.JdbcRealm使用数据库方式进行用户权限验证
org.apache.shiro.realm.activedirectory.ActiveDirectoryRealm 使用active directory LDAP进行身份验证
org.apache.shiro.realm.AuthorizingRealm 授权类,获取权限角色类信息
org.apache.shiro.realm.AuthenticatingRealm 身份验证类,获取身份信息
org.apache.shiro.realm.CachingRealm带有缓存实现的realm
如果要自定义Realm,继承AuthorizingRealm类就可以了。
以JdbcRealm为例:
4、身份验证器-Authenticator
验证用户帐号,如果验证成功,将返回 AuthenticationInfo 验证信息;此信息中包含了身份及凭证;如果验证失败将抛出相应的 AuthenticationException 实现。
具体的调用realm进行校验是在ModularRealmAuthenticator里。
5、AuthenticationStrategy
对于多个realm时采用AuthenticationStrategy策略进行校验,AuthenticationStrategy接口有三个实现如下,shiro默认AtLeastOneSuccessfulStrategy。
FirstSuccessfulStrategy:只要有一个 Realm 验证成功即可,只返回第一个 Realm 身份验证成功的认证信息,其他的忽略;
AtLeastOneSuccessfulStrategy: 只要有一个 Realm 验证成功即可, 和 FirstSuccessfulStrategy不同,返回所有 Realm 身份验证成功的认证信息;
AllSuccessfulStrategy:所有 Realm 验证成功才算成功,且返回所有 Realm 身份验证成功的认证信息,如果有一个失败就失败了。
以AtLeastOneSuccessfulStrategy为例展示继承关系:
6、AuthenticationToken
AuthenticationToken用于收集用户提交的身份(如用户名)及凭据(如密码);
扩展接口RememberMeAuthenticationToken:提供了"boolean isRememberMe()"现“记住我”的功能;
扩展接口HostAuthenticationToken:提供了"String getHost()"方法用于获取用户“主机”的功能;
UsernamePasswordToken:简单的用户名和密码;
CasToken:单点登录相关令牌;
BearerToken:包含一个承载令牌或API密钥,通常通过HTTP报头接收。
7、PrincipalCollection
我们可以在Shiro中同时配置多个Realm,所以呢身份信息可能就有多个; 因此其提供了 PrincipalCollection 用于聚合这些身份信息.
public interface PrincipalCollection extends Iterable, Serializable { Object getPrimaryPrincipal(); //得到主要的身份 <T> T oneByType(Class<T> type); //根据身份类型获取第一个 <T> Collection<T> byType(Class<T> type); //根据身份类型获取一组 List asList(); //转换为 List Set asSet(); //转换为 Set Collection fromRealm(String realmName); //根据 Realm 名字获取 Set<String> getRealmNames(); //获取所有身份验证通过的 Realm 名字 boolean isEmpty(); //判断是否为空 }
因为PrincipalCollection 聚合了多个, 此处最需要注意的是 getPrimaryPrincipal, 如果只有一 个 Principal 那么直接返回即可,如果有多个 Principal,则返回第一个(因为内部使用 Map 存储,所以可以认为是返回任意一个);oneByType / byType 根据凭据的类型返回相应的 Principal;fromRealm 根据 Realm 名字(每个 Principal 都与一个 Realm 关联)获取相应的 Principal。
目前Shiro只提供了一个实现SimplePrincipalCollection, 还记得之前的AuthenticationStrategy 实现嘛,用于在多 Realm 时判断是否满足条件的,在大多数实现中(继承了 AbstractAuthenticationStrategy ) afterAttempt 方 法 会 进 行 AuthenticationInfo ( 实 现 了 MergableAuthenticationInfo)的 merge,比如 SimpleAuthenticationInfo 会合并多个 Principal 为一个 PrincipalCollection。
8、AuthenticationInfo
AuthenticationInfo 有两个作用:
(1)如果 Realm 是 AuthenticatingRealm 子类,则提供给 AuthenticatingRealm 内部使用的CredentialsMatcher 进行凭据验证;(如果没有继承它需要在自己的 Realm中自己实现验证) ;
(2)提供给 SecurityManager 来创建 Subject(提供身份信息);
MergableAuthenticationInfo 用于提供在多 Realm 时合并 AuthenticationInfo 的功能,主要合并Principal、如果是其他的如 credentialsSalt,会用后边的信息覆盖前边的。 比如HashedCredentialsMatcher 在验证时会判断AuthenticationInfo是否是SaltedAuthenticationInfo子类,来获取盐信息。
Account相当于我们之前的User, SimpleAccount是其一个实现;
在IniRealm、 PropertiesRealm这种静态创建帐号信息的场景中使用,这些Realm直接继承了SimpleAccountRealm,而SimpleAccountRealm 提供了相关的 API 来动态维护 SimpleAccount;即可以通过这些 API来动态增删改查SimpleAccount;动态增删改查角色/权限信。
其他情况一般返回 SimpleAuthenticationInfo 即可
二、身份认证流程
1、代码示例
@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(); }
2、图示流程
(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,将按照相应的顺序及策略进行访问。
3、源码流程分析
Subject::login -->DelegatingSubject::login -->securityManager::login -->DefaultSecurityManager::login(对于javase应用) -->AuthenticatingSecurityManager::authenticate -->AbstractAuthenticator::authenticate -->ModularRealmAuthenticator::doAuthenticate 在这里会获取所有的realm 如果只有一个realm,就调用realm的getAuthenticationInfo进行身份验证; 如果是多个realm,先获取验证策略:ModularRealmAuthenticator::getAuthenticationStrategy -->然后再根据策略调用realm的getAuthenticationInfo方法进行验证
小结:在配置时,要选择正确的安全管理器,并自定义好自己的认证方式Realm,如果是多个Relam,则要设置好相应的验证策略。