在前后端分离的项目中,Spring Securit
y 与 JWT(JSON Web Token)
是常用的组合,用于实现认证和授权
。通过 JWT,服务器无需保存用户的会话状态,前端在每次请求时携带 Token,服务器根据 Token 验证用户身份
。以下是一个实现步骤:
项目结构
src
├── main
│ ├── java
│ │ └── com
│ │ └── example
│ │ └── jwtsecurity
│ │ ├── config # 配置文件
│ │ │ ├── SecurityConfig.java # Spring Security 配置
│ │ │ └── JwtAuthenticationEntryPoint.java # 未授权请求处理
│ │ ├── controller # 控制器
│ │ │ └── AuthController.java # 用户登录、注册等控制器
│ │ ├── dto # 数据传输对象
│ │ │ └── AuthRequest.java # 认证请求数据结构
│ │ ├── model # 数据模型
│ │ │ └── User.java # 用户实体
│ │ ├── repository # 数据库操作接口
│ │ │ └── UserRepository.java # 用户数据仓库
│ │ ├── security # 安全性相关的类
│ │ │ ├── JwtTokenProvider.java # JWT 令牌生成与验证
│ │ │ ├── JwtTokenFilter.java # JWT 过滤器
│ │ │ └── CustomUserDetailsService.java # 用户信息加载服务
│ │ ├── service # 业务逻辑服务
│ │ │ └── AuthService.java # 认证服务
│ │ └── JwtSecurityApplication.java # 启动类
│ └── resources
│ └── application.properties # 应用配置文件
└── test # 测试文件
1. 添加依赖
首先,在 pom.xml 中添加 Spring Security 和 JWT 相关的依赖。
xml
<dependencies>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- JWT 依赖 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!-- Spring Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
2. 编写 JWT 工具类
JwtTokenUtil 类用于生成、解析和验证 JWT Token。
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
@Component
public class JwtTokenUtil {
private static final String SECRET_KEY = "your_secret_key";
private static final long EXPIRATION_TIME = 1000 * 60 * 60 * 10; // Token有效期10小时
// 生成 Token
public String generateToken(String username) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, username);
}
// 从 Token 中获取用户名
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
// 验证 Token 是否过期
public Boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
private Claims extractAllClaims(String token) {
return Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(token)
.getBody();
}
private Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
private <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
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() + EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
}
}
3. 创建 UserDetailsService 实现类
UserDetailsService 用于加载用户的详细信息。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Service
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository; // 假设有一个用户数据库
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("User not found");
}
return new MyUserDetails(user);
}
}
4. 创建 JwtRequestFilter 过滤器
过滤器负责拦截每个请求,检查其中的 JWT 是否有效。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import io.jsonwebtoken.ExpiredJwtException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class JwtRequestFilter extends OncePerRequestFilter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String authorizationHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7);
try {
username = jwtTokenUtil.extractUsername(jwt);
} catch (ExpiredJwtException e) {
// 处理 Token 过期情况
}
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(jwt, userDetails)) {
// 设置认证
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken.setDetails(
new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
5. 配置 Spring Security
配置 Spring Security 以使其使用 JWT 进行认证。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyUserDetailsService myUserDetailsService;
@Autowired
private JwtRequestFilter jwtRequestFilter;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService).passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests().antMatchers("/authenticate").permitAll()
.anyRequest().authenticated()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
6. 生成和验证 JWT
在控制器中创建 authenticate 接口,生成 JWT Token。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.web.bind.annotation.*;
@RestController
public class AuthenticationController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private MyUserDetailsService userDetailsService;
@PostMapping("/authenticate")
public String createAuthenticationToken(@RequestBody AuthenticationRequest authenticationRequest) throws Exception {
try {
Authentication authenticate = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(authenticationRequest.getUsername(), authenticationRequest.getPassword())
);
} catch (AuthenticationException e) {
throw new Exception("Incorrect username or password", e);
}
final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
final String jwt = jwtTokenUtil.generateToken(userDetails.getUsername());
return jwt;
}
}
总结
前端登录后,向后端发送用户名和密码,后端验证通过后生成 JWT 返回给前端。
前端将 JWT 保存在 localStorage 或 sessionStorage 中,在每次请求时,将其添加到 Authorization 请求头中。
后端通过过滤器解析 JWT,验证其合法性,并根据 Token 提取用户信息。