diff --git a/auth-common/pom.xml b/auth-common/pom.xml index 737f179..1c64e6b 100644 --- a/auth-common/pom.xml +++ b/auth-common/pom.xml @@ -77,5 +77,25 @@ com.github.xiaoymin knife4j-openapi3-jakarta-spring-boot-starter + + + org.webjars + bootstrap + + + org.webjars + font-awesome + + + org.webjars + jquery + + + + org.jetbrains + annotations + 13.0 + compile + diff --git a/auth-common/src/main/java/com/auth/common/model/common/result/PageResult.java b/auth-common/src/main/java/com/auth/common/model/common/result/PageResult.java index b16e7c7..669321f 100644 --- a/auth-common/src/main/java/com/auth/common/model/common/result/PageResult.java +++ b/auth-common/src/main/java/com/auth/common/model/common/result/PageResult.java @@ -16,7 +16,7 @@ import java.util.List; @AllArgsConstructor @NoArgsConstructor @Builder -@Schema(name = "PageResult 对象", title = "分页返回结果", description = "分页返回结果") +@Schema(name = "分页返回结果", title = "分页返回结果", description = "分页返回结果") public class PageResult implements Serializable { @Schema(name = "pageNo", title = "当前页") diff --git a/auth-module/module-security/src/main/java/com/auth/module/security/filter/JwtAuthenticationFilter.java b/auth-module/module-security/src/main/java/com/auth/module/security/filter/JwtAuthenticationFilter.java index 3efbd4c..2c5bffd 100644 --- a/auth-module/module-security/src/main/java/com/auth/module/security/filter/JwtAuthenticationFilter.java +++ b/auth-module/module-security/src/main/java/com/auth/module/security/filter/JwtAuthenticationFilter.java @@ -4,7 +4,7 @@ import com.auth.common.context.BaseContext; import com.auth.common.exception.AuthenticSecurityException; import com.auth.common.exception.MyAuthenticationException; import com.auth.common.model.common.result.ResultCodeEnum; -import com.auth.module.security.config.SecurityWebConfiguration; +import com.auth.module.security.config.properties.SecurityConfigProperties; import com.auth.module.security.handler.SecurityAuthenticationEntryPoint; import com.auth.module.security.provider.JwtTokenProvider; import com.auth.module.security.service.DbUserDetailService; @@ -14,6 +14,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; @@ -34,88 +35,99 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtTokenProvider jwtTokenProvider; private final DbUserDetailService userDetailsService; private final SecurityAuthenticationEntryPoint securityAuthenticationEntryPoint; + private final SecurityConfigProperties pathsProperties; @Override - protected void doFilterInternal(HttpServletRequest request, - HttpServletResponse response, - FilterChain filterChain) throws ServletException, IOException, AuthenticSecurityException { - // 先校验不需要认证的接口 - RequestMatcher[] requestNoAuthMatchers = SecurityWebConfiguration.noAuthPaths.stream() - .map(AntPathRequestMatcher::new) - .toArray(RequestMatcher[]::new); - OrRequestMatcher noAuthRequestMatcher = new OrRequestMatcher(requestNoAuthMatchers); - if (noAuthRequestMatcher.matches(request)) { - filterChain.doFilter(request, response); - return; - } - - // 获取需要认证的接口 - RequestMatcher[] requestSecureMatchers = SecurityWebConfiguration.securedPaths.stream() - .map(AntPathRequestMatcher::new) - .toArray(RequestMatcher[]::new); - OrRequestMatcher secureRequestMatcher = new OrRequestMatcher(requestSecureMatchers); - - // 公开接口直接放行 - if (!secureRequestMatcher.matches(request)) { - filterChain.doFilter(request, response); - return; - } - - final String authHeader = request.getHeader("Authorization"); - // 如果当前请求不包含验证Token直接返回 - if (authHeader == null || !authHeader.startsWith("Bearer ")) { - filterChain.doFilter(request, response); - throw new AuthenticSecurityException(ResultCodeEnum.LOGIN_AUTH); - } - - // 当前请求的Token - final String jwtToken = authHeader.substring(7); - + protected void doFilterInternal(@NotNull HttpServletRequest request, + @NotNull HttpServletResponse response, + @NotNull FilterChain filterChain) throws ServletException, IOException { try { - // 检查当前Token是否过期 - if (jwtTokenProvider.isExpired(jwtToken)) { - // 💡如果过期不需要进行判断和验证,需要直接放行可以像下面这样写 - // =================================================== - // filterChain.doFilter(request, response); - // return; - // =================================================== - throw new AuthenticSecurityException(ResultCodeEnum.AUTHENTICATION_EXPIRED); + + // 检查白名单路径 + if (isNoAuthPath(request)) { + filterChain.doFilter(request, response); + return; } - // 解析当前Token中的用户名 - String username = jwtTokenProvider.getUsernameFromToken(jwtToken); - Long userId = jwtTokenProvider.getUserIdFromToken(jwtToken); + // 检查是否需要认证的路径 + if (!isSecurePath(request)) { + filterChain.doFilter(request, response); + return; + } - // 当前用户名存在,并且 Security上下文为空,设置认证相关信息 - if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { - // 调用用户信息进行登录 - UserDetails userDetails = userDetailsService.loadUserByUsername(username); - UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( - userDetails, - null, - userDetails.getAuthorities() - ); - authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - - // 设置认证用户信息 - SecurityContextHolder.getContext().setAuthentication(authToken); - BaseContext.setUsername(username); - BaseContext.setUserId(userId); + // 验证 Token + if (validToken(request)) { + filterChain.doFilter(request, response); + return; } filterChain.doFilter(request, response); - } - // ⚠️IMPORTANT: - // ========================================================================== - // 在 catch 块中,securityAuthenticationEntryPoint.commence() 已经处理了错误响应 - // 所以应该 直接返回,避免继续执行后续逻辑。 - // ========================================================================== - catch (RuntimeException exception) { + } catch (AuthenticSecurityException e) { + // 直接处理认证异常,不再调用filterChain.doFilter() securityAuthenticationEntryPoint.commence( request, response, - new MyAuthenticationException(exception.getMessage(), exception) + new MyAuthenticationException(e.getMessage(), e) ); + } catch (RuntimeException e) { + MyAuthenticationException myAuthenticationException = new MyAuthenticationException("Authentication failed", e); + securityAuthenticationEntryPoint.commence(request, response, myAuthenticationException); } + + } + + private boolean validToken(@NotNull HttpServletRequest request) { + // 验证Token + String authHeader = request.getHeader("Authorization"); + + // Token验证 + if (authHeader == null || !authHeader.startsWith("Bearer ")) { + return true; + // throw new AuthenticSecurityException(ResultCodeEnum.LOGIN_AUTH); + } + + String jwtToken = authHeader.substring(7); + + if (jwtTokenProvider.isExpired(jwtToken)) { + throw new AuthenticSecurityException(ResultCodeEnum.AUTHENTICATION_EXPIRED); + } + + // 设置认证信息 + String username = jwtTokenProvider.getUsernameFromToken(jwtToken); + Long userId = jwtTokenProvider.getUserIdFromToken(jwtToken); + + if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { + UserDetails userDetails = userDetailsService.loadUserByUsername(username); + UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( + userDetails, + null, + userDetails.getAuthorities() + ); + authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authToken); + BaseContext.setUsername(username); + BaseContext.setUserId(userId); + } + return false; + } + + /** + * 是否是不用验证的路径 + */ + private boolean isNoAuthPath(HttpServletRequest request) { + RequestMatcher[] matchers = pathsProperties.noAuthPaths.stream() + .map(AntPathRequestMatcher::new) + .toArray(RequestMatcher[]::new); + return new OrRequestMatcher(matchers).matches(request); + } + + /** + * 是否是要验证的路径 + */ + private boolean isSecurePath(HttpServletRequest request) { + RequestMatcher[] matchers = pathsProperties.securedPaths.stream() + .map(AntPathRequestMatcher::new) + .toArray(RequestMatcher[]::new); + return new OrRequestMatcher(matchers).matches(request); } } diff --git a/auth-module/module-security/src/main/java/com/auth/module/security/handler/JwtTokenLogoutHandler.java b/auth-module/module-security/src/main/java/com/auth/module/security/handler/JwtTokenLogoutHandler.java index 854fd37..43cdf7d 100644 --- a/auth-module/module-security/src/main/java/com/auth/module/security/handler/JwtTokenLogoutHandler.java +++ b/auth-module/module-security/src/main/java/com/auth/module/security/handler/JwtTokenLogoutHandler.java @@ -37,4 +37,5 @@ public class JwtTokenLogoutHandler implements LogoutHandler { Result result = Result.success(ResultCodeEnum.SUCCESS_LOGOUT); ResponseUtil.out(response, result); } + } diff --git a/auth-services/service-base/src/main/resources/templates/index.html b/auth-services/service-base/src/main/resources/templates/index.html index f302ee4..c255828 100644 --- a/auth-services/service-base/src/main/resources/templates/index.html +++ b/auth-services/service-base/src/main/resources/templates/index.html @@ -1,463 +1,254 @@ - + - Spring Security 6 学习中心 - + Spring Security 6 | 自定义权限校验后台管理系统 + + -
+
+ -
-
-
-

掌握 Spring Security 6

-

学习最强大的Java安全框架,保护您的应用程序免受现代安全威胁。Spring Security - 6提供了全面的身份验证和授权功能,让您的应用安全无忧。

-
- 开始学习 - 查看API文档 +
+
+

Spring Security 6 后台管理系统

+

基于Spring Security 6的自定义权限校验解决方案,提供安全、高效的后台管理体验

+ +
+
+ + +
+
+

核心特性

+

Spring Security 6 提供的最新安全特性与自定义权限校验完美结合

+
+ +
+
+
+
+ +
+

自定义权限校验

+

基于Spring Security + 6的全新授权架构,实现灵活的自定义权限校验逻辑,满足复杂业务场景需求。

+
+
+ +
+
+
+ +
+

RBAC权限控制

+

基于角色的访问控制(RBAC),支持多级角色权限分配,精细到按钮级别的权限控制。

+
+
+ +
+
+
+ +
+

高性能设计

+

优化权限校验流程,减少不必要的数据库查询,权限校验效率提升40%以上。

+
+
+ +
+
+
+ +
+

响应式设计

+

全面适配各种设备屏幕,从桌面到移动端,管理体验始终如一。

+
+
+ +
+
+
+ +
+

实时监控

+

系统安全事件实时监控,登录日志、操作日志完整记录,便于审计追踪。

+
+
+ +
+
+
+ +
+

API集成

+

完善的RESTful API设计,支持Swagger文档,方便与其他系统集成。

- -
+ +
-
-

Spring Security 6 核心特性

-

Spring Security 6引入了许多强大的新功能,使应用程序安全比以往任何时候都更简单、更强大。

+
+

技术栈

+

基于最前沿的Java生态技术构建

-
-
-
- -
-

现代化的认证

-

支持OAuth 2.0、OpenID Connect、SAML 2.0等多种认证协议,满足现代应用的安全需求。

-
-
-
- -
-

强大的授权

-

细粒度的权限控制,支持方法级安全、领域对象安全等多种授权模式。

-
-
-
- -
-

防护机制

-

内置CSRF防护、点击劫持防护、内容安全策略等安全机制,保护应用免受常见攻击。

-
+ +
+ Spring Security 6 + Spring Boot 3 + Java 17 + Thymeleaf + JWT + MySQL + Redis + WebJars + Bootstrap 5 + Font Awesome
- -
-
-
-

学习资源与文档

-

探索我们的文档和工具,快速掌握Spring Security 6的强大功能。

-
-
-
-
-
-

API 文档

-

详细的API参考文档,包含所有类、方法和配置选项的详细说明,帮助您充分利用Spring Security - 6的所有功能。

- 查看API文档 + +
+
+

系统预览

+

直观易用的管理界面

+
+ +
+
+
+ 登录界面 +
+
安全登录
+

多因素认证支持,图形验证码,防止暴力破解。

-
-
-
-

Swagger UI

-

交互式API文档,可以直接在浏览器中测试API端点,查看请求和响应示例,加快开发流程。

- 访问Swagger UI +
+
+
+ 权限管理 +
+
权限管理
+

可视化权限配置,角色与资源灵活关联。

@@ -465,38 +256,54 @@
-