Spring Security是一个强大的安全性框架,它提供了许多强大的功能来保护应用程序,而JWT(JSON Web Token)是一种用于在网络环境中传递声明的开放标准。
整合Spring Security和JWT,可以使我们的应用程序更加安全和高效。下面是整合步骤:
- 添加Spring Security和JWT的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
- 配置Spring Security
在Spring的配置类中,我们需要设置一些安全配置,包括:
- 配置安全规则
- 配置JWT过滤器
- 配置认证管理器
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final String[] AUTH_WHITELIST = {
"/swagger-resources/**",
"/swagger-ui.html",
"/v2/api-docs",
"/webjars/**"
};
@Autowired
private JwtFilter jwtFilter;
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and().csrf().disable()
.authorizeRequests()
.antMatchers(AUTH_WHITELIST).permitAll()
.antMatchers("/api/authenticate").permitAll()
.anyRequest().authenticated()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
}
@Bean(BeanIds.AUTHENTICATION_MANAGER)
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
- 配置JWT
@Configuration
public class JwtConfig {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private long expiration;
@Bean
public JwtEncoder jwtEncoder() {
return new JwtEncoder(secret, expiration);
}
@Bean
public JwtDecoder jwtDecoder() {
return new JwtDecoder(secret);
}
}
- 实现自定义UserDetailsService
我们需要提供一个实现了UserDetailsService接口的自定义类,用于从数据库中获取用户信息。
@Service
public class UserDetailsServiceImpl 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 with username: " + username);
}
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(),
Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")));
}
}
- 实现JwtEncoder和JwtDecoder
我们需要提供一个JwtEncoder和JwtDecoder类,用于创建和验证JWT。
public class JwtEncoder {
private final String secret;
private final long expiration;
public JwtEncoder(String secret, long expiration) {
this.secret = secret;
this.expiration = expiration;
}
public String createToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
claims.put("sub", userDetails.getUsername());
claims.put("iat", new Date());
claims.put("exp", new Date(System.currentTimeMillis() + expiration));
return Jwts.builder()
.setClaims(claims)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
}
public class JwtDecoder {
private final String secret;
public JwtDecoder(String secret) {
this.secret = secret;
}
public String getUsernameFromToken(String token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody().getSubject();
}
public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(secret).parseClaimsJws(token);
return true;
} catch (SignatureException e) {
LOGGER.error("Invalid JWT signature - {}", e.getMessage());
} catch (MalformedJwtException e) {
LOGGER.error("Invalid JWT token - {}", e.getMessage());
} catch (ExpiredJwtException e) {
LOGGER.error("Expired JWT token - {}", e.getMessage());
} catch (UnsupportedJwtException e) {
LOGGER.error("Unsupported JWT token - {}", e.getMessage());
} catch (IllegalArgumentException e) {
LOGGER.error("JWT claims string is empty - {}", e.getMessage());
}
return false;
}
}
- 实现JWT过滤器
我们需要提供一个JwtFilter类,用于过滤JWT。
@Component
public class JwtFilter extends OncePerRequestFilter {
@Autowired
private JwtDecoder jwtDecoder;
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
String header = request.getHeader("Authorization");
if (StringUtils.isBlank(header) || !header.startsWith("Bearer ")) {
chain.doFilter(request, response);
return;
}
String token = header.replace("Bearer ", "");
if (jwtDecoder.validateToken(token)) {
String username = jwtDecoder.getUsernameFromToken(token);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
}
至此,我们已经成功地整合了Spring Security和JWT。