安全

返回 Spring Boot 基础

Spring Security 是 Spring Boot 默认的安全框架,提供认证(Authentication)与授权(Authorization)能力。引入 spring-boot-starter-security 后自动生效。


核心概念

请求
  │
  ▼
Filter Chain(安全过滤器链)
  ├─ SecurityContextPersistenceFilter   # 从 Session/Token 恢复上下文
  ├─ UsernamePasswordAuthenticationFilter  # 表单登录
  ├─ BearerTokenAuthenticationFilter   # JWT / OAuth2 Token
  ├─ ExceptionTranslationFilter        # 统一处理 401/403
  └─ FilterSecurityInterceptor         # 授权决策
  │
  ▼
Controller / 业务逻辑
概念说明
Authentication当前登录用户信息(Principal + 权限列表)
SecurityContext存放当前请求的 Authentication,线程本地
UserDetails用户详情接口(用户名、密码、权限、账号状态)
GrantedAuthority权限/角色(如 ROLE_ADMIN
AuthenticationManager认证入口,委托给 AuthenticationProvider

基本配置

@Configuration
@EnableWebSecurity
public class SecurityConfig {
 
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable())             // REST API 通常禁用
            .sessionManagement(s -> s
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS))  // JWT 场景无状态
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/auth/**").permitAll()   // 公开接口
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )
            .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }
 
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();           // 详见:密码加密
    }
 
    @Bean
    public AuthenticationManager authManager(AuthenticationConfiguration config)
            throws Exception {
        return config.getAuthenticationManager();
    }
}

自定义用户服务

@Service
public class UserDetailsServiceImpl implements UserDetailsService {
 
    private final UserRepository userRepo;
 
    @Override
    public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException {
        User user = userRepo.findByUsername(username)
            .orElseThrow(() -> new UsernameNotFoundException("用户不存在: " + username));
 
        return org.springframework.security.core.userdetails.User.builder()
            .username(user.getUsername())
            .password(user.getPassword())             // 已加密的密码
            .roles(user.getRoles().toArray(String[]::new))
            .accountExpired(!user.isActive())
            .build();
    }
}

JWT 认证流程

登录请求 POST /api/auth/login
  │  username + password
  ▼
AuthenticationManager.authenticate()
  │  验证通过
  ▼
生成 JWT Token(accessToken + refreshToken)
  │
  ▼
客户端请求携带 Header: Authorization: Bearer <token>
  │
  ▼
JwtAuthFilter 解析 Token → 写入 SecurityContext
  │
  ▼
Controller 正常处理

JWT 集成详见 OAuth2 与 JWT


获取当前用户

// 方式一:SecurityContextHolder
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
String username = auth.getName();
 
// 方式二:Controller 参数注入(推荐)
@GetMapping("/me")
public UserInfo me(@AuthenticationPrincipal UserDetails user) {
    return userService.findByUsername(user.getUsername());
}
 
// 方式三:自定义注解封装(常见于大型项目)
@GetMapping("/me")
public UserInfo me(@CurrentUser LoginUser user) { ... }

授权

URL 级授权

SecurityFilterChain 中配置(见基本配置),常用表达式:

表达式说明
permitAll()完全公开
authenticated()已登录即可
hasRole("ADMIN")拥有 ROLE_ADMIN 权限
hasAnyRole("ADMIN","USER")拥有任意一个角色
hasAuthority("user:delete")精确权限字符串
denyAll()完全禁止

方法级授权

详见 方法级安全

@EnableMethodSecurity   // 启用方法级安全(配置类上)
 
@PreAuthorize("hasRole('ADMIN')")
public void deleteUser(Long id) { ... }
 
@PreAuthorize("hasAuthority('order:read') or #userId == authentication.principal.id")
public Order getOrder(Long userId, Long orderId) { ... }
 
@PostAuthorize("returnObject.owner == authentication.name")
public Document getDocument(Long id) { ... }

CSRF

REST API(前后端分离 + 无状态)通常禁用 CSRF:

http.csrf(csrf -> csrf.disable());

传统 MVC(Session + 表单)需保留 CSRF,Thymeleaf 会自动注入 Token,详见 Thymeleaf


异常处理

http.exceptionHandling(ex -> ex
    .authenticationEntryPoint((req, res, e) -> {
        // 401 未认证
        res.sendError(HttpServletResponse.SC_UNAUTHORIZED, "请先登录");
    })
    .accessDeniedHandler((req, res, e) -> {
        // 403 无权限
        res.sendError(HttpServletResponse.SC_FORBIDDEN, "权限不足");
    })
);

可配合 全局异常处理 统一返回 JSON 格式错误响应。


相关链接