Skip to content

认证


Spring Security 概览

Spring Security 是 Java 生态的事实标准安全框架。它的架构和 Laravel 的 Auth 系统有本质区别。

Laravel AuthSpring Security
auth()->attempt(['email' => ..., 'password' => ...])AuthenticationManager.authenticate(token)
Auth::user()SecurityContextHolder.getContext().getAuthentication()
auth()->check()SecurityContextHolder.getContext().getAuthentication() != null
中间件 authSecurityFilterChain 的 .authenticated()
php artisan make:auth自动配置 + Java Config

⚠️ 最大的差异:过滤器链

Laravel 中,认证是在路由中间件中触发——你主动调 auth()->attempt()

Spring Security 中,认证是一个过滤器链(Filter Chain),每个请求自动经过一系列过滤器:

请求


SecurityContextPersistenceFilter  ← 恢复已登录用户的 SecurityContext


UsernamePasswordAuthenticationFilter  ← 处理 /login 的 POST 请求(表单登录)


BasicAuthenticationFilter    ← 处理 HTTP Basic Auth 头


... 其他过滤器 ...


FilterSecurityInterceptor    ← 检查资源权限(@PreAuthorize 在这里生效)


控制器

这意味着:Spring Security 不是在你调用 auth()->attempt() 时才工作,而是每个请求都经过 Security 过滤器链。你可能还没写任何认证代码,所有请求就已经被拦截了(Spring Security 默认拦截所有请求)。


快速入门:最简单的配置

1. 添加依赖

xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

2. 配置 Security

java
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/login", "/api/register").permitAll()  // 公开接口
                .anyRequest().authenticated()                                // 其他需要登录
            )
            .csrf(csrf -> csrf.disable())                                      // API 项目关掉 CSRF
            .sessionManagement(session ->
                session.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 无状态(JWT)
            );
        return http.build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();  // 类比 Hash::make()
    }
}

JWT 认证(API 项目最常用)

1. 添加依赖

xml
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.12.5</version>
</dependency>

2. 登录接口

java
@RestController
@RequestMapping("/api")
public class AuthController {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private JwtTokenProvider tokenProvider;

    @PostMapping("/login")
    public ResponseEntity<JwtResponse> login(@RequestBody LoginRequest request) {

        // 类比 auth()->attempt(['email' => $request->email, 'password' => $request->password])
        Authentication authentication = authenticationManager.authenticate(
            new UsernamePasswordAuthenticationToken(
                request.getEmail(),
                request.getPassword()
            )
        );

        String token = tokenProvider.generateToken(authentication);
        return ResponseEntity.ok(new JwtResponse(token));
    }
}

3. JWT Token 提供者

java
@Component
public class JwtTokenProvider {

    private final String secret = "your-secret-key-at-least-256-bits-long";
    private final long expiration = 86400000; // 24h

    public String generateToken(Authentication authentication) {
        String email = authentication.getName();
        Date now = new Date();
        Date expiry = new Date(now.getTime() + expiration);

        return Jwts.builder()
                .subject(email)
                .issuedAt(now)
                .expiration(expiry)
                .signWith(new SecretKeySpec(secret.getBytes(), SignatureAlgorithm.HS256.getJcaName()))
                .compact();
    }

    public String getEmailFromToken(String token) {
        return Jwts.parser()
                .verifyWith(new SecretKeySpec(secret.getBytes(), SignatureAlgorithm.HS256.getJcaName()))
                .build()
                .parseSignedClaims(token)
                .getPayload()
                .getSubject();
    }

    public boolean validateToken(String token) {
        try {
            Jwts.parser()
                .verifyWith(new SecretKeySpec(secret.getBytes(), SignatureAlgorithm.HS256.getJcaName()))
                .build()
                .parseSignedClaims(token);
            return true;
        } catch (JwtException e) {
            return false;
        }
    }
}

4. JWT 过滤器(将 Token 转为 Authentication)

java
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Autowired
    private JwtTokenProvider tokenProvider;

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain)
            throws ServletException, IOException {

        String token = extractToken(request);

        if (token != null && tokenProvider.validateToken(token)) {
            String email = tokenProvider.getEmailFromToken(token);
            UserDetails userDetails = userDetailsService.loadUserByUsername(email);

            UsernamePasswordAuthenticationToken authentication =
                new UsernamePasswordAuthenticationToken(
                    userDetails, null, userDetails.getAuthorities()
                );
            authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

            // 相当于 Auth::setUser($user)
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }

        filterChain.doFilter(request, response);
    }

    private String extractToken(HttpServletRequest request) {
        String header = request.getHeader("Authorization");
        if (header != null && header.startsWith("Bearer ")) {
            return header.substring(7);
        }
        return null;
    }
}

5. 注册过滤器

java
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Autowired
    private JwtAuthenticationFilter jwtFilter;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -> csrf.disable())
            .sessionManagement(session ->
                session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/login", "/api/register").permitAll()
                .anyRequest().authenticated()
            )
            .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
            // ⬆ 把 JWT 过滤器加到 UsernamePasswordAuthenticationFilter 之前
        return http.build();
    }
}

获取当前用户

java
// 方式一:从 SecurityContext 获取
@GetMapping("/me")
public User me() {
    Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    String email = auth.getName();                      // 当前用户的 email
    UserDetails userDetails = (UserDetails) auth.getPrincipal();  // 用户详情
    return userService.findByEmail(email);
}

// 方式二:方法参数注入(推荐)
@GetMapping("/me")
public User me(@AuthenticationPrincipal UserDetails userDetails) {
    // ⬆ @AuthenticationPrincipal 直接从 SecurityContext 取
    return userService.findByEmail(userDetails.getUsername());
}

// 方式三:@AuthenticationPrincipal + 自定义 User 类
@GetMapping("/profile")
public User profile(@AuthenticationPrincipal User user) {
    return user;
}

⚠️ 常见坑

1. Spring Security 依赖一加上,所有接口都被拦截

加上 spring-boot-starter-security 后,不配任何代码,所有接口都返回 401。不像 Laravel 的 auth 中间件,加在哪里哪里才需要认证。Spring Security 默认是"全拦截,白名单放行"。

2. CSRF 默认开启

POST/PUT/DELETE 请求如果没有 CSRF Token 会返回 403。API 项目通常直接禁掉:

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

3. 密码加密是强制的

java
// Spring Security 不允许用明文密码
// 必须配置 PasswordEncoder
@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();  // 类似 Hash::make()
}

4. UserDetailsService 需要自己实现

Spring Security 不知道你的用户存在哪张表里,需要你告诉它:

java
@Service
public class CustomUserDetailsService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String email) {
        User user = userMapper.selectOne(
            new LambdaQueryWrapper<User>().eq(User::getEmail, email));
        if (user == null) throw new UsernameNotFoundException("User not found");
        return new org.springframework.security.core.userdetails.User(
            user.getEmail(), user.getPassword(), emptyList());
    }
}

面向 PHP 开发者的 Spring Boot 文档