JWT(JSON Web Token)是一种用于在网络中传输安全信息的开放标准(RFC 7519)。它可以在各个服务之间安全地传递用户认证信息,因为它使用数字签名来验证信息的真实性和完整性。
JWT有三个部分,每个部分用点(.)分隔:
在Spring Boot中,您可以使用Spring Security和jjwt库来实现JWT的认证和授权。下面是一个使用JWT的示例:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Value("${jwt.secret}")
private String jwtSecret;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/api/authenticate").permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new JwtAuthenticationFilter(authenticationManager(), jwtSecret))
.addFilter(new JwtAuthorizationFilter(authenticationManager(), jwtSecret))
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(new JwtAuthenticationProvider(jwtSecret));
}
}
在上面的示例中,SecurityConfig
类继承了WebSecurityConfigurerAdapter
并使用了@EnableWebSecurity
注解启用Spring Security。configure()
方法使用HttpSecurity
对象来配置HTTP请求的安全性。.csrf().disable()
禁用了CSRF保护。.authorizeRequests()
表示进行授权请求。.antMatchers(HttpMethod.POST, "/api/authenticate").permitAll()
表示允许POST请求到/api/authenticate
路径。.anyRequest().authenticated()
表示要求所有其他请求都需要身份验证。.addFilter(new JwtAuthenticationFilter(authenticationManager(), jwtSecret))
和.addFilter(new JwtAuthorizationFilter(authenticationManager(), jwtSecret))
分别添加JWT认证和授权过滤器。.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
指定了会话管理策略。
configure()
方法中还有一个configure(AuthenticationManagerBuilder auth)
方法,它使用JwtAuthenticationProvider
类配置身份验证。在这里,jwtSecret
被注入到JwtAuthenticationProvider
构造函数中,以便在认证过程中使用。
下面是JwtAuthenticationFilter
和JwtAuthorizationFilter
的实现:
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private final AuthenticationManager authenticationManager;
private final String jwtSecret;
public JwtAuthenticationFilter(AuthenticationManager authenticationManager, String jwtSecret) {
this.authenticationManager = authenticationManager;
this.jwtSecret = jwtSecret;
setFilterProcessesUrl("/api/authenticate");
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) {
try {
LoginRequest loginRequest = new ObjectMapper().readValue(request.getInputStream(), LoginRequest.class);
Authentication authentication = new UsernamePasswordAuthenticationToken(
loginRequest.getUsername(),
loginRequest.getPassword()
);
return authenticationManager.authenticate(authentication);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) {
UserPrincipal userPrincipal = (UserPrincipal) authResult.getPrincipal();
String token = Jwts.builder()
.setSubject(userPrincipal.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 864000000))
.signWith(SignatureAlgorithm.HS512, jwtSecret)
.compact();
response.addHeader("Authorization", "Bearer " + token);
}
}
JwtAuthenticationFilter
类继承了UsernamePasswordAuthenticationFilter
类,它用于处理基于用户名和密码的身份验证。它还使用AuthenticationManager
来验证用户名和密码是否正确。jwtSecret
在构造函数中被注入,用于生成JWT令牌。
在attemptAuthentication()
方法中,LoginRequest
对象被反序列化为从请求中获取的用户名和密码。这些值被封装到UsernamePasswordAuthenticationToken
中,并传递给AuthenticationManager
以验证用户身份。
在身份验证成功后,successfulAuthentication()
方法被调用。在这里,UserPrincipal
对象被从Authentication
对象中获取,然后使用Jwts
类生成JWT令牌。setSubject()
方法将用户名设置为JWT主题。setIssuedAt()
方法设置JWT令牌的发行时间。setExpiration()
方法设置JWT令牌的到期时间。signWith()
方法使用HS512算法和jwtSecret
密钥对JWT令牌进行签名。最后,JWT令牌被添加到响应标头中。
下面是JwtAuthorizationFilter
的实现:
public class JwtAuthorizationFilter extends BasicAuthenticationFilter {
private final String jwtSecret;
public JwtAuthorizationFilter(AuthenticationManager authenticationManager, String jwtSecret) {
super(authenticationManager);
this.jwtSecret = jwtSecret;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String authorizationHeader = request.getHeader("Authorization");
if (authorizationHeader == null || !authorizationHeader.startsWith("Bearer ")) {
chain.doFilter(request, response);
return;
}
String token = authorizationHeader.replace("Bearer ", "");
try {
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token);
String username = claimsJws.getBody().getSubject();
List<String> authorities = (List<String>) claimsJws.getBody().get("authorities");
List<GrantedAuthority> grantedAuthorities = authorities.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
Authentication authentication = new UsernamePasswordAuthenticationToken(username, null, grantedAuthorities);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request, response);
} catch (JwtException e) {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
}
}
}
JwtAuthorizationFilter
类继承了BasicAuthenticationFilter
类,并覆盖了doFilterInternal()
方法。在这个方法中,请求头中的Authorization
标头被解析,如果它不是以Bearer
开头,则直接传递给过滤器链。否则,从令牌中解析出主题(用户名)和授权信息,然后创建一个包含用户身份验证和授权信息的Authentication
对象,并将其设置到SecurityContextHolder
中。
如果JWT令牌无效,JwtException
将被抛出,并返回HTTP 401未经授权的错误。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。