总结:可能是自定义消息转换器影响到 swagger 文档的生成导致无法访问
第一步 :要在 securityConfig 放行相关的资源
auth:
resource:
excludeLoginPaths: # 不需要鉴权的路径
- /users/login
- /users/register
- /users/logout
- /common/**
- /categorys/list
- /items/page-to-receive
- /doc.html # 这些是 swagger 文档需要放行的路径
- /webjars/**
- /v3/**
- /swagger-resources/**
@Configuration
@RequiredArgsConstructor
@EnableMethodSecurity
public class SecurityConfig {
private final JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
private final CustomerAccessDeniedHandler customerAccessDeniedHandler;
private final AnonymousAuthenticationHandler anonymousAuthenticationHandler;
private final LoginFailureHandler loginFailureHandler;
private final ResourceAuthProperties resourceAuthProperties;
/**
* 密码加密
* @return
*/
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
/**
* 登录时需要调用AuthenticationManager.authenticate执行一次校验
*
*/
@Bean
public AuthenticationManager
authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
//配置关闭csrf机制
http.csrf(AbstractHttpConfigurer::disable);
http.formLogin(config-> config
// 用户登录校验异常处理器
.failureHandler(loginFailureHandler)
);
// STATELESS (无状态) , 表示应用程序是无状态的 , 不创建会话
http.sessionManagement(config-> config.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
//配置请求拦截方式
//permitAll:随意访问
// 获取放行路径
List<String> excludeLoginPaths = resourceAuthProperties.getExcludeLoginPaths();
String[] array = new String[excludeLoginPaths.size()];
for (int i = 0; i < excludeLoginPaths.size(); i++) {
array[i] = excludeLoginPaths.get(i);
}
http.authorizeHttpRequests(auth -> auth
.requestMatchers(array)
.permitAll()
.anyRequest()
.authenticated());
//把token校验过滤器添加到过滤器链中 , 并且要在 UsernamePasswordAuthenticationFilter (认证过滤器之前 , 在这个拦截完成认证操作 )
//认证操作就是将认证信息放入到 SecurityContextHolder中
http.addFilterBefore(jwtAuthenticationTokenFilter,
UsernamePasswordAuthenticationFilter.class);
// http.httpBasic(config-> config.authenticationEntryPoint(anonymousAuthenticationHandler));
http.exceptionHandling(config-> config
// 权限不够异常
.accessDeniedHandler(customerAccessDeniedHandler)
// 匿名认证异常
.authenticationEntryPoint(anonymousAuthenticationHandler)
);
//配置跨域
return http.build();
}
}
第二步 : 在过滤器放行资源
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String uri = request.getRequestURI();
// 放行请求
if (!isExcludedPath(uri)) {
log.info("请求方法:" + request.getMethod() + " - " + "请求地址:" + uri);
try {
verifyToken(request);
} catch (AuthenticationException e) {
log.error("认证失败", e);
// 指定异常处理器
response.setStatus(401);
loginFailureHandler.onAuthenticationFailure(request, response, e);
return;
}
}
//放行
filterChain.doFilter(request, response);
}
private void verifyToken(HttpServletRequest request) {
// 获取 token
String token = request.getHeader(jwtProperties.getTokenName());
if (StrUtil.isBlank(token)) throw new CustomerAuthenticationException("token为空");
// 拿着前端给的token 和 redis 中保存的 token 进行比对如果redis中不存中前端传递的
// token 就意味 token 过期了或者用户拿着退出的token 来访问资源
String key = RedisConstant.TOKEN_KEY + token;
String redisToken = (String) redisUtils.getCacheObject(key);
if (StrUtil.isBlank(redisToken)) throw new CustomerAuthenticationException("无效token");
if (!redisToken.equals(token)) throw new CustomerAuthenticationException("无效token");
// 校验令牌
Claims claims = null;
try {
claims = jwtUtil.parseJWT(token);
} catch (Exception e) {
throw new CustomerAuthenticationException("无效token");
}
String subject = claims.getSubject();
if (StrUtil.isBlank(subject)) throw new CustomerAuthenticationException("无效令牌");
if (jwtUtil.isExpired(token)) throw new CustomerAuthenticationException("令牌过期");
LoginUser loginUser = JSON.parseObject(subject, LoginUser.class);
System.out.println("loginUser = " + loginUser);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
/**
* 检查给定的 URI 是否匹配排除路径列表中的任何一个。
* 支持通配符
*
* @param uri 请求的 URI
* @return 如果 URI 匹配排除路径列表中的任何一个,则返回 true;否则返回 false
*/
public boolean isExcludedPath(String uri) {
AntPathMatcher matcher = new AntPathMatcher();
for (String excludeLoginPath : authProperties.getExcludeLoginPaths()) {
if (matcher.match(excludeLoginPath, uri)) {
return true;
}
}
return false;
}
第三步: 如果添加了自定义的消息转换器要将检查消息转换的位置
@Configuration
public class WebConfig implements WebMvcConfigurer {
// 将我们自定的转换器添加进行消息转换列表中 , 拓展自己消息转换器
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
MappingJackson2HttpMessageConverter mc = new MappingJackson2HttpMessageConverter();
mc.setObjectMapper(new JsonObjectMapper());
// TODO 如果将自定义的消息转换放在 1 第一, 可能到 swagger 生成的文档无法访问
converters.add(converters.size() - 10, mc);
}
}
重点是第三步