文档站点
Skip to content

认证 ​

Laravel 用 Breeze/Jetstream 或 Auth::attempt() + Session/JWT;Spring Boot 的标配是 Spring Security。这篇用「从零写一个 JWT 登录」走通认证主流程,帮 PHP 开发者理解 Spring Security 的核心思想:SecurityFilterChain 过滤器链 + AuthenticationManager。

先理解 Spring Security 的「过滤链」思想 ​

Laravel 的认证是「中间件 + Auth 门面」;Spring Security 是一整条过滤器链,每个请求依次穿过:

请求 → UsernamePasswordAuthenticationFilter(找登录凭证)
     → 校验 → SecurityContext(存当前用户)
     → 过滤器链放行 → Controller

核心概念对照:

LaravelSpring Security
Auth::attempt()AuthenticationManager.authenticate(...)
auth()->user()SecurityContextHolder.getContext().getAuthentication()
auth()->check()SecurityContextHolder.getContext().getAuthentication() != null
中间件 auth过滤器链的鉴权逻辑
Auth::logout()清理 SecurityContext

完整实现 JWT 登录 ​

依赖 ​

xml
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- JWT -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.12.5</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.12.5</version>
    <scope>runtime</scope>
</dependency>

1. 密码加密器 ​

java
@Configuration
public class SecurityConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

2. 登录接口(手动认证) ​

java
@RestController
@RequestMapping("/api/auth")
public class AuthController {
    private final AuthenticationManager authManager;
    private final JwtService jwtService;
    private final UserRepository userRepository;
    private final PasswordEncoder encoder;

    public AuthController(AuthenticationManager authManager, JwtService jwtService,
                          UserRepository userRepository, PasswordEncoder encoder) {
        this.authManager = authManager;
        this.jwtService = jwtService;
        this.userRepository = userRepository;
        this.encoder = encoder;
    }

    @PostMapping("/login")
    public LoginResponse login(@RequestBody LoginRequest req) {
        // 1. 交给 Spring Security 认证
        Authentication auth = authManager.authenticate(
                new UsernamePasswordAuthenticationToken(req.getUsername(), req.getPassword()));
        // 2. 认证通过,签发 JWT
        String token = jwtService.generate(auth.getName());
        return new LoginResponse(token);
    }

    @PostMapping("/register")
    public User register(@RequestBody RegisterRequest req) {
        if (userRepository.findByUsername(req.getUsername()).isPresent()) {
            throw BusinessException.badRequest("用户名已存在");
        }
        User user = new User();
        user.setUsername(req.getUsername());
        user.setPassword(encoder.encode(req.getPassword()));   // 存哈希,绝不存明文
        return userRepository.save(user);
    }
}

TIP

authManager.authenticate(...) ≈ Auth::attempt(['username' => ..., 'password' => ...])。区别:Laravel 的 attempt 会自己查用户、比对哈希;Spring 把「查用户」和「比密码」交给两个可替换组件(UserDetailsService + PasswordEncoder),更模块化。

3. UserDetailsService:查用户(≈ Laravel 的 Auth::user() 数据来源) ​

java
@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    private final UserRepository userRepository;

    public UserDetailsServiceImpl(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username)
                .orElseThrow(() -> new UsernameNotFoundException("用户不存在"));
        return org.springframework.security.core.userdetails.User
                .withUsername(user.getUsername())
                .password(user.getPassword())
                .roles(user.getRole())
                .build();
    }
}

对照

UserDetailsService.loadUserByUsername ≈ Laravel 认证时框架调 retrieveByCredentials 的地方。Spring Security 规定:你要告诉它「怎么按用户名找到用户」,它就负责密码比对和会话建立。

4. JwtService:签发与校验 ​

java
@Service
public class JwtService {
    private final SecretKey key = Keys.hmacShaKeyFor(
            "super-secret-key-at-least-32-characters-long!".getBytes(StandardCharsets.UTF_8));

    public String generate(String username) {
        return Jwts.builder()
                .subject(username)
                .issuedAt(new Date())
                .expiration(new Date(System.currentTimeMillis() + 3600_000))  // 1 小时
                .signWith(key)
                .compact();
    }

    public String extractUsername(String token) {
        return Jwts.parser().verifyWith(key).build()
                .parseSignedClaims(token).getPayload().getSubject();
    }

    public boolean isValid(String token, UserDetails user) {
        return extractUsername(token).equals(user.getUsername());
    }
}

TIP

JWT 是无状态的:服务端不存 Session,Token 自己携带用户信息。对应 Laravel 的 Tymon/JWT 或 Sanctum 的 token 模式。前后端分离 API 首选。

5. JWT 认证过滤器:每个请求自动带上用户 ​

java
@Component
public class JwtAuthFilter extends OncePerRequestFilter {

    private final JwtService jwtService;
    private final UserDetailsService userDetailsService;

    public JwtAuthFilter(JwtService jwtService, UserDetailsService userDetailsService) {
        this.jwtService = jwtService;
        this.userDetailsService = userDetailsService;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) throws ServletException, IOException {
        String header = request.getHeader("Authorization");
        if (header != null && header.startsWith("Bearer ")) {
            String token = header.substring(7);
            try {
                String username = jwtService.extractUsername(token);
                UserDetails user = userDetailsService.loadUserByUsername(username);
                UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
                        user, null, user.getAuthorities());
                auth.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(auth);   // 放进安全上下文
            } catch (Exception e) {
                // token 无效,不设置认证
            }
        }
        chain.doFilter(request, response);
    }
}

6. SecurityFilterChain 配置(核心) ​

java
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http,
                                                   JwtAuthFilter jwtAuthFilter) throws Exception {
        http
            .csrf(csrf -> csrf.disable())                          // API 无状态,关掉 CSRF
            .sessionManagement(sm -> sm.sessionCreationPolicy(
                    SessionCreationPolicy.STATELESS))              // 无 Session,全走 JWT
            .authorizeHttpRequests(auth -> auth
                    .requestMatchers("/api/auth/**").permitAll()   // 登录注册放行
                    .anyRequest().authenticated())                 // 其余全部要登录
            .addFilterBefore(jwtAuthFilter,
                    UsernamePasswordAuthenticationFilter.class);    // JWT 过滤器插在认证之前
        return http.build();
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration config)
            throws Exception {
        return config.getAuthenticationManager();
    }
}

这段配置就是 Spring Security 的「主心骨」,解释每行:

代码类比 Laravel
.csrf(csrf -> csrf.disable())API 场景关 CSRF(Session 场景要开)
STATELESS不用 Session 存登录态
.requestMatchers("/api/auth/**").permitAll()->withoutMiddleware('auth') 或放行登录路由
.anyRequest().authenticated()其余路由都要登录

控制器里拿当前用户 ​

java
@GetMapping("/me")
public User me(Authentication authentication) {
    // authentication.getName() = 当前登录用户名
    UserDetails user = (UserDetails) authentication.getPrincipal();
    return userRepository.findByUsername(user.getUsername()).orElseThrow();
}

对应 Laravel 的 auth()->user()。

也可以直接从 SecurityContext 取

SecurityContextHolder.getContext().getAuthentication() 在任何地方都能拿到当前用户(包括 Service 层),类似 Laravel 的 Auth::user() 全局访问。

完整流程串联 ​

POST /api/auth/login
  → UsernamePasswordAuthenticationFilter(被我们关闭)
  → AuthController.login()
  → authManager.authenticate() → UserDetailsService 查用户 + PasswordEncoder 比对
  → 通过 → 签发 JWT 返回前端

GET /api/posts(带 Authorization: Bearer xxx)
  → JwtAuthFilter 解析 token → 设置 SecurityContext
  → 过滤器链 → authorizeHttpRequests 校验已认证 → Controller

对照速查 ​

需求LaravelSpring Security
注册User::create + Hash::makeencoder.encode(...) 存库
登录Auth::attempt()authManager.authenticate(...)
当前用户auth()->user()Authentication 参数 / SecurityContext
是否登录auth()->check()authentication != null && authenticated
登出Auth::logout()清理 SecurityContext + 前端丢 token
路由保护->middleware('auth').anyRequest().authenticated()

更细的授权控制(角色、权限注解)见 授权,Spring Security 更完整的过滤器链机制见 Spring Security 深入。

面向 PHP / Laravel 开发者的 Spring Boot 中文文档