对Shiro的使用,一般都会涉及到自定义验证身份的问题。那么就需要实现自己的 AuthorizingRealm
,Authorizing
是授权的意思, realm
有领域的意思,合起来大概就是自定义实现授权的地方了。
基本的使用教程本文就不讲了,网上有很大。我主要记下自己在集成redis和jwt过程中碰到的几个问题。
一、与redis集成后,redis没有生效的问题。
我们首先要明白redis
与shiro
集成的目的是什么。
当然是管理session
啊!使用redis管理session能够方便的实现session集群,并且在服务重启(或服务器重启)时不会丢失session,而且关键使用它来管理session比tomcat强太多了啊。
session的管理可以归为一种Cache
,我们在shiro的配置中就要注意这点了。
关键代码,在ShiroConfig
中
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//设置realm
securityManager.setRealm(myShiroRealm());
securityManager.setCacheManager(cacheManager()); //这里放redis的实现
securityManager.setSessionManager(sessionManager());
return securityManager;
}
/**
* cacheManager 缓存 redis实现
* 使用的是shiro-redis开源插件
*
* @return
*/
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
return redisCacheManager;
}
/**
* 配置shiro redisManager
* 使用的是shiro-redis开源插件
*
* @return
*/
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
// redisManager.setTimeout(1800);
// redisManager.setExpire(1800);// 配置缓存过期时间
// redisManager.setTimeout(0);
// redisManager.setPassword(password);
return redisManager;
}
/**
* Session Manager
* 使用的是shiro-redis开源插件
*/
@Bean
public DefaultWebSessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setSessionDAO(redisSessionDAO());
return sessionManager;
}
/**
* RedisSessionDAO shiro sessionDao层的实现 通过redis
* 使用的是shiro-redis开源插件
*/
@Bean
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
// 设置redis缓存过期时间
redisSessionDAO.setExpire(86400);
redisSessionDAO.setRedisManager(redisManager());
return redisSessionDAO;
}
我们使用Redis就是为了管理它的session的,这里有shiro-redis
的开源插件可以使用。
设置redis的过期时间请注意shiro-redis
的版本,最新版3.2.1是在RedisSessionDAO
中设置过期时间的,老一些的版本(我不清楚具体什么版本开始改变的)是在RedisManager
中设置的。
二、与JWT的集成
如果不用考虑App端调用服务器端的接口问题,可以忽视这个,但如果存在APP端跨域调用接口,并且需要shiro来管理认证时,那么使用Jwt就是个不错的选择了。
在App端使用session是比较麻烦的,而且各种终端的实现方式不一样,有H5的,有原生(Android、ios)的,都是要先拿到服务器端返回的sessionID,再把它塞进其它的请求headers中,安全问题不谈。
由于使用session比较麻烦,那么我只能考虑使用其它的token来解决身份验证的问题子,而JWT就是专门来干这个事的。JWT本身就可以替代session来做身份认证,但这里我已经用了shiro和redis,所以我仅用JWT来做一个加密令牌的工作。
这里有一套已经集成好的项目 Shiro基于SpringBoot +JWT搭建简单的restful服务。
基本可以直接拿来即用了。
我讲这个,主要是因为这个项目中没有对密码的存储进行加盐加密处理,仅仅是对用户名和密码进行加密返回了token,在实际的项目中我们通常会对密码进行加密的,所以实现起来就和这个项目中有所不同,我仅截取一些与这个项目中的实现不同的代码。
首先是登录的地方:
User user = userService.selectByField("username", loginRbo.getUsername());
SimpleHash encryptPwd = new SimpleHash("MD5", loginRbo.getPassword(), ByteSource.Util.bytes(user.getSalt()));
if (!encryptPwd.toHex().equals(user.getPassword())) {
return authorityFailed("用户名或密码不正确!");
}
Subject subject = SecurityUtils.getSubject();
String token = JWTUtil.sign(user.getId(), user.getPassword());
JWTToken jwtToken = new JWTToken(token);
try {
subject.login(jwtToken);
} catch (AuthenticationException e) {
System.out.println(e.getMessage());
return authorityFailed("用户名或密码不正确!");
}
我在这里对密码进行了MD5加盐加密,然后因为我的项目中是用redis来管理用户登录的,所以我需要用到subject.login()
,这个是与redis集成的关键操作。
注意这上面的代码中没有用username+password的组合加密,我用了userId+password,这个只是换了个字段,不过需要把对应的取值也换成userId。
有一个非常值得注意的地方,认证的匹配方式。
我因为已经对密码进行过加密,并且已经实现了除JWT之外的所有代码,所以我的ShiroConfig中关于Realm的配置是这样的:
/**
* 身份认证realm
*
* @return
*/
@Bean
public MyShiroRealm myShiroRealm() {
MyShiroRealm myShiroRealm = new MyShiroRealm();
myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return myShiroRealm;
}
这里我对MyshiroRealm设置的认证匹配方式是
/**
* 凭证匹配器
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("MD5"); // 散列算法:这里使用MD5算法;
hashedCredentialsMatcher.setHashIterations(1); // 散列的次数,比如散列两次,相当于 md5(md5(""));
return hashedCredentialsMatcher;
}
但由于使用了JWT的加密后,匹配方式已经不是一个MD5的散列算法了。我如果不改这部分代码就会报一个。 java.lang.IllegalArgumentException: Odd number of characters.
反正就是他匹配不上了。如果有碰到这个问题,多半从匹配算法去找原因。
所以简单的解决方法就是不用这种匹配方式,直接用最简单的匹配方式,也就是不用它进行加、解密了。
那么直接删掉这
myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
就可以了。有以上两个问题解决不了的,可以私密我。