认证
Spring Security 概览
Spring Security 是 Java 生态的事实标准安全框架。它的架构和 Laravel 的 Auth 系统有本质区别。
| Laravel Auth | Spring Security |
|---|---|
auth()->attempt(['email' => ..., 'password' => ...]) | AuthenticationManager.authenticate(token) |
Auth::user() | SecurityContextHolder.getContext().getAuthentication() |
auth()->check() | SecurityContextHolder.getContext().getAuthentication() != null |
中间件 auth | SecurityFilterChain 的 .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 项目通常直接禁掉:
javahttp.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()); } }