前言
整个Shiro专题中,这个部分是最早需要开始看的,主要先了解ShiroConfig都有哪些东西;由于这个项目使用的redis依赖是org.crazycake的shiro-redis,与我后面所用的不同,所以该部分只是简单的梳理了一下。
PS:这里并没有使用到jwt,只是单纯的shiro+redis。
因为之前的理解,全都作为注释写在了代码上,所以该篇的介绍主要都在代码中。整体来说,该部分内容并不难,如果感兴趣的大佬可以自行往下深入了解,如果多少还有点迷糊的小伙伴,消化消化继续往专题往下看😉。
1、Maven依赖
<!-- Shiro 核心依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<!-- Shiro-redis插件 -->
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.1.0</version>
</dependency>
2、ShiroConfig
2.1 开启注解支持
/**
* 开启Shiro-aop注解支持
* 使用代理方式所以需要开启代码支持
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* 开启Shiro注解支持(例如@RequiresRoles()和@RequiresPermissions())
* shiro的注解需要借助Spring的AOP来实现
* 【ps:不知道为什么,这里没有加入该段代码,依然可以使用Shiro的注解】
*/
@Bean
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
2.2 Shiro基础配置shiroFilterFactory
/**
* Shiro基础配置
* 配置一个Shiro的过滤器bean,这个bean将配置Shiro相关的一个规则的拦截
* 如什么样的请求可以访问,什么样的请求不可以访问等等
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactory(SecurityManager securityManager){
// 创建Shiro的拦截的拦截器 ,用于拦截我们的用户请求
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 与这个一样 shiroFilterFactoryBean.setSecurityManager(securityManager());
// 设置Shiro的安全管理,设置管理的同时也会指定某个Realm 用来完成我们权限分配
shiroFilterFactoryBean.setSecurityManager(securityManager);
//定义一个Map集合,这个Map集合中存放的数据全部都是规则,用于设置通知Shiro什么样的请求可以访问,什么样的请求不可以访问
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// 注意过滤器配置顺序不能颠倒,配置过滤:不会被拦截的链接
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/userLogin/**", "anon");
// 表示所有的请求路径全部都需要被拦截登录,这个必须必须写在Map集合的最后面,这个选项是可选的
// 如果没有指定/** ,出现某个请求不符合上面的拦截规则Shiro将方行这个请求
filterChainDefinitionMap.put("/**", "authc");
// 配置shiro默认登录界面地址,前后端分离中登录界面跳转应由前端路由控制,后台仅返回json数据
/*
用于设置一个登录的请求地址,这个地址可以是一个html或jsp的访问路径,也可以是一个控制器的路径
作用是用于通知Shiro我们可以使用这里路径转向到登录页面,但Shiro判断到我们当前的用户没有登录时就会自动转换到这个路径
要求用户完成成功
简单说,访问的用户 没有登录 或者 token过期 就会跳转到setLoginUrl的路径下
*/
shiroFilterFactoryBean.setLoginUrl("/userLogin/unauth");
/*
// 登录成功后转向页面,由于用户的登录后期需要交给Shiro完成,因此就需要通知Shiro登录成功之后返回到那个位置
shiroFilter.setSuccessUrl("/success");
// 用于指定没有权限的页面,当用户访问某个功能是如果Shiro判断这个用户没有对应的操作权限,那么Shiro就会将请求
// 转向到这个位置,用于提示用户没有操作权限
shiroFilter.setUnauthorizedUrl("/noPermission");
*/
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
2.3 安全管理器securityManager
/**
* 安全管理器
* 它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,
* 并通过它来提供安全管理的各种服务
*/
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 自定义Session管理
securityManager.setSessionManager(sessionManager());
// 自定义Cache实现
securityManager.setCacheManager(cacheManager());
// 自定义Realm验证,这个Realm是最终用于完成我们的认证和授权操作的具体对象
securityManager.setRealm(shiroRealm());
return securityManager;
}
2.4 配置Session管理器sessionManager
/**
* 配置Session管理器
* web应用中一般是用web容器对session进行管理,
* session管理器,创建和管理用户session。通过设置这个管理器,shiro可以在任何环境下使用session。
* 这里session管理器负责管理sessionDAO,sessionDAO负责管理个性化的session数据(例如redis缓存数据)
* 用到 redisSessionDAO()
*/
@Bean
public SessionManager sessionManager() {
ShiroSessionManager shiroSessionManager = new ShiroSessionManager();
shiroSessionManager.setSessionDAO(redisSessionDAO());
return shiroSessionManager;
}
2.5 配置RedisSessionDAO
/**
* 配置RedisSessionDAO
* 通过SessionDao管理session数据,针对个性化的session数据存储需要使用sessionDao。
* DAO大家都用过,数据访问对象,用于会话的CRUD,比如我们想把Session保存到数据库,那么可以实现自己的SessionDAO,通过如JDBC写到数据库;
* 比如想把Session放到Memcached中,可以实现自己的Memcached SessionDAO;另外SessionDAO中可以使用Cache进行缓存,以提高性能;
* 用到 redisManager()和sessionIdGenerator()
* 这里的RedisSessionDAO使用的是shiro-redis开源插件
*/
@Bean
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
redisSessionDAO.setSessionIdGenerator(sessionIdGenerator());
redisSessionDAO.setKeyPrefix(SESSION_KEY);
redisSessionDAO.setExpire(timeout);
return redisSessionDAO;
}
补充1:sessionIdGenerator()
/**
* SessionID生成器
*/
@Bean
public ShiroSessionIdGenerator sessionIdGenerator(){
return new ShiroSessionIdGenerator();
}
这里小小的介绍一下这个sessionID生成器,它的实现类中主要是调用JavaUuidSessionIdGenerator
类中的generateId
方法,然后得到 Serializable对象值后与定义的前缀做字符拼接,最后返回使用的sessionId。
public class ShiroSessionIdGenerator implements SessionIdGenerator {
/**
* 实现SessionId生成
*/
@Override
public Serializable generateId(Session session) {
Serializable sessionId = new JavaUuidSessionIdGenerator().generateId(session);
return String.format("login_token_%s", sessionId);
}
这个generateId
方法到底返回什么?点进去看下源码,原来就是个uuid😂
2.6 配置Redis管理器redisManager
/**
* 配置Redis管理器
* 这里的RedisManager使用的是shiro-redis开源插件
*/
@Bean
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost(host);
redisManager.setPort(port);
redisManager.setTimeout(timeout);
// 我的redis没有设置密码
// redisManager.setPassword(password);
return redisManager;
}
2.7 配置Cache管理器cacheManager
/**
* 配置Cache管理器
* 缓存控制器,来管理如用户、角色、权限等的缓存的
* 用于往Redis存储权限和角色标识
* 这里的RedisCacheManager使用的是shiro-redis开源插件
*/
@Bean
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
redisCacheManager.setKeyPrefix(CACHE_KEY);
// 配置缓存的话要求放在session里面的实体类必须有个id标识
redisCacheManager.setPrincipalIdFieldName("userId");
return redisCacheManager;
}
2.8 身份验证器Realm
/**
* 身份验证器
* 领域,充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。
* 也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息
* 可以有1个或多个Realm,可以认为是安全实体数据源,即用于获取安全实体的;
* 可以是JDBC实现,也可以是LDAP实现,或者内存实现等等;由用户提供;
* 注意:Shiro不知道你的用户/权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的Realm;
* 相当于数据源,通过realm存取认证、授权相关数据。
*/
@Bean
public ShiroRealm shiroRealm() {
ShiroRealm shiroRealm = new ShiroRealm();
shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return shiroRealm;
}
Realm中主要重写doGetAuthenticationInfo
认证和doGetAuthorizationInfo
授权两个方法,具体了解可以看【Shiro】SimpleAuthenticationInfo如何验证password和【Shiro】SimpleAuthorizationInfo如何授权这两篇文章。
2.9 凭证匹配器hashedCredentialsMatcher
/**
* 数据密码加密主要是防止数据在浏览器访问后台服务器之间进行数据传递时被篡改或被截获,因此应该在前端到后台的过程中
* 进行加密,而这里的加密方式是将浏览器中获取后台的明码加密和对数据库中的数据进行加密
* 这就丢失了数据加密的意义 因此不建议在这里进行加密,应该在页面传递时进行加密
* 注:
* 建议浏览器传递数据时就加密数据,数据库中存在的数据也是加密数据,必须保证前端传递的数据
* 和数据主库中存放的数据加密次数以及盐规则都是完全相同的,否则认证失败
*/
/**
* 凭证匹配器
* 将密码校验交给Shiro的SimpleAuthenticationInfo进行处理,在这里做匹配配置
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher shaCredentialsMatcher = new HashedCredentialsMatcher();
// 散列算法:这里使用SHA256算法;
shaCredentialsMatcher.setHashAlgorithmName(SHA256Util.HASH_ALGORITHM_NAME);
// 散列的次数,比如散列两次,相当于 md5(md5(""));
shaCredentialsMatcher.setHashIterations(SHA256Util.HASH_ITERATIONS);
return shaCredentialsMatcher;
}
补充2:SHA256Util工具类
public class SHA256Util {
/** 私有构造器 **/
private SHA256Util(){};
/** 加密算法 **/
public final static String HASH_ALGORITHM_NAME = "SHA-256";
/** 循环次数 **/
public final static int HASH_ITERATIONS = 15;
/** 执行加密-采用SHA256和盐值加密 添加用户的时候才用到**/
public static String sha256(String password, String salt) {
// 1加密方法 2被加密的字符串 3盐值(符串或数字) 4hash次数
return new SimpleHash(HASH_ALGORITHM_NAME, password, salt, HASH_ITERATIONS).toString();
}
}
补充3:SHA256和salt加密在项目中怎么用
1. 生成一个随机的salt值,这个salt值可以是一个字符串或者一个数字。
2. 将用户输入的密码和salt值拼接起来,然后使用SHA256算法对拼接后的字符串进行加密。
3. 将加密后的字符串存储在数据库中,以便以后使用。
4. 当用户再次输入密码时,将用户输入的密码和数据库中存储的salt值拼接起来,
然后使用SHA256算法对拼接后的字符串进行加密,将加密后的字符串与数据库中存储的加密字符串进行比较,
如果两者相同,则表示用户输入的密码正确,反之则表示用户输入的密码错误。