From c7201b03e1d9095b630446f4ac6e17e511b218f7 Mon Sep 17 00:00:00 2001 From: wh Date: Mon, 13 Apr 2026 20:44:42 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B0=86shiro=E5=88=87=E6=8D=A2=E8=87=B3jdk17?= =?UTF-8?q?=20servlet=20api=EF=BC=8C=E9=80=82=E9=85=8Dspringboot3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 31 +++++++++++- .../com/label/common/shiro/ShiroConfig.java | 49 ++++++++++--------- .../com/label/common/shiro/TokenFilter.java | 39 +++++---------- 3 files changed, 69 insertions(+), 50 deletions(-) diff --git a/pom.xml b/pom.xml index a81d3b5..a2ef961 100644 --- a/pom.xml +++ b/pom.xml @@ -95,10 +95,37 @@ - + + + + org.apache.shiro + shiro-core + jakarta + 2.0.0 + + + + org.apache.shiro + shiro-web + jakarta + 2.0.0 + + + + org.apache.shiro + shiro-spring + jakarta + 2.0.0 + + + org.apache.shiro + shiro-web + + diff --git a/src/main/java/com/label/common/shiro/ShiroConfig.java b/src/main/java/com/label/common/shiro/ShiroConfig.java index 4987113..aa648b2 100644 --- a/src/main/java/com/label/common/shiro/ShiroConfig.java +++ b/src/main/java/com/label/common/shiro/ShiroConfig.java @@ -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 tokenFilterRegistration(TokenFilter tokenFilter) { FilterRegistrationBean registration = new FilterRegistrationBean<>(); diff --git a/src/main/java/com/label/common/shiro/TokenFilter.java b/src/main/java/com/label/common/shiro/TokenFilter.java index 0aeb3d7..513768d 100644 --- a/src/main/java/com/label/common/shiro/TokenFilter.java +++ b/src/main/java/com/label/common/shiro/TokenFilter.java @@ -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; @@ -98,7 +97,7 @@ public class TokenFilter extends OncePerRequestFilter { return; } String token = parts[1]; - //String token = authHeader.substring(7).trim(); + // String token = authHeader.substring(7).trim(); Map tokenData = redisService.hGetAll(RedisKeyManager.tokenKey(token)); if (tokenData == null || tokenData.isEmpty()) { @@ -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");