前言：为什么你需要读这篇文章还记得那个深夜吗？你的系统被人用 admin/123456 登录了，然后删库跑路。老板问你："我们的安全措施呢？"你支支吾吾地说："我以为用户会自觉的..."朋友，醒醒吧。本文将带你深入了解Java世界的三大安全框架：Spring Security（官方嫡长子）、Apache Shiro（轻量级老将）、Sa-Token（国产新秀）。看完之后，你至少能在老板面前假装很懂安全。一、安全框架核心概念在开始之前，先搞清楚几个灵魂拷问：1.1 认证 vs 授权认证（Authentication）：你是谁？—— 验明正身 授权（Authorization）：你能干啥？—— 权限控制打个比方：认证：门卫检查你的工牌，确认你是公司员工授权：你虽然是员工，但你不能进财务室拿钱1.2 安全框架核心架构flowchart TB subgraph 客户端 A[用户请求] end subgraph 安全框架 B[认证过滤器链] C[身份认证模块] D[授权决策模块] E[会话管理] F[密码加密] end subgraph 业务系统 G[Controller] H[Service] I[数据库] end A --> B B --> C C -->|认证成功| D C -->|认证失败| J[返回401] D -->|授权通过| G D -->|授权失败| K[返回403] G --> H --> I C E C F1.3 主流安全框架对比特性Spring SecurityApache ShiroSa-Token学习曲线🔥🔥🔥🔥🔥 陡峭如珠峰🔥🔥🔥 相对平缓🔥🔥 新手友好功能完整度⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐Spring集成原生支持需要适配良好支持社区活跃度非常活跃一般国内活跃OAuth2支持原生支持需要扩展内置支持文档质量英文为主,较全一般中文友好适用场景企业级复杂系统中小型项目快速开发二、Spring Security：官方嫡长子2.1 简介Spring Security 是 Spring 家族的亲儿子，功能强大到令人发指，配置复杂到令人放弃。但没办法，谁让人家是正统呢？"Spring Security 的学习曲线不是曲线，是悬崖。" —— 某不愿透露姓名的秃头程序员2.2 核心架构flowchart TB subgraph FilterChain[安全过滤器链] F1[SecurityContextPersistenceFilter安全上下文持久化] F2[UsernamePasswordAuthenticationFilter用户名密码认证] F3[BasicAuthenticationFilterBasic认证] F4[ExceptionTranslationFilter异常转换] F5[FilterSecurityInterceptor授权拦截器] end subgraph Core[核心组件] A[AuthenticationManager认证管理器] P[AuthenticationProvider认证提供者] U[UserDetailsService用户详情服务] E[PasswordEncoder密码编码器] end subgraph Authorization[授权组件] AD[AccessDecisionManager访问决策管理器] V1[AffirmativeBased一票通过] V2[ConsensusBased少数服从多数] V3[UnanimousBased一票否决] end F1 --> F2 --> F3 --> F4 --> F5 F2 --> A A --> P P --> U P --> E F5 --> AD AD --> V1 AD --> V2 AD --> V32.3 快速入门2.3.1 添加依赖 org.springframework.boot spring-boot-starter-security 恭喜你！加完这一行，你的所有接口都需要登录了。是不是很惊喜？😏2.3.2 基础配置（Spring Boot 3.x 风格）@Configuration @EnableWebSecurity @EnableMethodSecurity // 开启方法级别安全 public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http // 关闭CSRF（前后端分离项目通常关闭） .csrf(csrf -> csrf.disable()) // 配置请求授权规则 .authorizeHttpRequests(auth -> auth // 放行登录、注册接口 .requestMatchers("/auth/**", "/public/**").permitAll() // Swagger放行 .requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll() // 管理员接口 .requestMatchers("/admin/**").hasRole("ADMIN") // 其他请求需要认证 .anyRequest().authenticated() ) // 配置表单登录 .formLogin(form -> form .loginProcessingUrl("/auth/login") .successHandler(authenticationSuccessHandler()) .failureHandler(authenticationFailureHandler()) ) // 配置登出 .logout(logout -> logout .logoutUrl("/auth/logout") .logoutSuccessHandler(logoutSuccessHandler()) ) // 异常处理 .exceptionHandling(ex -> ex .authenticationEntryPoint(authenticationEntryPoint()) .accessDeniedHandler(accessDeniedHandler()) ) // Session配置（无状态） .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.STATELESS) ); // 添加JWT过滤器 http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); return http.build(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public AuthenticationManager authenticationManager( AuthenticationConfiguration config) throws Exception { return config.getAuthenticationManager(); } }2.3.3 自定义 UserDetailsService@Service @RequiredArgsConstructor public class CustomUserDetailsService implements UserDetailsService { private final UserRepository userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 从数据库查询用户 User user = userRepository.findByUsername(username) .orElseThrow(() -> new UsernameNotFoundException( "用户不存在: " + username)); // 构建Spring Security的UserDetails return org.springframework.security.core.userdetails.User .withUsername(user.getUsername()) .password(user.getPassword()) .authorities(user.getRoles().stream() .map(role -> new SimpleGrantedAuthority("ROLE_" + role.getName())) .collect(Collectors.toList())) .accountExpired(false) .accountLocked(user.isLocked()) .credentialsExpired(false) .disabled(!user.isEnabled()) .build(); } }2.3.4 JWT 工具类@Component public class JwtTokenProvider { @Value("${jwt.secret}") private String jwtSecret; @Value("${jwt.expiration}") private long jwtExpiration; /** * 生成JWT Token */ public String generateToken(Authentication authentication) { UserDetails userDetails = (UserDetails) authentication.getPrincipal(); Date now = new Date(); Date expiryDate = new Date(now.getTime() + jwtExpiration); return Jwts.builder() .setSubject(userDetails.getUsername()) .claim("roles", userDetails.getAuthorities().stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.toList())) .setIssuedAt(now) .setExpiration(expiryDate) .signWith(getSigningKey(), SignatureAlgorithm.HS512) .compact(); } /** * 从Token中获取用户名 */ public String getUsernameFromToken(String token) { Claims claims = Jwts.parserBuilder() .setSigningKey(getSigningKey()) .build() .parseClaimsJws(token) .getBody(); return claims.getSubject(); } /** * 验证Token */ public boolean validateToken(String token) { try { Jwts.parserBuilder() .setSigningKey(getSigningKey()) .build() .parseClaimsJws(token); return true; } catch (JwtException | IllegalArgumentException e) { log.error("Invalid JWT token: {}", e.getMessage()); return false; } } private Key getSigningKey() { byte[] keyBytes = Decoders.BASE64.decode(jwtSecret); return Keys.hmacShaKeyFor(keyBytes); } }2.3.5 JWT 认证过滤器@Component @RequiredArgsConstructor public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtTokenProvider tokenProvider; private final CustomUserDetailsService userDetailsService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { try { String jwt = getJwtFromRequest(request); if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) { String username = tokenProvider.getUsernameFromToken(jwt); UserDetails userDetails = userDetailsService .loadUserByUsername(username); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); authentication.setDetails( new WebAuthenticationDetailsSource().buildDetails(request)); // 将认证信息放入Security上下文 SecurityContextHolder.getContext() .setAuthentication(authentication); } } catch (Exception e) { log.error("无法设置用户认证: {}", e.getMessage()); } filterChain.doFilter(request, response); } private String getJwtFromRequest(HttpServletRequest request) { String bearerToken = request.getHeader("Authorization"); if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { return bearerToken.substring(7); } return null; } }2.4 方法级别权限控制@RestController @RequestMapping("/api/users") @RequiredArgsConstructor public class UserController { private final UserService userService; // 需要ADMIN角色 @PreAuthorize("hasRole('ADMIN')") @GetMapping public List<UserDTO> getAllUsers() { return userService.findAll(); } // 需要USER或ADMIN角色 @PreAuthorize("hasAnyRole('USER', 'ADMIN')") @GetMapping("/{id}") public UserDTO getUser(@PathVariable Long id) { return userService.findById(id); } // 只能访问自己的信息，或者是管理员 @PreAuthorize("#id == authentication.principal.id or hasRole('ADMIN')") @PutMapping("/{id}") public UserDTO updateUser(@PathVariable Long id, @RequestBody UserDTO dto) { return userService.update(id, dto); } // 基于权限（Permission）的控制 @PreAuthorize("hasAuthority('user:delete')") @DeleteMapping("/{id}") public void deleteUser(@PathVariable Long id) { userService.delete(id); } // 方法执行后校验返回值 @PostAuthorize("returnObject.createdBy == authentication.name") @GetMapping("/my-data/{id}") public DataDTO getMyData(@PathVariable Long id) { return dataService.findById(id); } }2.5 Spring Security 认证流程详解sequenceDiagram participant C as 客户端 participant F as JwtAuthFilter participant SC as SecurityContext participant AM as AuthenticationManager participant AP as AuthenticationProvider participant UDS as UserDetailsService participant DB as 数据库 C->>F: 1. 携带Token请求 F->>F: 2. 解析Token获取username F->>UDS: 3. loadUserByUsername() UDS->>DB: 4. 查询用户 DB-->>UDS: 5. 返回用户信息 UDS-->>F: 6. 返回UserDetails F->>SC: 7. 设置Authentication SC-->>C: 8. 请求继续执行 Note over C,DB: 登录流程 C->>AM: 9. authenticate(用户名,密码) AM->>AP: 10. 委托认证 AP->>UDS: 11. 加载用户 UDS->>DB: 12. 查询 DB-->>UDS: 13. 用户数据 UDS-->>AP: 14. UserDetails AP->>AP: 15. 密码比对 AP-->>AM: 16. Authentication AM-->>C: 17. 认证成功,返回Token三、Apache Shiro：轻量级老将3.1 简介Apache Shiro 是一个老牌安全框架，比 Spring Security 更轻量，学习曲线更平缓。如果你觉得 Spring Security 太重了，Shiro 是个不错的选择。"Shiro 的 API 设计得像人话。" —— 被 Spring Security 虐过的程序员3.2 核心架构flowchart TB subgraph Subject[Subject 当前用户] S[Subject] end subgraph SecurityManager[SecurityManager 安全管理器] SM[SecurityManager] AU[Authenticator认证器] AZ[Authorizer授权器] SEM[SessionManager会话管理器] end subgraph Realms[Realm 数据源] R1[JdbcRealm] R2[LdapRealm] R3[CustomRealm] end subgraph Support[支撑组件] CC[CacheManager缓存管理] CR[Cryptography加密] end S --> SM SM --> AU SM --> AZ SM --> SEM AU --> R1 AU --> R2 AU --> R3 AZ --> R1 AZ --> R2 AZ --> R3 SM --> CC SM --> CR3.3 快速入门3.3.1 添加依赖 org.apache.shiro shiro-spring-boot-starter 1.13.0 3.3.2 自定义 Realmpublic class CustomRealm extends AuthorizingRealm { @Autowired private UserService userService; /** * 授权（权限验证） */ @Override protected AuthorizationInfo doGetAuthorizationInfo( PrincipalCollection principals) { String username = (String) principals.getPrimaryPrincipal(); User user = userService.findByUsername(username); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); // 设置角色 Set<String> roles = user.getRoles().stream() .map(Role::getName) .collect(Collectors.toSet()); info.setRoles(roles); // 设置权限 Set<String> permissions = user.getRoles().stream() .flatMap(role -> role.getPermissions().stream()) .map(Permission::getCode) .collect(Collectors.toSet()); info.setStringPermissions(permissions); return info; } /** * 认证（身份验证） */ @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken token) throws AuthenticationException { String username = (String) token.getPrincipal(); User user = userService.findByUsername(username); if (user == null) { throw new UnknownAccountException("用户不存在"); } if (user.isLocked()) { throw new LockedAccountException("账户已锁定"); } // 返回认证信息，Shiro会自动比对密码 return new SimpleAuthenticationInfo( user.getUsername(), user.getPassword(), ByteSource.Util.bytes(user.getSalt()), // 盐值 getName() ); } }3.3.3 Shiro 配置@Configuration public class ShiroConfig { @Bean public CustomRealm customRealm() { CustomRealm realm = new CustomRealm(); // 设置密码匹配器 HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(); matcher.setHashAlgorithmName("SHA-256"); matcher.setHashIterations(1024); realm.setCredentialsMatcher(matcher); return realm; } @Bean public DefaultWebSecurityManager securityManager() { DefaultWebSecurityManager manager = new DefaultWebSecurityManager(); manager.setRealm(customRealm()); manager.setSessionManager(sessionManager()); manager.setCacheManager(cacheManager()); return manager; } @Bean public ShiroFilterFactoryBean shiroFilter() { ShiroFilterFactoryBean filter = new ShiroFilterFactoryBean(); filter.setSecurityManager(securityManager()); // 登录页面 filter.setLoginUrl("/login"); // 未授权页面 filter.setUnauthorizedUrl("/403"); // 过滤器链配置 Map<String, String> filterChainMap = new LinkedHashMap<>(); filterChainMap.put("/static/**", "anon"); // 静态资源放行 filterChainMap.put("/login", "anon"); // 登录放行 filterChainMap.put("/logout", "logout"); // 登出 filterChainMap.put("/admin/**", "roles[admin]"); // admin角色 filterChainMap.put("/**", "authc"); // 其他需要认证 filter.setFilterChainDefinitionMap(filterChainMap); return filter; } @Bean public SessionManager sessionManager() { DefaultWebSessionManager manager = new DefaultWebSessionManager(); manager.setSessionIdUrlRewritingEnabled(false); manager.setGlobalSessionTimeout(1800000); // 30分钟 return manager; } @Bean public CacheManager cacheManager() { return new MemoryConstrainedCacheManager(); } // 开启注解支持 @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() { AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager()); return advisor; } }3.3.4 使用示例@RestController @RequestMapping("/api") public class ApiController { /** * 登录 */ @PostMapping("/login") public Result login(@RequestBody LoginRequest request) { Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken( request.getUsername(), request.getPassword(), request.isRememberMe() ); try { subject.login(token); return Result.success("登录成功"); } catch (UnknownAccountException e) { return Result.fail("用户不存在"); } catch (IncorrectCredentialsException e) { return Result.fail("密码错误"); } catch (LockedAccountException e) { return Result.fail("账户已锁定"); } } /** * 需要认证 */ @RequiresAuthentication @GetMapping("/user/info") public Result getUserInfo() { Subject subject = SecurityUtils.getSubject(); String username = (String) subject.getPrincipal(); return Result.success(userService.findByUsername(username)); } /** * 需要角色 */ @RequiresRoles("admin") @GetMapping("/admin/users") public Result listUsers() { return Result.success(userService.findAll()); } /** * 需要权限 */ @RequiresPermissions("user:delete") @DeleteMapping("/user/{id}") public Result deleteUser(@PathVariable Long id) { userService.delete(id); return Result.success(); } /** * 多个角色（满足其一） */ @RequiresRoles(value = {"admin", "manager"}, logical = Logical.OR) @GetMapping("/reports") public Result getReports() { return Result.success(reportService.findAll()); } }3.4 Shiro 过滤器说明过滤器说明示例anon匿名访问，不需要认证/public/**authc需要认证/**logout登出过滤器/logoutroles需要指定角色roles[admin]perms需要指定权限perms[user:read]user已认证或RememberMe/user/**ssl需要HTTPS/secure/**四、Sa-Token：国产新秀4.1 简介Sa-Token 是一个轻量级 Java 权限认证框架，主打的就是一个简单。如果你厌倦了 Spring Security 的繁琐配置，Sa-Token 绝对能让你眼前一亮。"终于有一个看得懂的安全框架了！" —— 刚从 Spring Security 逃出来的程序员4.2 核心特性mindmap root((Sa-Token)) 登录认证 多端登录 单点登录SSO OAuth2.0 记住我 权限认证 角色认证 权限认证 会话查询 踢人下线 Session会话 Token风格 自动续签 会话治理 其他功能 微服务鉴权 临时Token 二级认证 密码加密4.3 快速入门4.3.1 添加依赖 cn.dev33 sa-token-spring-boot3-starter 1.38.0 cn.dev33 sa-token-redis-jackson 1.38.0 4.3.2 配置文件sa-token: # Token名称（同时也是Cookie名称） token-name: Authorization # Token有效期（秒），-1代表永不过期 timeout: 86400 # Token临时有效期（指定时间内无操作则过期） active-timeout: 1800 # 是否允许同一账号并发登录 is-concurrent: true # 同一账号最大登录数量 max-login-count: 5 # Token风格 token-style: uuid # 是否输出操作日志 is-log: true4.3.3 登录认证@RestController @RequestMapping("/auth") public class AuthController { @Autowired private UserService userService; /** * 登录 —— 就这么简单！ */ @PostMapping("/login") public SaResult login(@RequestBody LoginRequest req) { User user = userService.findByUsername(req.getUsername()); if (user == null) { return SaResult.error("用户不存在"); } if (!passwordEncoder.matches(req.getPassword(), user.getPassword())) { return SaResult.error("密码错误"); } // 登录：一行代码搞定！ StpUtil.login(user.getId()); // 返回Token return SaResult.data(StpUtil.getTokenValue()); } /** * 登出 */ @PostMapping("/logout") public SaResult logout() { StpUtil.logout(); return SaResult.ok("登出成功"); } /** * 查询登录状态 */ @GetMapping("/is-login") public SaResult isLogin() { return SaResult.ok("是否登录: " + StpUtil.isLogin()); } /** * 获取当前登录用户信息 */ @GetMapping("/info") public SaResult info() { // 获取当前登录用户ID long userId = StpUtil.getLoginIdAsLong(); User user = userService.findById(userId); return SaResult.data(user); } }4.3.4 权限认证首先，实现权限接口：@Component public class StpInterfaceImpl implements StpInterface { @Autowired private UserService userService; /** * 返回用户的权限列表 */ @Override public List<String> getPermissionList(Object loginId, String loginType) { Long userId = Long.parseLong(loginId.toString()); User user = userService.findById(userId); return user.getRoles().stream() .flatMap(role -> role.getPermissions().stream()) .map(Permission::getCode) .distinct() .collect(Collectors.toList()); } /** * 返回用户的角色列表 */ @Override public List<String> getRoleList(Object loginId, String loginType) { Long userId = Long.parseLong(loginId.toString()); User user = userService.findById(userId); return user.getRoles().stream() .map(Role::getName) .collect(Collectors.toList()); } }然后愉快地使用：@RestController @RequestMapping("/api") public class ApiController { /** * 角色校验 */ @GetMapping("/admin/data") public SaResult adminData() { // 校验当前用户是否有admin角色 StpUtil.checkRole("admin"); return SaResult.data("管理员数据"); } /** * 权限校验 */ @GetMapping("/user/delete") public SaResult deleteUser() { // 校验当前用户是否有user:delete权限 StpUtil.checkPermission("user:delete"); return SaResult.ok("删除成功"); } /** * 多角色校验（满足其一） */ @GetMapping("/reports") public SaResult reports() { StpUtil.checkRoleOr("admin", "manager", "analyst"); return SaResult.data("报表数据"); } /** * 多权限校验（全部满足） */ @GetMapping("/sensitive/operation") public SaResult sensitiveOp() { StpUtil.checkPermissionAnd("data:read", "data:write", "data:export"); return SaResult.ok("敏感操作执行成功"); } }4.3.5 注解鉴权@RestController @RequestMapping("/api") public class AnnotationController { // 登录校验 @SaCheckLogin @GetMapping("/user/info") public SaResult userInfo() { return SaResult.data("用户信息"); } // 角色校验 @SaCheckRole("admin") @GetMapping("/admin/settings") public SaResult adminSettings() { return SaResult.data("管理员设置"); } // 权限校验 @SaCheckPermission("user:add") @PostMapping("/user") public SaResult addUser() { return SaResult.ok("添加成功"); } // 多角色校验（mode = OR 表示满足其一） @SaCheckRole(value = {"admin", "manager"}, mode = SaMode.OR) @GetMapping("/dashboard") public SaResult dashboard() { return SaResult.data("仪表盘数据"); } // 二级认证（敏感操作前需要再次验证） @SaCheckSafe @PostMapping("/transfer") public SaResult transfer() { return SaResult.ok("转账成功"); } }4.4 高级功能4.4.1 多端登录// 在不同设备登录时，指定设备类型 StpUtil.login(10001, "PC"); // PC端登录 StpUtil.login(10001, "APP"); // APP端登录 StpUtil.login(10001, "WeChat"); // 微信小程序登录 // 踢掉指定设备 StpUtil.logout(10001, "PC"); // 踢掉所有设备 StpUtil.logout(10001);4.4.2 踢人下线// 踢人下线（根据用户ID） StpUtil.kickout(10001); // 踢人下线（根据Token） StpUtil.kickoutByTokenValue("xxxx-xxxx-xxxx"); // 获取某用户的所有Token List<String> tokens = StpUtil.getTokenValueListByLoginId(10001);4.4.3 同端互斥登录sa-token: # 同端互斥：同一设备类型只能登录一个 is-concurrent: false # 当登录数量超出时，是否踢出旧Token is-share: false五、OAuth 2.0 集成5.1 OAuth 2.0 简介OAuth 2.0 是一个开放授权标准，允许用户授权第三方应用访问其资源，而无需暴露密码。sequenceDiagram participant U as 用户 participant C as 客户端应用 participant A as 授权服务器 participant R as 资源服务器 U->>C: 1. 点击"使用GitHub登录" C->>A: 2. 重定向到授权页面 A->>U: 3. 显示授权页面 U->>A: 4. 用户同意授权 A->>C: 5. 返回授权码(code) C->>A: 6. 用授权码换取Token A->>C: 7. 返回Access Token C->>R: 8. 携带Token请求资源 R->>C: 9. 返回受保护资源 C->>U: 10. 显示资源5.2 Spring Security OAuth2 客户端@Configuration @EnableWebSecurity public class OAuth2ClientConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth .requestMatchers("/", "/login/**").permitAll() .anyRequest().authenticated() ) .oauth2Login(oauth2 -> oauth2 .loginPage("/login") .defaultSuccessUrl("/home") .userInfoEndpoint(userInfo -> userInfo .userService(customOAuth2UserService()) ) ); return http.build(); } @Bean public OAuth2UserService<OAuth2UserRequest, OAuth2User> customOAuth2UserService() { return new CustomOAuth2UserService(); } }配置文件：spring: security: oauth2: client: registration: github: client-id: your-client-id client-secret: your-client-secret scope: read:user,user:email google: client-id: your-google-client-id client-secret: your-google-client-secret scope: openid,profile,email5.3 Sa-Token OAuth2 服务端@Configuration public class SaOAuth2Config { @Autowired public void configOAuth2Server(SaOAuth2Config oauth2Config) { // 配置OAuth2模式 oauth2Config // 授权码模式 .setEnableAuthorizationCode(true) // 隐式模式 .setEnableImplicit(true) // 密码模式 .setEnablePassword(true) // 客户端模式 .setEnableClientCredentials(true); } } @RestController @RequestMapping("/oauth2") public class OAuth2Controller { // 授权端点 @RequestMapping("/authorize") public Object authorize() { return SaOAuth2Handle.serverRequest(); } // Token端点 @RequestMapping("/token") public Object token() { return SaOAuth2Handle.serverRequest(); } }六、安全最佳实践6.1 密码安全@Component public class PasswordHelper { // 使用BCrypt（推荐） private final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(12); /** * 加密密码 */ public String encode(String rawPassword) { return encoder.encode(rawPassword); } /** * 验证密码 */ public boolean matches(String rawPassword, String encodedPassword) { return encoder.matches(rawPassword, encodedPassword); } /** * 密码强度检查 */ public boolean isStrongPassword(String password) { // 至少8位，包含大小写字母、数字、特殊字符 String pattern = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,}$"; return password.matches(pattern); } }6.2 防止常见攻击@Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http // CSRF保护（前后端分离可以关闭，但要使用其他方式） .csrf(csrf -> csrf .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) ) // 安全响应头 .headers(headers -> headers // 防止点击劫持 .frameOptions(frame -> frame.deny()) // XSS保护 .xssProtection(xss -> xss.enable()) // 内容类型嗅探 .contentTypeOptions(content -> {}) // HSTS .httpStrictTransportSecurity(hsts -> hsts .includeSubDomains(true) .maxAgeInSeconds(31536000) ) ) // 其他配置... ; return http.build(); } }6.3 登录安全策略@Service @RequiredArgsConstructor public class LoginSecurityService { private final StringRedisTemplate redisTemplate; private static final int MAX_ATTEMPTS = 5; private static final int LOCK_MINUTES = 30; /** * 检查是否被锁定 */ public boolean isLocked(String username) { String key = "login:locked:" + username; return Boolean.TRUE.equals(redisTemplate.hasKey(key)); } /** * 记录失败次数 */ public void recordFailure(String username) { String key = "login:attempts:" + username; Long attempts = redisTemplate.opsForValue().increment(key); redisTemplate.expire(key, LOCK_MINUTES, TimeUnit.MINUTES); if (attempts != null && attempts >= MAX_ATTEMPTS) { lock(username); } } /** * 锁定账户 */ public void lock(String username) { String key = "login:locked:" + username; redisTemplate.opsForValue().set(key, "1", LOCK_MINUTES, TimeUnit.MINUTES); } /** * 清除失败记录（登录成功后调用） */ public void clearFailures(String username) { redisTemplate.delete("login:attempts:" + username); redisTemplate.delete("login:locked:" + username); } }6.4 敏感数据脱敏public class DataMasker { /** * 手机号脱敏：138****8888 */ public static String maskPhone(String phone) { if (phone == null || phone.length() != 11) { return phone; } return phone.substring(0, 3) + "****" + phone.substring(7); } /** * 身份证脱敏：110***********1234 */ public static String maskIdCard(String idCard) { if (idCard == null || idCard.length() < 8) { return idCard; } return idCard.substring(0, 3) + "***********" + idCard.substring(idCard.length() - 4); } /** * 邮箱脱敏：t***@example.com */ public static String maskEmail(String email) { if (email == null || !email.contains("@")) { return email; } int atIndex = email.indexOf("@"); if (atIndex <= 1) { return email; } return email.charAt(0) + "***" + email.substring(atIndex); } /** * 银行卡脱敏：**** **** **** 1234 */ public static String maskBankCard(String cardNo) { if (cardNo == null || cardNo.length() < 4) { return cardNo; } return "**** **** **** " + cardNo.substring(cardNo.length() - 4); } }七、框架选型建议flowchart TD A[项目类型?] --> B{企业级复杂系统?} B -->|是| C[Spring Security] B -->|否| D{需要快速开发?} D -->|是| E[Sa-Token] D -->|否| F{团队熟悉Shiro?} F -->|是| G[Apache Shiro] F -->|否| H{有Spring环境?} H -->|是| I[Spring Security或 Sa-Token] H -->|否| J[Apache Shiro] C --> K[功能最全,生态最好学习成本高] E --> L[简单易用,国内友好功能够用] G --> M[轻量灵活社区一般]选型总结场景推荐框架理由Spring Boot企业项目Spring Security官方支持，功能全面快速原型/中小项目Sa-Token上手快，中文文档友好非Spring项目Apache Shiro框架无关，轻量灵活需要OAuth2/OIDCSpring Security原生支持最完善微服务架构Sa-Token / Spring Security都有微服务方案学习入门Sa-Token门槛最低，概念清晰八、总结安全框架就像保险，平时觉得没用，出事了追悔莫及。核心要点：认证和授权是两码事：先确认你是谁，再决定你能干啥选择适合的框架：别用牛刀杀鸡，也别用水果刀砍牛安全是系统工程：框架只是一部分，还需要配合其他安全措施持续学习：安全领域日新月异，今天的最佳实践可能明天就过时了最后，无论选择哪个框架，记住这句话："最大的安全漏洞永远是人。" —— 鲁迅（可能没说过）参考资料Spring Security 官方文档Apache Shiro 官方文档Sa-Token 官方文档OAuth 2.0 规范JWT 官方网站