将shiro切换至jdk17 servlet api,适配springboot3
This commit is contained in:
31
pom.xml
31
pom.xml
@@ -95,10 +95,37 @@
|
||||
</dependency>
|
||||
|
||||
<!-- Apache Shiro -->
|
||||
<dependency>
|
||||
<!-- <dependency>
|
||||
<groupId>org.apache.shiro</groupId>
|
||||
<artifactId>shiro-spring-boot-web-starter</artifactId>
|
||||
<version>1.13.0</version>
|
||||
<version>2.1.0</version>
|
||||
</dependency> -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.shiro</groupId>
|
||||
<artifactId>shiro-core</artifactId>
|
||||
<classifier>jakarta</classifier>
|
||||
<version>2.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.shiro</groupId>
|
||||
<artifactId>shiro-web</artifactId>
|
||||
<classifier>jakarta</classifier>
|
||||
<version>2.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.shiro</groupId>
|
||||
<artifactId>shiro-spring</artifactId>
|
||||
<classifier>jakarta</classifier>
|
||||
<version>2.0.0</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.apache.shiro</groupId>
|
||||
<artifactId>shiro-web</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- AWS SDK v2 - S3 -->
|
||||
|
||||
@@ -1,19 +1,30 @@
|
||||
package com.label.common.shiro;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.label.common.redis.RedisService;
|
||||
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
|
||||
import org.apache.shiro.mgt.DefaultSecurityManager;
|
||||
import org.apache.shiro.mgt.DefaultSubjectDAO;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.apache.shiro.mgt.SecurityManager;
|
||||
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.List;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.label.common.redis.RedisService;
|
||||
|
||||
/**
|
||||
* Shiro security configuration for the Jakarta servlet stack.
|
||||
* Shiro 安全配置。
|
||||
*
|
||||
* 设计说明:
|
||||
* - 使用 Spring 的 FilterRegistrationBean 注册 TokenFilter(jakarta.servlet),
|
||||
* 替代 Shiro 的 ShiroFilterFactoryBean(javax.servlet),避免 Shiro 1.x 与
|
||||
* Spring Boot 3.x 之间的 javax/jakarta 命名空间冲突。
|
||||
* - URL 路由逻辑内聚于 TokenFilter.shouldNotFilter():
|
||||
* /api/auth/login → 跳过(公开)
|
||||
* 非 /api/ 路径 → 跳过(公开)
|
||||
* /api/** → 强制校验 Bearer Token
|
||||
* - SecurityUtils.setSecurityManager() 必须在此处调用,
|
||||
* 以便 @RequiresRoles 等 AOP 注解和 SecurityUtils.getSubject() 可正常工作。
|
||||
*/
|
||||
@Configuration
|
||||
public class ShiroConfig {
|
||||
@@ -25,28 +36,22 @@ public class ShiroConfig {
|
||||
|
||||
@Bean
|
||||
public SecurityManager securityManager(UserRealm userRealm) {
|
||||
// Keep Shiro on the core stack. Shiro 1.x web classes depend on javax.servlet.
|
||||
DefaultSecurityManager manager = new DefaultSecurityManager();
|
||||
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
|
||||
manager.setRealms(List.of(userRealm));
|
||||
manager.setSubjectDAO(statelessSubjectDao());
|
||||
// 设置全局 SecurityManager,使 SecurityUtils.getSubject() 及 AOP 注解可用
|
||||
SecurityUtils.setSecurityManager(manager);
|
||||
return manager;
|
||||
}
|
||||
|
||||
private DefaultSubjectDAO statelessSubjectDao() {
|
||||
DefaultSessionStorageEvaluator evaluator = new DefaultSessionStorageEvaluator();
|
||||
evaluator.setSessionStorageEnabled(false);
|
||||
|
||||
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
|
||||
subjectDAO.setSessionStorageEvaluator(evaluator);
|
||||
return subjectDAO;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TokenFilter tokenFilter(RedisService redisService, ObjectMapper objectMapper,
|
||||
SecurityManager securityManager) {
|
||||
return new TokenFilter(redisService, objectMapper, securityManager);
|
||||
public TokenFilter tokenFilter(RedisService redisService, ObjectMapper objectMapper) {
|
||||
return new TokenFilter(redisService, objectMapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 TokenFilter 注册为 Servlet 过滤器,覆盖所有路径。
|
||||
* 实际的路径过滤逻辑由 TokenFilter.shouldNotFilter() 控制。
|
||||
*/
|
||||
@Bean
|
||||
public FilterRegistrationBean<TokenFilter> tokenFilterRegistration(TokenFilter tokenFilter) {
|
||||
FilterRegistrationBean<TokenFilter> registration = new FilterRegistrationBean<>();
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
package com.label.common.shiro;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.apache.shiro.util.ThreadContext;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.label.common.context.CompanyContext;
|
||||
import com.label.common.redis.RedisKeyManager;
|
||||
import com.label.common.redis.RedisService;
|
||||
import com.label.common.result.Result;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.shiro.mgt.SecurityManager;
|
||||
import org.apache.shiro.subject.SimplePrincipalCollection;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
import org.apache.shiro.util.ThreadContext;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* JWT-style Bearer Token 过滤器。
|
||||
@@ -40,7 +40,6 @@ public class TokenFilter extends OncePerRequestFilter {
|
||||
|
||||
private final RedisService redisService;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final SecurityManager securityManager;
|
||||
|
||||
@Value("${shiro.auth.enabled:true}")
|
||||
private boolean authEnabled;
|
||||
@@ -81,7 +80,7 @@ public class TokenFilter extends OncePerRequestFilter {
|
||||
TokenPrincipal principal = new TokenPrincipal(
|
||||
mockUserId, mockRole, mockCompanyId, mockUsername, "mock-token");
|
||||
CompanyContext.set(mockCompanyId);
|
||||
bindSubject(principal);
|
||||
SecurityUtils.getSubject().login(new BearerToken("mock-token", principal));
|
||||
request.setAttribute("__token_principal__", principal);
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
@@ -116,7 +115,7 @@ public class TokenFilter extends OncePerRequestFilter {
|
||||
|
||||
// 创建 TokenPrincipal 并登录 Shiro Subject,使 @RequiresRoles 等注解生效
|
||||
TokenPrincipal principal = new TokenPrincipal(userId, role, companyId, username, token);
|
||||
bindSubject(principal);
|
||||
SecurityUtils.getSubject().login(new BearerToken(token, principal));
|
||||
request.setAttribute("__token_principal__", principal);
|
||||
redisService.expire(RedisKeyManager.tokenKey(token), tokenTtlSeconds);
|
||||
redisService.expire(RedisKeyManager.userSessionsKey(userId), tokenTtlSeconds);
|
||||
@@ -129,21 +128,9 @@ public class TokenFilter extends OncePerRequestFilter {
|
||||
// 关键:必须清除 ThreadLocal,防止线程池复用时数据串漏
|
||||
CompanyContext.clear();
|
||||
ThreadContext.unbindSubject();
|
||||
ThreadContext.unbindSecurityManager();
|
||||
}
|
||||
}
|
||||
|
||||
private void bindSubject(TokenPrincipal principal) {
|
||||
SimplePrincipalCollection principals = new SimplePrincipalCollection(principal, UserRealm.class.getName());
|
||||
Subject subject = new Subject.Builder(securityManager)
|
||||
.principals(principals)
|
||||
.authenticated(true)
|
||||
.sessionCreationEnabled(false)
|
||||
.buildSubject();
|
||||
ThreadContext.bind(securityManager);
|
||||
ThreadContext.bind(subject);
|
||||
}
|
||||
|
||||
private void writeUnauthorized(HttpServletResponse resp, String message) throws IOException {
|
||||
resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
resp.setContentType(MediaType.APPLICATION_JSON_VALUE + ";charset=UTF-8");
|
||||
|
||||
Reference in New Issue
Block a user