spring security整合JWT鉴权
关于JWT是什么就不赘述了,可以看看阮一峰大佬的文章:https://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html
定义JWT Body
首先需要定义一个Bean用来存放JWT Body,字段可以根据具体业务决定。这一步当然可以跳过,你可以用Map传值,只不过可维护性会差点
@Data
public class JwtBody {
private String id;
private String user;
private String role;
}
编写JWT工具类
首先添加Maven依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.2</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
工具类
public abstract class JwtUtil {
//传入JwtBody和密钥,生成Token返回,有效期两个小时
@SuppressWarnings("unchecked")
public static String generate(JwtBody claims, String secret) {
final SecretKey secretKey = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
return Jwts.builder()
.setExpiration(Date.from(ZonedDateTime.now().plusHours(2).toInstant()))
.addClaims(BeanMap.create(claims))
.signWith(secretKey).compact();
}
//传入Token和密钥,验证Token并解析JwtBody返回
public static JwtBody parse(String token, String secret) {
final SecretKey secretKey = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
final JwtBody claims = new JwtBody();
final Claims body = Jwts.parserBuilder().setSigningKey(secretKey).build().parseClaimsJws(token).getBody();
BeanMap.create(claims).putAll(body);
return claims;
}
}
两个方法,一个编码,一个解码
编写Login Controller
编写一个登录API接口,用来生成Token返回给客户端
@RestController
@RequiredArgsConstructor
@RequestMapping("")
@CrossOrigin
public class AuthController {
private final AuthService authService;
@PostMapping("/login")
public UserLoginResponse login(@RequestBody UserLoginRequest userLoginRequest) {
String token = authService.login(userLoginRequest.getUsername(), userLoginRequest.getPassword());
return UserLoginResponse.builder().token(token).build();
}
}
登录方法的实现
public String login(String username, String password) {
User user = userService.getByUsername(username);
if (!passwordEncoder.matches(password, user.getPassword())) {
throw new BadCredentialsException("Password error");
}
JwtBody claims = new JwtBody() {
{
setId(user.getId().toString());
setUser(user.getUsername());
setRole(user.getRole().name());
}
};
return JwtUtil.generate(claims, config.getJwtSecret())
}
编写JWT Filter
需要编写一个过滤器,添加到Spring Security过滤器链,用来验证Token授权
@Component
public class JwtFilter extends OncePerRequestFilter {
private final static String TOKEN_PREFIX = "Bearer";
private AuthenticationManager authenticationManager;
//从Header中提取Token封装成JwtAuthentication传入AuthenticationManager进行验证,然后将验证之后的JwtAuthentication设置到Spring Security上下文
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
final String header = request.getHeader(HttpHeaders.AUTHORIZATION);
if (header != null && header.startsWith(TOKEN_PREFIX)) { //只有在Header中包含Token的时候才会进行授权
final String token = header.replaceAll("^" + TOKEN_PREFIX, "").trim();
Authentication auth = new JwtAuthentication(token);
try {
Authentication authResult = authenticationManager.authenticate(auth);
SecurityContextHolder.getContext().setAuthentication(authResult);
} catch (JwtException e) {
logger.warn("Authentication failed: " + e.getLocalizedMessage());
}
}
filterChain.doFilter(request, response);
}
@Autowired
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
}
JwtAuthentication实现
public class JwtAuthentication implements Authentication {
@Getter
private final String token;
@Getter
@Setter
private User user;
private Boolean isAuthenticated = false;
public JwtAuthentication(String token) {
this.token = token;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return user.getAuthorities();
}
@Override
public Object getCredentials() {
return null;
}
@Override
public Object getDetails() {
return user;
}
@Override
public Object getPrincipal() {
return user.getUsername();
}
@Override
public boolean isAuthenticated() {
return isAuthenticated;
}
@Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
this.isAuthenticated = isAuthenticated;
}
@Override
public String getName() {
return getPrincipal().toString();
}
}
编写JwtAuthenticationProvider
这个类用来实现具体的Token验证操作
@Component
@RequiredArgsConstructor
public class JwtAuthenticationProvider implements AuthenticationProvider {
private final Config config;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
JwtAuthentication jwtAuthentication = (JwtAuthentication) authentication;
JwtBody claims;
try {
claims = JwtUtil.parse(jwtAuthentication.getToken(), config.getJwtSecret()); //解析Token失败,直接抛出异常,让Filter捕获
} catch (SignatureException e) {
throw new AccessDeniedException(e.getLocalizedMessage());
}
final User user = new User();
user.setId(Long.valueOf(claims.getId().toString()));
user.setUsername(claims.getUser());
user.setRole(Role.valueOf(claims.getRole()));
jwtAuthentication.setUser(user);
jwtAuthentication.setAuthenticated(true); //认证完毕,将认证状态设为已认证
return jwtAuthentication;
}
@Override
public boolean supports(Class<?> authentication) { //此处表示只认证JwtAuthentication及其子类
return JwtAuthentication.class.isAssignableFrom(authentication);
}
}
编写Spring Security配置类
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private JwtFilter jwtFilter;
private JwtAuthenticationProvider jwtAuthenticationProvider;
@Bean
@Override //将AuthenticationManager作为Bean添加到Spring容器
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(jwtAuthenticationProvider); //添加JwtAuthenticationProvider
}
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().logout().disable().formLogin().disable() //启用CORS,关闭CSRF防御,关闭登出,关闭表单登录
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) //使用无状态会话,即不使用cookie
.authorizeRequests(request -> {
request.antMatchers("/api/users/*").hasAuthority(Role.ADMIN.name());
request.antMatchers("/login", "/register").permitAll(); //放行登陆、注册接口
})
.addFilterAt(jwtFilter, UsernamePasswordAuthenticationFilter.class); //将JwtFilter添加到过滤器链
}
//为了防止循环依赖注入,使用setter注入依赖,而不是构造器
@Autowired
public void setJwtFilter(JwtFilter jwtFilter) {
this.jwtFilter = jwtFilter;
}
@Autowired
public void setJwtAuthenticationProvider(JwtAuthenticationProvider jwtAuthenticationProvider) {
this.jwtAuthenticationProvider = jwtAuthenticationProvider;
}
}