使用Spring Boot和Spring Security结合JWT实现安全的RESTful API
引言
在现代Web应用中,安全性是一个不可忽视的重要方面。Spring Boot和Spring Security是Java生态系统中广泛使用的框架,而JWT(JSON Web Token)则是一种轻量级的身份验证和授权机制。本文将详细介绍如何结合这三者来实现一个安全的RESTful API。
技术栈
- 核心框架: Spring Boot, Spring Security
- 身份验证: JWT
- 数据库: H2 (用于演示)
- 工具: Maven, Postman
实现步骤
1. 初始化Spring Boot项目
首先,使用Spring Initializr创建一个新的Spring Boot项目,添加以下依赖:
- Spring Web
- Spring Security
- H2 Database (可选)
- Lombok (可选)
2. 配置Spring Security
在application.properties
中配置JWT相关的密钥和过期时间:
jwt.secret=mySecretKey
jwt.expiration=86400000
3. 实现JWT工具类
创建一个JwtTokenUtil
类,用于生成和验证JWT令牌:
public class JwtTokenUtil {
private String secret;
private Long expiration;
// 生成Token
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return Jwts.builder()
.setClaims(claims)
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + expiration))
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
// 验证Token
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
// 提取用户名
public String extractUsername(String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody().getSubject();
}
// 检查Token是否过期
private Boolean isTokenExpired(String token) {
final Date expiration = extractExpiration(token);
return expiration.before(new Date());
}
// 提取过期时间
private Date extractExpiration(String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody().getExpiration();
}
}
4. 实现自定义UserDetailsService
创建一个CustomUserDetailsService
类,用于加载用户信息:
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 这里可以从数据库加载用户信息
return new User("admin", "password", new ArrayList<>());
}
}
5. 配置Spring Security
创建一个SecurityConfig
类,配置Spring Security:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/authenticate").permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new JwtAuthenticationFilter(authenticationManager(), jwtTokenUtil));
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
6. 实现JWT认证过滤器
创建一个JwtAuthenticationFilter
类,用于拦截请求并验证JWT令牌:
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private JwtTokenUtil jwtTokenUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
final String token = request.getHeader("Authorization");
if (token != null && jwtTokenUtil.validateToken(token, userDetails)) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
}
7. 测试API
使用Postman测试API,确保以下功能正常工作:
- 获取JWT令牌
- 使用令牌访问受保护的资源
总结
通过本文,我们学习了如何使用Spring Boot和Spring Security结合JWT实现安全的RESTful API。这种方法不仅简单高效,而且可以轻松扩展到更复杂的场景中。