现在一个项目后台api以集群方式部署,前端请求会经过负载均衡分散到集群上。shiro默认使用内存存储session,从而需要解决分布式session问题。
主要用到了shiro+spring-session依赖
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-spring-boot-web-starterartifactId>
<version>1.6.0version>
dependency>
<dependency>
<groupId>org.springframework.sessiongroupId>
<artifactId>spring-session-jdbcartifactId>
dependency>
完整的pom.xml
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.3.2.RELEASEversion>
<relativePath/>
parent>
<groupId>com.examplegroupId>
<artifactId>authartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>authname>
<description>Demo project for Spring Bootdescription>
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-spring-boot-web-starterartifactId>
<version>1.6.0version>
dependency>
<dependency>
<groupId>org.springframework.sessiongroupId>
<artifactId>spring-session-jdbcartifactId>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.1.0version>
dependency>
<dependency>
<groupId>org.mybatis.generatorgroupId>
<artifactId>mybatis-generator-coreartifactId>
<version>1.3.6version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
<exclusions>
<exclusion>
<groupId>org.junit.vintagegroupId>
<artifactId>junit-vintage-engineartifactId>
exclusion>
exclusions>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
<plugin>
<groupId>org.mybatis.generatorgroupId>
<artifactId>mybatis-generator-maven-pluginartifactId>
<version>1.3.2version>
<configuration>
<verbose>trueverbose>
<overwrite>trueoverwrite>
configuration>
plugin>
plugins>
build>
project>
mybatis:
mapper-locations: classpath:mapping/*.xml
spring:
main:
allow-bean-definition-overriding: true
datasource:
url: jdbc:mysql://localhost:3306/ml?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 111111
driver-class-name: com.mysql.cj.jdbc.Driver
##session.timeout用于设置session过期时间,server.session.timeout不生效
session:
timeout: 100s
server:
port: 8081
写死了用户admin ,密码123
package com.example.auth.config;
import com.example.auth.entity.User;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.util.StringUtils;
public class Myrealm extends AuthorizingRealm {
/**
* @Description 权限配置类
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取登录用户名
String name = (String) principalCollection.getPrimaryPrincipal();
//查询用户名称
// User user = loginService.getUserByName(name);
User usr = new User();
usr.setId(1);
usr.setRole("admin");
usr.setUser("admin");
usr.setPassword("123");
//添加角色和权限
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
// for (Role role : user.getRoles()) {
// //添加角色
// simpleAuthorizationInfo.addRole(role.getRoleName());
// //添加权限
// for (Permissions permissions : role.getPermissions()) {
// simpleAuthorizationInfo.addStringPermission(permissions.getPermissionsName());
// }
// }
return simpleAuthorizationInfo;
}
/**
* @Description 认证配置类
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
if (StringUtils.isEmpty(authenticationToken.getPrincipal())) {
return null;
}
//获取用户信息
String name = authenticationToken.getPrincipal().toString();
User usr = null;
if ("admin".equals(name)) {
usr = new User();
usr.setId(1);
usr.setRole("admin");
usr.setUser("admin");
usr.setPassword("123");
}
if (usr == null) {
//这里返回后会报出对应异常
return null;
} else {
//这里验证authenticationToken和simpleAuthenticationInfo的信息
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(name, usr.getPassword().toString(), getName());
return simpleAuthenticationInfo;
}
}
}
package com.example.auth.config;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.DefaultSessionManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.session.mgt.ServletContainerSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
@Bean
@ConditionalOnMissingBean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
defaultAAP.setProxyTargetClass(true);
return defaultAAP;
}
//将自己的验证方式加入容器
@Bean
public Myrealm myShiroRealm() {
Myrealm customRealm = new Myrealm();
return customRealm;
}
//权限管理,配置主要是Realm的管理认证
@Bean
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// securityManager.setSessionManager(getDefaultWebSessionManager());
securityManager.setRealm(myShiroRealm());
return securityManager;
}
// @Bean
// public ServletContainerSessionManager getDefaultWebSessionManager() {
// ServletContainerSessionManager defaultWebSessionManager = new ServletContainerSessionManager();
defaultWebSessionManager.setGlobalSessionTimeout(1000 * 60);// 会话过期时间,单位:毫秒(在无操作时开始计时)--->一分钟,用于测试
defaultWebSessionManager.setSessionValidationSchedulerEnabled(true);
defaultWebSessionManager.setSessionIdCookieEnabled(true);
// return defaultWebSessionManager;
// }
//Filter工厂,设置对应的过滤条件和跳转条件
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, String> map = new HashMap<>();
//登出
map.put("/user/login", "anon");
//对所有用户认证
map.put("/user/get","authc");
//登录
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
controller提供login、logout、以及需要登录才能访问的get方法
package com.example.auth.controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/login")
String login(@RequestParam("username") String username, @RequestParam("password") String password){
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(
username,
password
);
try {
//进行验证,这里可以捕获异常,然后返回对应信息
subject.login(usernamePasswordToken);
// subject.checkRole("admin");
// subject.checkPermissions("query", "add");
} catch (UnknownAccountException e) {
// log.error("用户名不存在!", e);
return "用户名不存在!";
} catch (AuthenticationException e) {
// log.error("账号或密码错误!", e);
return "账号或密码错误!";
} catch (AuthorizationException e) {
// log.error("没有权限!", e);
return "没有权限";
}
return "login success";
}
@GetMapping("/get")
String get(){
String userName = (String) SecurityUtils.getSubject().getPrincipal();
System.out.println(userName);
return "result";
}
@GetMapping("/logout")
public String logout() {
SecurityUtils.getSubject().logout();
return "out";
}
}
DROP TABLE IF EXISTS SPRING_SESSION_ATTRIBUTES;
DROP TABLE IF EXISTS SPRING_SESSION;
CREATE TABLE SPRING_SESSION (
PRIMARY_ID CHAR(36) NOT NULL,
SESSION_ID CHAR(36) NOT NULL,
CREATION_TIME BIGINT NOT NULL,
LAST_ACCESS_TIME BIGINT NOT NULL,
MAX_INACTIVE_INTERVAL INT NOT NULL,
EXPIRY_TIME BIGINT NOT NULL,
PRINCIPAL_NAME VARCHAR(100),
CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID)
) ENGINE=INNODB ROW_FORMAT=DYNAMIC;
CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID);
CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME);
CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME);
CREATE TABLE SPRING_SESSION_ATTRIBUTES (
SESSION_PRIMARY_ID CHAR(36) NOT NULL,
ATTRIBUTE_NAME VARCHAR(200) NOT NULL,
ATTRIBUTE_BYTES BLOB NOT NULL,
CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_PRIMARY_ID, ATTRIBUTE_NAME),
CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_PRIMARY_ID) REFERENCES SPRING_SESSION(PRIMARY_ID) ON DELETE CASCADE
) ENGINE=INNODB ROW_FORMAT=DYNAMIC;
同时启动两台服务一个端口8080,一个端口8081
在8081上进行一次登录:
spring-session表记录了session数据
Max_inactive_interval代表session的持续时间为100s
使用8080的get获取数据:
使用8080登出或者session过期,mysql表的数据会删除:
初始的session过期时间是30分钟,想调整过期时间。一开始看网上是要在shiro config里自定义session manager来设置过期时间。
然后配置了DefaultWebSessionManager,然而spring-session就失效了,mysql在登录后还是没数据。
@Bean
public DefaultWebSessionManager getDefaultWebSessionManager() {
DefaultWebSessionManager defaultWebSessionManager = new DefaultWebSessionManager();
defaultWebSessionManager.setGlobalSessionTimeout(1000 * 60);// 会话过期时间,单位:毫秒(在无操作时开始计时)--->一分钟,用于测试
defaultWebSessionManager.setSessionValidationSchedulerEnabled(true);
defaultWebSessionManager.setSessionIdCookieEnabled(true);
return defaultWebSessionManager;
}
原因:
在没有设置session manager时,SecurityManager默认使用的是ServletContainerSessionManager。
ServletContainerSessionManager的方法返回true
DefaultWebSessionManager返回false
只有当为true时才会交给spring-session来管理