SpringBoot 集成JWT以及相关认证
1. jwt 工具类
生成 jwt 目前有两类比较流行的工具库:
- jjwt (io.jsonwebtoken)
- java-jwt (com.auth0)
以上两种方式均可,java-jwt 相对比较简单些。但目前推荐使用 jjwt,因此项目中将使用 jjwt 作为生产 jwt token 的工具。
TokenBuilder.java:
public class TokenBuilder {
private final String secret = "xxxx";
private final SecretKey secretKey = Keys.hmacShaKeyFor(secret.getBytes());
public TokenBuilder() {
}
public String generate(PayloadData payloadData) {
payloadData.checkParam();
String token = Jwts.builder()
.claim("payload", payloadData)
.signWith(secretKey)
.compact();
log.info(token);
return Base64.getUrlEncoder().withoutPadding().encodeToString(token.getBytes(StandardCharsets.UTF_8));
}
public PayloadData parse(String vtoken) {
String token = new String(Base64.getUrlDecoder().decode(vtoken), StandardCharsets.UTF_8);
try {
Jws<Claims> claimsJws = Jwts.parser()
.verifyWith(secretKey)
.build()
.parseSignedClaims(token);
Claims body = claimsJws.getPayload();
Map<String, Object> payloadMap = body.get("payload", Map.class);
if(payloadMap != null) {
return PayloadData.builder()
.userId(((Number) payloadMap.get("userId")).longValue())
.projectName((String) payloadMap.get("projectName"))
.loginType((String) payloadMap.get("loginType"))
.ctime((Long) payloadMap.get("ctime"))
.build();
}
} catch (SecurityException e) {
// 签名无效
log.error("{}", e.getMessage());
JDZException.reponse(GlobalCode.ERR_TOKEN_PARSE_SIGNAL);
} catch (ExpiredJwtException e) {
// Token 过期
log.error("{}", e.getMessage());
JDZException.reponse(GlobalCode.ERR_TOKEN_PARSE_EXP);
} catch (Exception e) {
log.error("{}", e.getMessage());
JDZException.reponse(GlobalCode.ERR_TOKEN_PARSE_ERR);
}
JDZException.reponse(GlobalCode.ERR_TOKEN_PARSE_NO_DATA);
return null;
}
}
2. 配置 Authentication Filter 请求时,解析 token 并验证
public abstract class BaseAuthenticationFilter extends OncePerRequestFilter {
private final TokenBuilder tokenBuilder;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String token = getTokenFromRequest(request);
if(!StringUtils.hasText(token)) {
// 无Token,放行(交给Spring Security处理未认证)
filterChain.doFilter(request, response);
return;
}
try {
PayloadData payloadData = tokenBuilder.parse(token);
handleAuth(payloadData, request);
} catch (JDZException e) {
log.info("-------{}------", e.getMessage());
} catch (JwtAuthenticationException e) {
log.info("JwtAuthenticationException caught:{} {}", e.getErrorCode().getCode(), e.getMessage());
}catch (Exception e) {
log.info("-------{}--55555----", e.getMessage());
}
try {
filterChain.doFilter(request, response);
} finally {
SecurityContextHolder.clearContext();
}
}
protected String getTokenFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if(StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
// 子类去实现
protected abstract void handleAuth(PayloadData payloadData, HttpServletRequest request);
}
@Override
protected void handleAuth(PayloadData payloadData, HttpServletRequest request) {
AccountDO accountDO = accountService.getById(payloadData.getUserId());
if(accountDO == null) {
throw new BadCredentialsException(GlobalCode.ERR_TOKEN_UID.getMsgKey());
}
if(!accountDO.getId().equals(payloadData.getUserId())) {
throw new JwtAuthenticationException(GlobalCode.ERR_TOKEN_UID);
}
UserDetails userDetails = User
.withUsername(accountDO.getUsername())
.password("N/A")
//.authorities(getAuthorities(accountDO)) // 确保有正确的权限
.build();
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities()
);
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
3. 将上面的 jwt Authentication Filter 配置进入 SecurityFilterChain:
@Component
@RequiredArgsConstructor
public class SecurityConfigurator {
private final BaseAuthenticationFilter authenticationFilter;
private final JDZAuthExceptionHandler authExceptionHandler;
private final List<AuthorizationCustomizer> authorizationCustomizers = new ArrayList<>();
private final List<String> publicPaths = new ArrayList<>();
public void addPublicPath(String path) {
publicPaths.add(path);
}
public void addAuthorizationCustomizer(AuthorizationCustomizer customizer) {
authorizationCustomizers.add(customizer);
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> {
// 公共公开路径
auth.requestMatchers(
"/swagger-ui/**",
"/v3/api-docs/**"
).permitAll();
// 动态添加的业务公开路径
if(!publicPaths.isEmpty()) {
auth.requestMatchers(publicPaths.toArray(new String[0])).permitAll();
}
// 应用所有授权定制器
for (AuthorizationCustomizer customizer : authorizationCustomizers) {
customizer.customize(auth);
}
auth.anyRequest().authenticated();
})
.exceptionHandling(ex -> ex
// 配置认证失败处理器(401)
.authenticationEntryPoint(authExceptionHandler)
// 配置授权失败处理器(403)
.accessDeniedHandler(authExceptionHandler)
)
.addFilterBefore(authenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@FunctionalInterface
public interface AuthorizationCustomizer {
void customize(AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry auth);
}
}
本文为原创内容,作者:闲鹤,原文链接:https://blog.uwenya.cc/1664.html,转载请注明出处。