前言
在企业级开发或者我们自己的课程设计中,确保用户数据的安全性和访问控制非常重要。而Spring Security和JWT是都两个强大的工具,它俩结合可以帮助我们实现这一目标。
Spring Security提供了全面的安全功能,而JWT则是一种用于身份验证的令牌机制。
JWT简单介绍
前面两个章节介绍过了Spring Security,这里就不再赘述了!!!
JWT是一种轻量级的身份验证和授权机制,通过发送包含用户信息的加密令牌来实现身份验证。这个工具我们在前面的文章中也提起过。
整合步骤与代码实现
目前大部分项目,大多数是使用前后端分离的模式。前后端分离的情况下,我们使用SpringSecurity解决权限问题的最常见的方案就是SpringSecurity+JWT 。
添加依赖
首先,我们需要在项目的pom.xml文件中添加Spring Security和JWT的依赖:
<!--JWT-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.8.1</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!--工具包-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.0.M3</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>
接下来配置Spring Security,在Spring Security配置类中,我们自定义用户详情服务和认证管理器,并配置HTTP安全策略:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Autowired
private JwtRequestFilter jwtRequestFilter;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/authenticate").permitAll()
.anyRequest().authenticated()
.and()
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
}
实现JWT生成和验证,我们创建一个JWT工具类,用于生成和解析JWT:
java
@Component
public class JwtTokenUtil {
private String secret = "your_secret_key"; // 私钥,用于签名JWT
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return Jwts.builder()
.setClaims(claims)
.setSubject(((User) userDetails).getUsername())
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) // 10小时过期
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
public String getUsernameFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
private <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();
}
public boolean validateToken(String token, UserDetails userDetails) {
final String username = getUsernameFromToken(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
private boolean isTokenExpired(String token) {
final Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
创建JWT过滤器与认证管理器
为了在用户每次请求时验证JWT,我们需要创建一个自定义的过滤器。同时,我们还需要一个认证管理器来处理用户的登录请求。
我们实现JWT过滤器
@Component
public class JwtRequestFilter extends OncePerRequestFilter {
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
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 (Exception e) {
logger.error("Unable to get JWT Token");
}
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.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);
}
}
filterChain.doFilter(request, response);
}
}
认证管理器,我们创建一个AuthenticationManager的实现来处理用户的登录请求:
@Service
public class CustomAuthenticationManager implements AuthenticationManager {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = authentication.getCredentials().toString();
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (userDetails == null) {
throw new BadCredentialsException("User not found");
}
if (!passwordEncoder.matches(password, userDetails.getPassword())) {
throw new BadCredentialsException("Wrong password");
}
return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
}
}
创建控制层LoginController
RestController
@RequestMapping("/security")
public class AuthenticationController {
@Autowired
private CustomAuthenticationManager authenticationManager;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private UserDetailsService userDetailsService;
@PostMapping("/login")
public ResponseEntity<?> createAuthenticationToken(@Valid @RequestBody LoginRequest loginRequest) throws Exception {
authenticate(loginRequest.getUsername(), loginRequest.getPassword());
final UserDetails userDetails = userDetailsService.loadUserByUsername(loginRequest.getUsername());
final String token = jwtTokenUtil.generateToken(userDetails);
return ResponseEntity.ok(new JwtAuthenticationResponse(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);
}
}
}
使用ApiFox测试
这样,我们就可以构建一个安全且高效的Web应用了。
小结
Spring Security提供了强大的身份验证和授权功能,而JWT则提供了一种轻量级的令牌机制来验证用户身份。通过结合使用,我们可以实现无缝的用户身份验证和访问控制,然后保护我们应用的数据安全。
文章到这里就先结束了,后续会继续分享相关的知识点。