1、实现共享Session
1、重新设置 session 及 cookie 去除 httpOnly 浏览器 脚本 都能取到 cookie
最终采用:
package com.daoshu.involved.shared.core.shiro;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;
@Configuration
@Slf4j
public class ShiroConfig {
private static final int redis_expire = 1000 * 60 * 60 * 2;
private static final long session_expire = 1000 * 60 * 60 * 24;
@Value("${spring.redis.host}")
private String redisHost;
@Value("${spring.redis.port}")
private String redisPort;
/**
* 设置过滤规则
*
* @param securityManager
* @return
*/
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
/**
* bean.setLoginUrl("/login") 是在项目启动后,如果没有登录的情况下,会被shiro强制请求的路径,即为/unauth ;
*/
shiroFilterFactoryBean.setLoginUrl("/auth/unauth");
/**
* authc,表示拦截的路径,anon,表示不拦截的路径
* 注意此处使用的是LinkedHashMap,是有顺序的,shiro会按从上到下的顺序匹配验证,匹配了就不再继续验证
* 所以上面的url要苛刻,宽松的url要放在下面,尤其是"/**"要放到最下面,如果放前面的话其后的验证规则就没作用了。
*/
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
/**
* swagger 免验证
*/
filterChainDefinitionMap.put("/swagger-ui.html", "anon");
filterChainDefinitionMap.put("/swagger-resources", "anon");
filterChainDefinitionMap.put("/swagger-resources/**", "anon");
filterChainDefinitionMap.put("/v2/api-docs", "anon");
filterChainDefinitionMap.put("/webjars/springfox-swagger-ui/**", "anon");
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/auth/login", "anon");
filterChainDefinitionMap.put("/**.jpg", "anon");
filterChainDefinitionMap.put("/**.ico", "anon");
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
*
*
* 可以分别采用 单例,及集群
* RedisClusterManager redis 集群管理
* RedisManager redis 集群管理
*
* @return
*/
@Bean
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setDatabase(2);
redisManager.setHost(redisHost + ":" + redisPort);
return redisManager;
}
/**
* 基于Redis实现共享Session
* @return
*/
@Bean
public RedisSessionDAO redisSessionDAO(RedisManager redisManager) {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager);
// Session ID 生成器
redisSessionDAO.setSessionIdGenerator(new JavaUuidSessionIdGenerator());
return redisSessionDAO;
}
@Bean
public SimpleCookie cookie() {
// cookie的name,对应的默认是 JSESSIONID
SimpleCookie cookie = new SimpleCookie("SHAREJSESSIONID");
/**
* 浏览器 脚本 都能取到 cookie
*/
cookie.setHttpOnly(false);
// path为 / 用于多个系统共享JSESSIONID
cookie.setPath("/");
return cookie;
}
@Bean
public RedisCacheManager redisCacheManager(RedisManager redisManager) {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager);
// 设置redis超时
redisCacheManager.setExpire(redis_expire);
redisCacheManager.setPrincipalIdFieldName("userId");
return redisCacheManager;
}
/**
* 凭证匹配器
*
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");
//散列的次数
hashedCredentialsMatcher.setHashIterations(1);
return hashedCredentialsMatcher;
}
/**
* 自定义realm
*
* @return
*/
@Bean
public UserRealm userRealm() {
UserRealm userRealm = new UserRealm();
userRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return userRealm;
}
/**
* 安全管理器
* 注:使用shiro-spring-boot-starter 1.4时,返回类型是SecurityManager会报错,直接引用shiro-spring则不报错
*
* @return
*/
@Bean
public SecurityManager securityManager(RedisSessionDAO redisSessionDAO, RedisCacheManager redisCacheManager) {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
// 设置session超时
sessionManager.setGlobalSessionTimeout(session_expire);
// 删除无效session
sessionManager.setDeleteInvalidSessions(true);
// 设置JSESSIONID
sessionManager.setSessionIdCookie(cookie());
// 设置sessionDAO
sessionManager.setSessionDAO(redisSessionDAO);
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setSessionManager(sessionManager);
securityManager.setCacheManager(redisCacheManager);
securityManager.setRememberMeManager(cookieRememberMeManager());
securityManager.setRealm(userRealm());
return securityManager;
}
/**
* DefaultAdvisorAutoProxyCreator,Spring的一个bean,由Advisor决定对哪些类的方法进行AOP代理
*
* @return
*/
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();
proxyCreator.setProxyTargetClass(true);
return proxyCreator;
}
/**
* 开启shiro aop注解支持.
* 使用代理方式;所以需要开启代码支持;
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
@Bean
public SimpleMappingExceptionResolver resolver() {
SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();
Properties properties = new Properties();
properties.setProperty("org.apache.shiro.authz.UnauthorizedException", "error/403");
exceptionResolver.setExceptionMappings(properties);
return exceptionResolver;
}
@Bean
public SimpleCookie rememberMeCookie() {
log.info("ShiroConfiguration.rememberMeCookie()");
//这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
//<!-- 记住我cookie生效时间 ,单位秒;-->
simpleCookie.setMaxAge(1800);
return simpleCookie;
}
@Bean
public CookieRememberMeManager cookieRememberMeManager() {
log.info("ShiroConfiguration.rememberMeManager()");
CookieRememberMeManager manager = new CookieRememberMeManager();
manager.setCookie(rememberMeCookie());
return manager;
}
}
项目发布到微服务k8s里,发现容器数量为1的时候 能正常登录,而当容器数量调整到多个的时候就会发现登录不了。 经排查是多个容器的时候Session会话没保持,就需要在多个应用的时候共享session会话。
上一篇2020-04-01-Shiro Session集群共享存入Redis中SimpleSession的transient 属性不能序列化已经提到了集成redis实现共享session的坑!! 这里我就不用自己去 RedisManager、SessionDAO了,而是使用shiro-redis 框架。
下面通过实现一个小Demo,来说明如何使用并集成shiro-redis!
一、实现步骤
- pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-all -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.3.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.crazycake/shiro-redis -->
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.2.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
- redis配置
package com.example.demo.conf;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
@Configuration
@PropertySource("classpath:conf/redis.properties")
public class RedisConfig {
@Value("${shiro.redis.host}")
private String host;
@Value("${shiro.redis.timeout}")
private int timeout;
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getTimeout() {
return timeout;
}
public void setTimeout(int timeout) {
this.timeout = timeout;
}
}
- Shiro配置文件
package com.example.demo.conf;
import com.example.demo.auth.PermissionRealm;
import com.example.demo.common.entity.User;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.util.LinkedHashMap;
@Configuration
public class ShiroConfig {
@Bean
public RedisConfig redisConfig(){
return new RedisConfig();
}
@Bean
public RedisManager redisManager(){
RedisManager redisManager = new RedisManager(); // crazycake 实现
redisManager.setHost(redisConfig().getHost());
redisManager.setTimeout(redisConfig().getTimeout());
return redisManager;
}
@Bean
public JavaUuidSessionIdGenerator sessionIdGenerator(){
return new JavaUuidSessionIdGenerator();
}
@Bean
public RedisSessionDAO sessionDAO(){
RedisSessionDAO sessionDAO = new RedisSessionDAO(); // crazycake 实现
sessionDAO.setRedisManager(redisManager());
sessionDAO.setSessionIdGenerator(sessionIdGenerator()); // Session ID 生成器
return sessionDAO;
}
@Bean
public SimpleCookie cookie(){
SimpleCookie cookie = new SimpleCookie("SHAREJSESSIONID"); // cookie的name,对应的默认是 JSESSIONID
cookie.setHttpOnly(true);
cookie.setPath("/"); // path为 / 用于多个系统共享JSESSIONID
return cookie;
}
@Bean
public DefaultWebSessionManager sessionManager(){
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
sessionManager.setGlobalSessionTimeout(redisConfig().getTimeout()); // 设置session超时
sessionManager.setDeleteInvalidSessions(true); // 删除无效session
sessionManager.setSessionIdCookie(cookie()); // 设置JSESSIONID
sessionManager.setSessionDAO(sessionDAO()); // 设置sessionDAO
return sessionManager;
}
/**
* 1\. 配置SecurityManager
* @return
*/
@Bean
public DefaultWebSecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm()); // 设置realm
securityManager.setSessionManager(sessionManager()); // 设置sessionManager
// securityManager.setCacheManager(redisCacheManager()); // 配置缓存的话,退出登录的时候crazycake会报错,要求放在session里面的实体类必须有个id标识
return securityManager;
}
/**
* 2\. 配置缓存
* @return
*/
// @Bean
// public CacheManager cacheManager(){
// EhCacheManager ehCacheManager = new EhCacheManager();
// ehCacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
// return ehCacheManager;
// }
@Bean
public RedisCacheManager redisCacheManager(){
RedisCacheManager cacheManager = new RedisCacheManager(); // crazycake 实现
cacheManager.setRedisManager(redisManager());
return cacheManager;
}
/**
* 3\. 配置Realm
* @return
*/
@Bean
public AuthorizingRealm realm(){
PermissionRealm realm = new PermissionRealm();
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
// 指定加密算法
matcher.setHashAlgorithmName("MD5");
// 指定加密次数
matcher.setHashIterations(10);
// 指定这个就不会报错
matcher.setStoredCredentialsHexEncoded(true);
realm.setCredentialsMatcher(matcher);
return realm;
}
/**
* 4\. 配置LifecycleBeanPostProcessor,可以来自动的调用配置在Spring IOC容器中 Shiro Bean 的生命周期方法
* @return
*/
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
return new LifecycleBeanPostProcessor();
}
/**
* 5\. 启用IOC容器中使用Shiro的注解,但是必须配置第四步才可以使用
* @return
*/
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
return new DefaultAdvisorAutoProxyCreator();
}
/**
* 6\. 配置ShiroFilter
* @return
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(){
LinkedHashMap<String, String> map = new LinkedHashMap<>();
// 静态资源
map.put("/css/**", "anon");
map.put("/js/**", "anon");
// 公共路径
map.put("/login", "anon");
map.put("/register", "anon");
//map.put("/*", "anon");
// 登出,项目中没有/logout路径,因为shiro是过滤器,而SpringMVC是Servlet,Shiro会先执行
map.put("/logout", "logout");
// 授权
map.put("/user/**", "authc,roles[user]");
map.put("/admin/**", "authc,roles[admin]");
// everything else requires authentication:
map.put("/**", "authc");
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
// 配置SecurityManager
factoryBean.setSecurityManager(securityManager());
// 配置权限路径
factoryBean.setFilterChainDefinitionMap(map);
// 配置登录url
factoryBean.setLoginUrl("/");
// 配置无权限路径
factoryBean.setUnauthorizedUrl("/unauthorized");
return factoryBean;
}
/**
* 配置RedisTemplate,充当数据库服务
* @return
*/
@Bean
public RedisTemplate<String,User> redisTemplate(RedisConnectionFactory connectionFactory){
RedisTemplate<String,User> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<User>(User.class));
return redisTemplate;
}
}
- UserServer.java
package com.example.demo.service;
import com.example.demo.common.entity.User;
import java.util.List;
public interface UserService {
void addUser(User user);
User login(User user);
List<User> getUsers();
}
- UserServiceImpl.java
package com.example.demo.service.impl;
import com.example.demo.common.PasswordUtils;
import com.example.demo.common.entity.User;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private RedisTemplate<String, User> redisTemplate;
@Override
public void addUser(User user) {
user.setPassword(PasswordUtils.saltAndMd5(user.getUsername(),user.getPassword())); // 加密
redisTemplate.boundHashOps("users").put(user.getUsername(), user);
}
@Override
public User login(User user) {
user.setPassword(PasswordUtils.saltAndMd5(user.getUsername(),user.getPassword())); // 加密
User u = (User) redisTemplate.boundHashOps("users").get(user.getUsername());
if (u == null || !check(user, u)){
return null;
}
return u;
}
@Override
public List<User> getUsers() {
List<Object> list = redisTemplate.boundHashOps("users").values();
List<User> users = new ArrayList<>();
list.forEach(u->{
users.add((User) u);
});
return users;
}
private boolean check(User a, User b){
if (a.getUsername().equals(b.getUsername()) && a.getPassword().equals(b.getPassword())){
return true;
}
return false;
}
}
- IndexController.java
package com.example.demo.controller;
import com.example.demo.common.entity.User;
import com.example.demo.common.response.BaseResponse;
import com.example.demo.service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
@RestController
public class IndexController {
@Autowired
private UserService userService;
@RequestMapping("/")
public ModelAndView index(){
return new ModelAndView("index");
}
@RequestMapping("/login")
public BaseResponse<String> login(@RequestBody User user){
BaseResponse<String> response = new BaseResponse<>(0,"登陆成功");
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(
user.getUsername(), user.getPassword());
subject.login(token);
response.setData("/home");
return response;
}
@RequestMapping("/register")
public BaseResponse register(@RequestBody User user){
userService.addUser(user);
return new BaseResponse(0,"注册成功");
}
@RequestMapping("/home")
public ModelAndView home(){
ModelAndView mv = new ModelAndView("home");
mv.addObject("users", userService.getUsers());
return mv;
}
}
- applicatin.properties
server.port=8080
spring.redis.host=127.0.0.1
spring.redis.port=6379
- index.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Index</title>
<link th:href="@{css/index.css}" rel="stylesheet" type="text/css">
</head>
<body>
<div class="container">
<div class="main">
<div class="left">
<div class="form-group">
<input type="text" name="username" placeholder="请输入用户名">
</div>
<div class="form-group">
<input type="password" name="password" placeholder="请输入密码">
</div>
<div class="form-group">
<a href="javascript:;" id="login">登录</a>
</div>
<div class="form-group">
<a href="/home">点我!不登录进不去</a>
</div>
</div>
<div class="right">
<div class="form-group">
<input type="text" name="username" placeholder="请输入用户名">
</div>
<div class="form-group">
<input type="password" name="password" placeholder="请输入密码">
</div>
<div class="form-group">
<input type="text" name="show" placeholder="自我介绍">
</div>
<div class="form-group">
<a href="javascript:;" id="register">注册</a>
</div>
</div>
</div>
</div>
<script th:src="@{js/jquery-3.3.1.min.js}"></script>
<script th:src="@{js/index.js}"></script>
</body>
</html>
二、本地测试
通过nginx 启动两个不同端口的jar(8081、8082)
upstream myapp{
server 127.0.0.1:8081 weight=1;
server 127.0.0.1:8082 weight=1;
}
server{
listen 80;
server_name myapp;
location / {
proxy_pass http://myapp;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
项目启动会,访问登录 就会发现cookie里存在SHAREJSESSIONID了 redis里也有对应的SessionId了。
三、生产环境&ShiroConfig配置
很多时候生产环境的redis都是集群化,这里的配置就有一点不同。
下面贴一下我公司项目的ShiroConfig配置,跟上面的demo不是一回事 大体思想还是一致的。
package cn.pconline.pcloud.admin.config;
import cn.pconline.pcloud.admin.service.RoleService;
import cn.pconline.pcloud.admin.service.UserService;
import cn.pconline.pcloud.base.entity.system.Resource;
import cn.pconline.pcloud.base.entity.system.Role;
import cn.pconline.pcloud.base.entity.system.User;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisClusterManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
import java.util.*;
/**
* @Description Shiro配置 支持session集群
* @Author jie.zhao
* @Date 2020/3/31 13:54
*/
@Configuration
public class ShiroConfig {
private static final Logger logger = LoggerFactory.getLogger(ShiroConfig.class);
@Value("${app.domain:/}")
private String domain;
@Value("${spring.redis.cluster.nodes}")
private String redisClusterNodes;
@Autowired
private UserService userService;
@Autowired
private RoleService roleService;
private static final int redis_expire = 1000 * 60 * 60 * 2;
private static final long session_expire = 1000 * 60 * 60 * 2;
/**
* 授权凭证(启动项目时加载)
* 对应 realm.doGetAuthorizationInfo()
*
* @param securityManager
* @return
*/
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean getShiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
shiroFilter.setLoginUrl(domain + "admin/login");
shiroFilter.setSuccessUrl(domain + "admin/index");
// 没权限时跳转至该页面
shiroFilter.setUnauthorizedUrl(domain + "admin/permission/died");
// anon、authc、user对应realm.doGetAuthenticationInfo(..)登录认证
// perms、roles、ssl、est、port对应realm.doGetAuthorizationInfo(..)授权认证
// 设置过滤器链接集合 注意:Map要支持顺序,授权配置后出
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
shiroFilter.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilter;
}
@Bean
public RedisClusterManager redisClusterManager() {
RedisClusterManager redisManager = new RedisClusterManager();
redisManager.setHost(redisClusterNodes);
return redisManager;
}
@Bean
public RedisSessionDAO redisSessionDAO(RedisClusterManager redisClusterManager) {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisClusterManager);
// Session ID 生成器
redisSessionDAO.setSessionIdGenerator(new JavaUuidSessionIdGenerator());
return redisSessionDAO;
}
@Bean
public SimpleCookie cookie() {
// cookie的name,对应的默认是 JSESSIONID
SimpleCookie cookie = new SimpleCookie("SHAREJSESSIONID");
cookie.setHttpOnly(true);
// path为 / 用于多个系统共享JSESSIONID
cookie.setPath("/");
return cookie;
}
@Bean
public RedisCacheManager redisCacheManager(RedisClusterManager redisClusterManager) {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisClusterManager);
redisCacheManager.setExpire(redis_expire);
redisCacheManager.setPrincipalIdFieldName("userId");
return redisCacheManager;
}
@Bean
public SecurityManager securityManager(AuthorizingRealm myShiroRealm, RedisSessionDAO redisSessionDAO, RedisCacheManager redisCacheManager) {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
// 设置session超时
sessionManager.setGlobalSessionTimeout(session_expire);
// 删除无效session
sessionManager.setDeleteInvalidSessions(true);
// 设置JSESSIONID
sessionManager.setSessionIdCookie(cookie());
// 设置sessionDAO
sessionManager.setSessionDAO(redisSessionDAO);
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setSessionManager(sessionManager);
securityManager.setCacheManager(redisCacheManager);
securityManager.setRememberMeManager(cookieRememberMeManager());
securityManager.setRealm(myShiroRealm);
return securityManager;
}
@Bean
public AuthorizingRealm myShiroRealm() {
AuthorizingRealm myShiroRealm = new AuthorizingRealm() {
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
logger.info("认证 --> MyShiroRealm.doGetAuthenticationInfo()");
String username = (String) token.getPrincipal();
User user = userService.findByAccount(username);
String password = new String((char[]) token.getCredentials());
// 账号不存在
if (user == null) {
throw new UnknownAccountException("账号不存在!");
}
// 密码错误
/*if (!MD5Utils.md5(password).equals(user.getPassword())) {
throw new IncorrectCredentialsException("账号或密码不正确");
}*/
// 账号锁定
if (user.getIsLock() == 1) {
throw new LockedAccountException("账号已被锁定,请联系管理员!");
}
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, getName());
return info;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
logger.info("权限配置 --> MyShiroRealm.doGetAuthorizationInfo()");
User user = (User) principals.getPrimaryPrincipal();
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
Set<String> shiroPermissions = new HashSet<>();
Set<String> roleSet = new HashSet<String>();
// 加载你的角色
List<Role> roles = roleService.list4Login(user);
if (roles != null) {
for (Role role : roles) {
// 添加角色
roleSet.add(role.getRoleKey());
// 添加角色关联的资源
if (role.getRelResourceList() != null) {
for (Resource resource : role.getRelResourceList()) {
shiroPermissions.add(resource.getSourceKey());
}
}
}
}
authorizationInfo.setRoles(roleSet);
authorizationInfo.setStringPermissions(shiroPermissions);
return authorizationInfo;
}
};
myShiroRealm.setCachingEnabled(true);
myShiroRealm.setAuthorizationCachingEnabled(true);
return myShiroRealm;
}
/**
* DefaultAdvisorAutoProxyCreator,Spring的一个bean,由Advisor决定对哪些类的方法进行AOP代理
*
* @return
*/
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();
proxyCreator.setProxyTargetClass(true);
return proxyCreator;
}
/**
* 开启shiro aop注解支持.
* 使用代理方式;所以需要开启代码支持;
*
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
@Bean
public SimpleMappingExceptionResolver resolver() {
SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();
Properties properties = new Properties();
properties.setProperty("org.apache.shiro.authz.UnauthorizedException", "error/403");
exceptionResolver.setExceptionMappings(properties);
return exceptionResolver;
}
@Bean
public SimpleCookie rememberMeCookie() {
logger.info("ShiroConfiguration.rememberMeCookie()");
//这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
//<!-- 记住我cookie生效时间 ,单位秒;-->
simpleCookie.setMaxAge(1800);
return simpleCookie;
}
@Bean
public CookieRememberMeManager cookieRememberMeManager() {
logger.info("ShiroConfiguration.rememberMeManager()");
CookieRememberMeManager manager = new CookieRememberMeManager();
manager.setCookie(rememberMeCookie());
return manager;
}
}
网友评论