使用Spring Boot与Spring Security构建安全的RESTful API
引言
在现代Web开发中,安全性是一个不可忽视的重要方面。Spring Boot和Spring Security为开发者提供了一套强大的工具,用于构建安全的RESTful API。本文将详细介绍如何结合Spring Boot和Spring Security,并使用JWT(JSON Web Token)实现身份验证与授权。
技术栈
- 核心框架: Spring Boot 3.x, Spring Security 6.x
- 身份验证: JWT (JSON Web Token)
- 数据库: H2 (用于演示)
- 构建工具: Maven
项目初始化
首先,我们需要创建一个Spring Boot项目。可以通过Spring Initializr(https://start.spring.io/)快速生成项目骨架。选择以下依赖:
- Spring Web
- Spring Security
- H2 Database
- Lombok
配置Spring Security
Spring Security默认会为所有端点启用基本身份验证。我们需要自定义配置以实现JWT验证。
1. 添加JWT依赖
在pom.xml
中添加以下依赖:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
2. 创建JWT工具类
编写一个工具类用于生成和验证JWT:
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
@Component
public class JwtUtil {
private final Key secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256);
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, userDetails.getUsername());
}
private String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) // 10小时有效期
.signWith(secretKey)
.compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
private Boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
public Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
private <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
private Claims extractAllClaims(String token) {
return Jwts.parserBuilder().setSigningKey(secretKey).build().parseClaimsJws(token).getBody();
}
}
3. 配置Spring Security
创建一个配置类,继承WebSecurityConfigurerAdapter
并覆盖相关方法:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/authenticate").permitAll()
.anyRequest().authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
实现身份验证
创建一个控制器用于处理用户登录和生成JWT:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AuthController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtUtil jwtUtil;
@PostMapping("/authenticate")
public ResponseEntity<String> authenticate(@RequestBody AuthRequest authRequest) throws Exception {
try {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(authRequest.getUsername(), authRequest.getPassword())
);
} catch (Exception e) {
throw new Exception("Invalid credentials", e);
}
final UserDetails userDetails = userDetailsService.loadUserByUsername(authRequest.getUsername());
final String jwt = jwtUtil.generateToken(userDetails);
return ResponseEntity.ok(jwt);
}
}
测试API
使用Postman或curl测试以下端点:
-
登录:
POST /authenticate { "username": "admin", "password": "password" }
返回的JWT可用于后续请求的
Authorization
头。 -
访问受保护端点:
GET /api/protected Authorization: Bearer <JWT>
总结
本文详细介绍了如何使用Spring Boot和Spring Security构建安全的RESTful API,并结合JWT实现身份验证与授权。通过实际代码示例,展示了从零开始搭建一个安全的后端服务的完整流程。