spring security整合jwt的解决方案如下:1. 添加依赖:在pom.xml中添加spring-boot-starter-security和jjwt相关依赖,包括jjwt-api、jjwt-impl和jjwt-jackson;2. 配置spring security:创建securityconfig类继承websecurityconfigureradapter,禁用csrf,设置认证规则为除/authenticate外均需认证,并配置无状态会话管理;3. 创建jwtauthenticationentrypoint:实现authenticationentrypoint接口,用于处理未授权访问,返回401错误;4. 创建jwtrequestfilter:继承onceperrequestfilter,拦截请求提取jwt,验证有效性后设置认证信息到securitycontextholder;5. 创建jwtuserdetailsservice:实现userdetailsservice,根据用户名加载用户信息,实际项目中应从数据库获取;6. 创建jwttokenutil:负责生成、解析和验证jwt,包含生成token、提取用户名、判断过期等功能;7. 创建authenticationcontroller:提供认证接口,接收用户名密码并返回生成的jwt;8. 处理jwt过期问题:通过引入refresh token机制,在用户登录时同时颁发refresh token,并设置专门的刷新端点,验证有效后颁发新jwt;9. 保护jwt secret:避免硬编码,使用环境变量或配置文件存储,定期轮换,使用强密码生成器,限制访问权限并监控使用情况;10. 在微服务架构中使用jwt:可通过api网关统一验证jwt并将用户信息转发给微服务,或每个微服务独立验证jwt,推荐将验证逻辑封装为共享库,并通过配置中心统一管理secret。

Spring Security整合JWT,简单来说,就是让你的应用能够安全地验证用户身份,并且让用户在一段时间内无需重复登录。 JWT就像一张通行证,用户拿着这张通行证就能访问受保护的资源。

直接输出解决方案即可)

添加依赖: 首先,需要在pom.xml中添加Spring Security和JWT相关的依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<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>这里使用了jjwt库,它提供了JWT的生成和解析功能。版本号可以根据实际情况调整。
配置Spring Security: 创建一个配置类,继承WebSecurityConfigurerAdapter,并重写configure(HttpSecurity http)方法。
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Autowired
private JwtRequestFilter jwtRequestFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/authenticate").permitAll() // 允许/authenticate接口匿名访问
.anyRequest().authenticated() // 其他所有请求都需要认证
.and()
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS); // 使用JWT,不创建session
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}这段代码禁用了CSRF,配置了哪些接口允许匿名访问,哪些需要认证,并设置了session管理策略为STATELESS,意味着我们不会使用session来存储用户信息。JwtRequestFilter是用于拦截请求并验证JWT的过滤器,后面会讲到。
创建JwtAuthenticationEntryPoint: 当用户尝试访问需要认证的资源,但未提供有效的凭证时,JwtAuthenticationEntryPoint会被调用。
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {
private static final long serialVersionUID = -7858869558953243875L;
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
}这里简单地返回一个401未授权错误。
创建JwtRequestFilter: 这个过滤器负责拦截每个请求,从请求头中提取JWT,验证其有效性,并设置Spring Security的上下文。
@Component
public class JwtRequestFilter extends OncePerRequestFilter {
@Autowired
private JwtUserDetailsService jwtUserDetailsService;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String requestTokenHeader = request.getHeader("Authorization");
String username = null;
String jwtToken = null;
if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
jwtToken = requestTokenHeader.substring(7);
try {
username = jwtTokenUtil.getUsernameFromToken(jwtToken);
} catch (IllegalArgumentException e) {
System.out.println("Unable to get JWT Token");
} catch (ExpiredJwtException e) {
System.out.println("JWT Token has expired");
}
} else {
logger.warn("JWT Token does not begin with Bearer String");
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.jwtUserDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}这个过滤器首先从Authorization请求头中获取JWT。如果JWT存在且有效,它会从JWT中提取用户名,然后从JwtUserDetailsService加载用户信息,并创建一个UsernamePasswordAuthenticationToken,最后将其设置到SecurityContextHolder中。
创建JwtUserDetailsService: 这个类负责根据用户名从数据库或其他数据源加载用户信息。
@Service
public class JwtUserDetailsService implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 假设从数据库获取用户信息
if ("javahub".equals(username)) {
return new User("javahub", passwordEncoder.encode("password"),
new ArrayList<>());
} else {
throw new UsernameNotFoundException("User not found with username: " + username);
}
}
}这里为了简单起见,直接硬编码了一个用户。实际项目中,你需要从数据库或其他数据源加载用户信息。注意,密码需要使用PasswordEncoder进行加密。
创建JwtTokenUtil: 这个类负责生成和验证JWT。
@Component
public class JwtTokenUtil implements Serializable {
private static final long serialVersionUID = -2550185165626007488L;
public static final long JWT_TOKEN_VALIDITY = 5 * 60 * 60;
@Value("${jwt.secret}")
private String secret;
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}
private Boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
}
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return doGenerateToken(claims, userDetails.getUsername());
}
private String doGenerateToken(Map<String, Object> claims, String subject) {
return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY * 1000))
.signWith(SignatureAlgorithm.HS512, secret).compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
}这个类包含了生成JWT、从JWT中提取信息、验证JWT等方法。secret是用于签名JWT的密钥,应该保密。JWT_TOKEN_VALIDITY是JWT的有效期,这里设置为5小时。
创建AuthenticationController: 这个Controller负责处理用户认证请求,生成JWT并返回给用户。
@RestController
@CrossOrigin
public class AuthenticationController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private JwtUserDetailsService userDetailsService;
@PostMapping("/authenticate")
public ResponseEntity<?> createAuthenticationToken(@RequestBody AuthenticationRequest authenticationRequest) throws Exception {
authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
final UserDetails userDetails = userDetailsService
.loadUserByUsername(authenticationRequest.getUsername());
final String token = jwtTokenUtil.generateToken(userDetails);
return ResponseEntity.ok(new AuthenticationResponse(token));
}
private void authenticate(String username, String password) throws Exception {
try {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
} catch (DisabledException e) {
throw new Exception("USER_DISABLED", e);
} catch (BadCredentialsException e) {
throw new Exception("INVALID_CREDENTIALS", e);
}
}
}
class AuthenticationRequest {
private String username;
private String password;
// Getters and setters
}
class AuthenticationResponse {
private final String jwt;
public AuthenticationResponse(String jwt) {
this.jwt = jwt;
}
// Getter
}这个Controller接收用户名和密码,使用AuthenticationManager进行认证。如果认证成功,它会使用JwtTokenUtil生成JWT,并将其返回给用户。
JWT过期是使用JWT认证时必须考虑的问题。一个常见的解决方案是使用refresh token。 当用户的JWT即将过期时,可以使用refresh token来获取一个新的JWT,而无需用户重新登录。 实现refresh token机制通常涉及以下步骤:
/refresh-token)。/refresh-token端点。 服务器验证refresh token是否有效(例如,检查它是否存在于数据库中,并且未被撤销)。此外,也可以考虑使用滑动过期策略,即每次用户访问受保护的资源时,都更新JWT的过期时间。
保护JWT的secret至关重要,因为如果secret泄露,攻击者可以伪造JWT,冒充任何用户。 以下是一些保护JWT secret的最佳实践:
在微服务架构中使用JWT可以实现单点登录(SSO)和授权。 一种常见的做法是使用API网关作为JWT的验证中心。
另一种做法是每个微服务都验证JWT。 这种做法的优点是微服务可以独立地进行授权,缺点是每个微服务都需要维护JWT的验证逻辑。 为了避免代码重复,可以将JWT的验证逻辑提取到一个共享库中。
无论使用哪种做法,都需要确保JWT的secret在所有微服务中都是一致的。 可以使用配置中心来管理JWT的secret。
另外,可以考虑使用OAuth 2.0和OpenID Connect等标准协议来实现更安全和灵活的认证和授权。
以上就是Spring Security整合JWT的详细配置与实现的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号