SpringBoot+Shiro基於Redis實現共享Session
項目發布到微服務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;
}
}
參考文檔: