使用shiro就会用到缓存,则缓存包括cacheManager、sessionManager,都可以使用redis缓存,但需要注意过期时间的设置,后续会不断完善。
用户认证和授权信息缓存使用CacheManager
shiro主要包括认证和授权两个操作
doGetAuthenticationInfo
doGetAuthorizationInfo
用户进行权限验证时 Shiro会去缓存中找,如果查不到数据,会执行doGetAuthorizationInfo这个方法去查权限,并放入缓存中,若想即时生效需要执行清除shiro缓存的方法
Shiro内部提供了对认证信息和授权信息的缓存,但是shiro默认是关闭认证信息缓存,对于授权信息的缓存shiro默认开启的。一般情况下,使用shiro缓存时,只需要关注授权信息缓存,因为认证信息只是一次验证查询,而授权信息需要在每次认证都会执行(访问量大),且一般情况下授权的数据量大。但是,当用户信息被修改时,我们希望理解看到认证信息也被同步时,需要关注认证信息清空同步问题。
在没有使用缓存的情况下,每发送一次请求都会调用一次doGetAuthorizationInfo方法来进行用户的授权操作,但是我们知道,一个用户具有的权限一般不会频繁的修改,也就是每次授权的内容都是一样的,所以我们希望在用户登录成功的第一次授权成功后将用户的权限保存在缓存中,下一次请求授权的话就直接从缓存中获取,这样效率会更高一些。
下面为认证过程的源码解读,特此记录。
首先要注册用户,用户密码加密后入库,然后该用户登录时进入doGetAuthenticationInfo方法从数据库获取用户信息,最后进入hashedCredentialsMatcher方法进行密码匹对
login首次登录
1进入ShiroSessionManager类getSessionId方法获取token,没有则去cookie中获取
2进入DelegatingSubject类的login方法,执行Subject subject = securityManager.login(this, token);
3进入DefaultSecurityManager类的login方法,通过usernamepasswordtoken进行认证
4进入AuthenticatingSecurityManager类authenticate方法,进行认证
5进入AuthenticatingRealm类getAuthenticationInfo方法,执行AuthenticationInfo info = getCachedAuthenticationInfo(token);获取是否有缓存
6若没有缓存,执行info = doGetAuthenticationInfo(token);获取AuthenticationInfo对象
7进入MyShiroRealm类的doGetAuthenticationInfo方法,去数据库获取对象
8获得AuthenticationInfo对象后,进行密码比对
9进入AuthenticatingRealm类assertCredentialsMatch方法,比对成功后返回---------------------------------5
10进入DefaultSecurityManager类的login方法,返回AuthenticationInfo对象-----------------------3
11进入DelegatingSubject类的login方法,返回Subject对象--------------------------2
12登录成功
login非首次登录
在第5步中,直接通过缓存获得了AuthenticationInfo对象,一直返回到DelegatingSubject类中,一直到登录成功。
访问其他接口通过token令牌访问即可。
在AuthenticatingRealm类getAuthenticationInfo方法中,通过assertCredentialsMatch进行秘密比对,进入assertCredentialsMatch方法,执行doCredentialsMatch(token, info)
进入HashedCredentialsMatcher类doCredentialsMatch方法,执行Object tokenHashedCredentials = hashProvidedCredentials(token, info);和Object accountCredentials = getCredentials(info);分别获取密码比对
注意:
// 报序列化错误
// ByteSource.Util.bytes(user.getCredentialsSalt()),//salt=username+salt
改用
ByteSourceUtils.bytes(user.getCredentialsSalt()),//salt=username+salt
需补充工具类
用户登录时输入用户名和密码(密码为明文)
获取认证信息时从数据库获取的密码为用相同方式加密的密文
此方式为shiroConfig中配置的hashedCredentialsMatcher
密码比对时比对的是通过用户输入的token中的明文密码,通过此方式加密获得的值与数据库中入库的密码密文比较,通过加盐非可逆
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 获取当前登录用户信息
String name = authenticationToken.getPrincipal().toString();
// 根据用户名称去数据库查询用户
User user = demoService.getUserByName(name);
if (user == null) {
//这里返回后会报出对应异常
return null;
} else {
// 这里验证authenticationToken和simpleAuthenticationInfo的信息
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
user,
user.getPassword(),
// 报序列化错误
// ByteSource.Util.bytes(user.getCredentialsSalt()),//salt=username+salt
ByteSourceUtils.bytes(user.getCredentialsSalt()),//salt=username+salt
getName()
);
return simpleAuthenticationInfo;
}
}
@Bean
public MyShiroRealm myShiroRealm() {
MyShiroRealm myShiroRealm = new MyShiroRealm();
myShiroRealm.setCachingEnabled(true);
myShiroRealm.setAuthenticationCachingEnabled(true);
myShiroRealm.setAuthorizationCachingEnabled(true);
myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return myShiroRealm;
}
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher shaCredentialsMatcher = new HashedCredentialsMatcher();
// 散列算法:这里使用SHA256算法;
shaCredentialsMatcher.setHashAlgorithmName("md5");
// 散列的次数,比如散列两次,相当于 md5(md5(""));
shaCredentialsMatcher.setHashIterations(2);
return shaCredentialsMatcher;
}
过期时间:redisManager中设置redis的过期时间、redisSessionDAO中设置过期时间...
redisSessionDAO.setExpire()
redisCacheManager.setExpire()
sessionManager.setGlobalSessionTimeout()
redisManager.setExpire()