Springboot 3 + springsecurity + knife4j 无法访问 swagger文档

总结:可能是自定义消息转换器影响到 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);
    }
}
 重点是第三步
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值