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 - - - 特性 - 文档 - 用户管理 - 登录 + + Security Admin + + + + + + + + 首页 + + + 功能 + + + 关于 + - + + + API文档 + + + Swagger UI + + + 登录系统 + + + - + - - - - 掌握 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 @@ -
学习最强大的Java安全框架,保护您的应用程序免受现代安全威胁。Spring Security - 6提供了全面的身份验证和授权功能,让您的应用安全无忧。
基于Spring Security 6的自定义权限校验解决方案,提供安全、高效的后台管理体验
Spring Security 6 提供的最新安全特性与自定义权限校验完美结合
基于Spring Security + 6的全新授权架构,实现灵活的自定义权限校验逻辑,满足复杂业务场景需求。
基于角色的访问控制(RBAC),支持多级角色权限分配,精细到按钮级别的权限控制。
优化权限校验流程,减少不必要的数据库查询,权限校验效率提升40%以上。
全面适配各种设备屏幕,从桌面到移动端,管理体验始终如一。
系统安全事件实时监控,登录日志、操作日志完整记录,便于审计追踪。
完善的RESTful API设计,支持Swagger文档,方便与其他系统集成。
Spring Security 6引入了许多强大的新功能,使应用程序安全比以往任何时候都更简单、更强大。
基于最前沿的Java生态技术构建
支持OAuth 2.0、OpenID Connect、SAML 2.0等多种认证协议,满足现代应用的安全需求。
细粒度的权限控制,支持方法级安全、领域对象安全等多种授权模式。
内置CSRF防护、点击劫持防护、内容安全策略等安全机制,保护应用免受常见攻击。
探索我们的文档和工具,快速掌握Spring Security 6的强大功能。
详细的API参考文档,包含所有类、方法和配置选项的详细说明,帮助您充分利用Spring Security - 6的所有功能。
直观易用的管理界面
多因素认证支持,图形验证码,防止暴力破解。
交互式API文档,可以直接在浏览器中测试API端点,查看请求和响应示例,加快开发流程。
可视化权限配置,角色与资源灵活关联。