Shiro认证思路分析
之前将架构的时候说过,如果需要访问Shiro的安全数据的话,需要用Realm。所以我们需要用Realm获取数据库认证数据,而密码比对是由Shiro帮我们完成的。
- 获取当前的Subject,调用
SecurityUtils.getSubject()
; - 测试当前的用户是否已经被认证,即是否已经登录,调用Subject的
isAuthenticated()
; - 若没有被认证,则把用户名和密码封装为
UsernamePasswordToken
对象;
1)创建一个表单页面
2)把请求提交到SpringMVC的Handler
3)获取用户名和密码 - 执行登陆:调用Subject的
login(AuthenticationToken)
方法 - 自定义Realm的方法,从数据库中获取对应的记录,返回给Shiro
1)实际上需要继承org.apache.shiro.realm.AuthenticatingRealm
类
2)实现doGetAuthenticationInfo(AuthenticationToken)
方法 - 用shiro完成对密码的比对
【登录表单】

【Handler】


【Realm】
Handler中的token会传到Realm的doGetAuthenticationInfo方法中:

实现认证Realm
上面我们说到Handler中的token会传到Realm的doGetAuthenticationInfo
方法中,所以我们可以将AuthenticationToken
转换为UsernamePasswordToken
。


现在有登陆的验证了,我们再写一个登出。

logout表示登出的过滤器。
shiro密码的比对
密码的比对是由shiro完成的,那么shiro是怎么比对密码的呢?
查看源代码可以发现密码的比对是通过通过AuthenticatingRealm的CredentialsMathcer属性进行密码的比对。


密码加密
(1)密码的MD5加密
上面的代码中,密码的比对是通过明文直接比对的,这样显然是不安全的,我们应该对密码加密。如何进行密码的加密呢?也可以通过CredentialsMathcer
完成。
如何把前端传过来的密码加密为MD5?这个时候就需要靠“凭证匹配器”CredentialsMathcer
,我们需要替换当前Realm的CredentialsMathcer
属性,把它替换成HashedCredentialsMathcer
,并设置加密算法即可。

hashIterations可以指定加密的次数。

下面就是把“123456”加密1024次的结果:

此时还需要把数据库获取到的密码也进行MD5加密再传入SimpleAuthenticationInfo
构造器当中:

(2)密码的MD5盐值加密
密码一样的话加密后的结果是一样的话,还是有不安全性的。我们希望即便是两个人的原始密码一样,加密之后的结果也不一样。这个时候可以加点作料:盐值。
实现步骤:
1)在doGetAuthenticationInfo
方法返回值创建SimpleAuthenticationInfo
对象的时候,需要使用SimpleAuthenticationInfo(principal,credentials,credentialsSalt,realName)
构造器;
2)使用ByteSource.Util.byte()
方法来计算盐值;
3)盐值需要唯一,一般使用随机字符串或 user id;
4)使用new SimpleHash(hashAlgorithmName,credentials,salt,hashIteratons)
来计算盐值加密后的密码的值;

多Realm验证
在实际的开发中,我们可能会把不同的数据放在不同的数据库里面(比如mysql里面有,oracle里面也有),mysql里面的加密算法可能是MD5,而oracle的加密算法是RSA,这个时候进行用户验证的话需要同时访问这两个数据库,就需要多个Realm。如果有多个Realm的话还涉及到认证策略的问题。
我们再创建多一个Realm进行测试,这里使用到的加密算法就是SHA1:


然后把这第二个Realm配置到IOC容器中:



shiro认证策略
有多个Realm的情况下,怎么样才算是认证通过呢?这就涉及到认证策略。
认证策略实际上就是AuthenticationStrategy
接口的实现。这个接口有三个默认是实现:
- FirstSuccessfulStrategy:只要有一个Realm验证成功即可,只返回第一个Realm身份验证成功的认证信息,其他的忽略;
- AtLeastOneSuccessfulStrategy:只要有一个Realm验证成功即可,和FirstSuccessfulStrategy不同,将返回所有Realm身份验证成功的认证信息;
- AllSuccessfulStrategy:所有Realm验证成功才算成功,且返回所有Realm身份验证成功的认证信息,如果有一个失败就失败了。
默认用的是FirstSuccessfulStrategy
。如果想要使用其他认证策略,可以像下面这样配置:

把Realm配置给SecurityManager
我们把authenticator里面的Realm配置删掉,直接配置在SecurityManager里面:


然后再次运行发现是没有问题的。为什么这样做没问题呢?为什么要这么改呢?因为在做授权的时候,我们需要SecurityManager去读Realm,所以需要把relms配置到SecurityManager中。
分析源码可以看到实际上即使把Realm配置在SecurityManager,SecurityManager最终也会把Realm传递给authenticator:

网友评论