无法解决403 Forbidden当使用开放的API与 Spring Boot 和swagger和允许其他请求

k75qkfdt  于 12个月前  发布在  Spring
关注(0)|答案(2)|浏览(265)

我在这里看了很多问题,但没有适合我的解决方案。
我有一个服务,我想运行开放的API和swagger用户界面上。
我在没有安全性的情况下运行它们没有问题,当我尝试使用下面的代码添加基本身份验证时,它可以按照我的意愿工作,但它也将我所有其他的API置于相同的身份验证之后,这是我不希望的。我想我可以添加一个额外的requestMatcher,如下所示:

@Bean
    public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.authorizeHttpRequests()
                    .requestMatchers("/swagger-ui/**",
                                     "/v3/api-docs/**",
                                     "/v3/api-docs",
                                     "/swagger-ui.html")
                    .hasRole("USER")
                    .requestMatchers("/**").permitAll()
                    .and()
                    .httpBasic(Customizer.withDefaults());
        return httpSecurity.build();
    }

字符串
当我这样做的时候,我的其他API是可以访问的,没有安全性,正如我所期望的,当我去swagger ui我得到弹出窗口,我可以登录,但然后get请求到/v3/api-config失败与一个403禁止错误或400错误,这取决于所有的requestMatcher位于何处。
正确的方法是什么?
我希望 Swagger 的API的背后基本认证,但所有其他API的任何人都可以访问。
我找不到现有的答案来帮助解决这个问题。
我目前的安全配置如下,这阻止了一切,并允许在身份验证后 swagger :

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    public static final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.authorizeHttpRequests()
                    .requestMatchers("/swagger-ui/**",
                                     "/v3/api-docs/**",
                                     "/v3/api-docs",
                                     "/swagger-ui.html")
                    .hasRole("USER")
                    .and()
                    .httpBasic(Customizer.withDefaults());
        return httpSecurity.build();

    }

    @Bean
    public UserDetailsService userDetailsService(PasswordEncoder passwordEncoder) {
        UserDetails user = User.withUsername("user")
                               .password(passwordEncoder.encode("password"))
                               .roles("USER")
                               .build();
        return new InMemoryUserDetailsManager(user);
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

smtd7mpg

smtd7mpg1#

为了只在swagger上进行身份验证,我实现了两个不同的过滤器链。一个用于swagger,另一个用于整个服务。这是我的SecurityConfig的样子:

http.addFilterBefore(ssoAuthFilter, UsernamePasswordAuthenticationFilter.class);

        http.authorizeHttpRequests(c ->
                            c.requestMatchers("/swagger-ui").hasAnyAuthority("STAFF","ADMIN","CASHIER","TENANTADMIN")
                                    .requestMatchers("/api-docs/swagger-config").permitAll()
                                    .requestMatchers("/swagger-ui/login").permitAll()
                                    .requestMatchers("/actuator/health").permitAll()
                                    .requestMatchers("/actuator/prometheus").permitAll()
                                    .requestMatchers("/api-docs").hasAnyAuthority("STAFF","ADMIN", "CASHIER", "TENANTADMIN")
                                    .requestMatchers("/api-docs/eod").hasAnyAuthority("ADMIN", "STAFF", "TENANTADMIN", "CASHIER")
                                    .requestMatchers("/api-docs/statistics").hasAnyAuthority("ADMIN", "STAFF", "CASHIER", "TENANTADMIN")
                                    .dispatcherTypeMatchers( DispatcherType.ERROR ).permitAll()
                            .anyRequest().authenticated()
        );

        http.formLogin(form -> form
                .loginPage("/swagger-ui/login")
                .loginProcessingUrl("/swagger-ui/login")
                .successHandler(myAuthenticationSuccessHandler())
                .failureHandler(myAuthenticationFailureHandler())
                .permitAll()
        );

        http.addFilterBefore(swaggerAuthFilter, UsernamePasswordAuthenticationFilter.class).exceptionHandling(handling -> {
            handling.accessDeniedHandler(accessDeniedHandler());
            handling.authenticationEntryPoint(new CustomHttp403ForbiddenEntryPoint());
        });

        http.authenticationProvider(swaggerAuthProvider);

字符串
正如你所看到的,我已经为我的整个服务和SwaggerAuthFilter的 Swagger 的目的只SSOAuthFilter。
您还需要一个AuthenticationProvider:

public class SwaggerAuthProvider implements AuthenticationProvider {
        private UserService userService;
    
        @Autowired
        public SwaggerAuthProvider(UserService userService) {
            this.userService = userService;
        }
    
        @Override
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
            String username = authentication.getName();
            String password = authentication.getCredentials().toString();
            Optional<WhoAmIResponse> whoAmIResponse = userService.authenticate(new LoginRequest(username,password));
    
            if (whoAmIResponse.isEmpty()) {
                log.debug("Authentication failed. Invalid credentials.");
                throw new BadCredentialsException("Authentication failed. Invalid credentials.");
            }
    
            // Create a new authenticated token with the JWT token
            return new JwtAuthenticationToken(
                    whoAmIResponse.get().getToken(),
                    username, password,
                    List.of(
                            new SimpleGrantedAuthority(
                                whoAmIResponse.get().getType().toUpperCase()
                            )
                    )
            );
        }
    
        @Override
        public boolean supports(Class<?> authentication) {
            return authentication.isAssignableFrom(JwtAuthenticationToken.class);
        }
    }


这就是我的SwaggerAuthFilter看起来的样子:

public class SwaggerAuthFilter extends OncePerRequestFilter {

    private final MyUserDetailsService userDetailsService;
    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    @Autowired
    public SwaggerAuthFilter(MyUserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }

    private String checkCookie(Cookie tokenCookie){
        String token = tokenCookie.getValue();
        if(token == null || token.length() < 7){
            log.debug("token problem with length {}", token.length());
        }

        if(token.startsWith("Bearer"))
            return token.substring(7);
        return token;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest req,
                                    HttpServletResponse res,
                                    FilterChain chain) throws IOException, ServletException {
        String url = req.getRequestURI();
        if(
                (!req.getRequestURI().contains("swagger-ui") &&
                !req.getRequestURI().contains("api-docs")) ||
                req.getRequestURI().contains("login")
        ){
            log.debug("request with uri: {} was not associated with swagger filter", url);
            chain.doFilter(req, res);
            return;
        }

        Cookie tokenCookie = WebUtils.getCookie(req, "token");
        if(tokenCookie == null){
            log.debug("token cookie is null, redirecting to login page");
            redirectStrategy.sendRedirect(req, res, "/swagger-ui/login");
            return;
        }
        String token = checkCookie(tokenCookie);

        UsernamePasswordAuthenticationToken authentication;
        try {
            UserDetails userDetails = userDetailsService.loadUserByUsername(token);
            if(userDetails == null){
                log.debug("user retrieved by token in cookie is null, redirecting to login page");
                tokenCookie.setMaxAge(0);
                res.addCookie(tokenCookie);
                redirectStrategy.sendRedirect(req, res, "/swagger-ui/login");
                return;
            }
            authentication =
                    new JwtAuthenticationToken(token,userDetails,
                            null,
                            userDetails.getAuthorities());

            authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(req));

        } catch (AuthException | NullPointerException | IllegalArgumentException e) {
            log.debug("cannot authorize because {}", e.getLocalizedMessage());
            authentication = null;
        }
        SecurityContextHolder.getContext().setAuthentication(authentication); // set the user here
        chain.doFilter(req, res);
    }
}


希望这能帮助您解决问题!

ffx8fchx

ffx8fchx2#

这是一个奇怪的例子。在我工作的服务中,我们有一个端点叫做/error
Spring有一个默认的错误控制器,它也有一个名为/error的端点。
这在以前从来都不是问题,但是很明显,随着spring-security的加入,有些事情发生了变化,这就成了一个问题。
Spring不知道该使用哪个资源来做它想做的事情,所以选择了错误的资源,这导致了奇怪的行为。
有趣的是,没有实际的错误。
在调试日志中,我注意到我们出于某种原因调用了/error(仍然无法弄清楚这一点),并且有一个警告说预期的主体不在请求中,这很奇怪,因为我们没有调用该资源,并且失败的GET请求没有主体。
直到我们打开跟踪日志,我们才看到有两个冲突的/error资源,spring正在挑选资源,而不是它正在寻找的资源。
我们将错误资源重命名为其他内容,所有问题都消失了。
我觉得奇怪的是,当这种情况发生时,spring并没有正确地抱怨。
无论如何,它现在正在工作。

相关问题