无法使用 Springfox 发送授权承载令牌

Posted

技术标签:

【中文标题】无法使用 Springfox 发送授权承载令牌【英文标题】:Cannot send Authorization Bearer Token using Springfox 【发布时间】:2018-07-04 16:12:09 【问题描述】:

我无法理解为什么没有使用 Springfox 2.5.0 在我的 api 中发送“授权:承载 __”。我有以下配置:

private ApiKey apiKey() 
        return new ApiKey(
                "Authorization", // name: My key - Authorization
                "api_key", // keyname: api_key
                "header");
    

@Bean
    SecurityConfiguration security() 
        return new SecurityConfiguration(
                null, null, null,
                "Docserver2_fwk", // app name
                "BEARER", // api key value
                ApiKeyVehicle.HEADER, "Authorization", ",");
    

发送的卷曲是:

似乎我无法在 springfox (2.5.0) 中发送“Authorization: Bearer Token”,这可能吗?这是一个已知问题吗?

类似问题:https://github.com/springfox/springfox/issues/1812

PS:OpenAPI 3.0 允许“承载”格式,例如:https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#jwt-bearer-sample

谢谢。

【问题讨论】:

您应该尝试迁移到 springfox 2.8.0。和 springfox-swagger-ui 2.8.0。我相信它应该可以解决您的问题。至于不记名令牌,您仍然需要在令牌前面加上 Bearer 感谢您的回答,但我仅限于使用具有 springfox 2.5.0 的客户端库。客户端不会更改 springfox 版本,因为它会影响其他几个应用程序。虽然我对springfox版本没有控制,但是我用的是最新的springfox-swagger-ui。 另一件事,如果我在令牌前面加上“Bearer”,我会得到“Authorization Bearer:”而不是“Authorization: Bearer”(注意冒号)。 我的意思是在键值中:不要单独使用键,而是使用Bearer <key> 啊,是的,这行得通,但这不是一个理想的解决方案,因为它会迫使使用该界面的人知道他们必须编写“Bearer ”而不是仅仅粘贴文本字段中的标记。 【参考方案1】:

一个简单的解决方法是键入Bearer,而不是在其后粘贴令牌。您最终会得到一个包含以下内容的文本框:

Bearer <token>

我希望有一种更自动化的方式。但就目前而言,似乎文本框中简单获取的内容已粘贴到给定标题条目的值部分。我想前缀 Bearer 没有自动注入的原因是因为 Swagger 会非常固执己见地决定其用户使用哪种身份验证!

@Configuration
@EnableSwagger2
class SwaggerConfig 

    @Bean
    Docket api() 

        return new Docket(DocumentationType.SWAGGER_2)
                .select()
                .apis(RequestHandlerSelectors.any())
                .paths(Predicates.not(PathSelectors.regex("/error.*")))
                .build()
                .securitySchemes(securitySchemes())
    

    private static ArrayList<? extends SecurityScheme> securitySchemes() 

        return [new ApiKey("Bearer", "Authorization", "header")]
    

REST 端点方法:

@GetMapping("/count")
@ApiOperation(value = "Count the number of entities associated with resource name. This operation does not requires any role." , authorizations = [@Authorization(value = "Bearer")])
def count() 

    count(service)

登录前的curl命令:

curl -X GET "http://localhost:8080/category/count" -H "accept: */*"

回复:


  "timestamp": "2018-10-29T15:13:02.388+0000",
  "status": 401,
  "error": "Unauthorized",
  "message": "Unauthorized",
  "path": "/category/count"

登录后的curl命令:

curl -X GET "http://localhost:8080/category/count" -H "accept: */*" -H "Authorization: Bearer eyJhbGciOiJIUzUxMiJ9..."

回复:


  "message": "There are 0 entities",
  "count": 0

注意:我的代码是用 Groovy 编写的,如果您使用标准 Java,我相信您可以翻译。

【讨论】:

【参考方案2】:

我正在使用2.8.0 版本和以下招摇配置适用于我。我在 cmets 中提到,对于我来说,2.7.0 版本一切正常,但后来我升级到2.8.0,并且 jwt 令牌停止在请求标头中发送。我使用的是 Spring Boot 版本 - 1.5.2.RELEASE

请注意,我想要一个用户可以手动输入 JWT 令牌的 UI 格式 - Bearer ... 并且令牌应该放在 Authorization 请求标头中。它不会自动与 OAuth 服务器集成。

import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.ResponseEntity;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger.web.SecurityConfiguration;
import springfox.documentation.swagger.web.SecurityConfigurationBuilder;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import com.google.common.base.Predicates;
import com.google.common.collect.Lists;

@Configuration
@EnableSwagger2
public class SwaggerConfig extends WebMvcConfigurerAdapter 

    @SuppressWarnings("unchecked")
    @Bean
    public Docket swaggerPlugin() 
    return new Docket(DocumentationType.SWAGGER_2)
        .select()
        .paths(PathSelectors.any())
        .apis(Predicates.or(RequestHandlerSelectors
            .basePackage("**controller package 1**"),
            RequestHandlerSelectors
                .basePackage("**controller package 2**")))
        .build().directModelSubstitute(LocalDate.class, String.class)
        .genericModelSubstitutes(ResponseEntity.class)
        .apiInfo(apiInfo())
        .securitySchemes(Lists.newArrayList(apiKey()))
        .securityContexts(Arrays.asList(securityContext()));
    

    private ApiInfo apiInfo() 
    return new ApiInfoBuilder().title("**Comment**")
        .description("**Comment**")
        .termsOfServiceUrl("**Comment**")
        .contact("**Comment**")
        .license("Apache License Version 2.0")
        .licenseUrl("**Comment**").version("0.0.1")
        .build();
    

    @Bean
    public SecurityConfiguration security() 
    return SecurityConfigurationBuilder.builder().scopeSeparator(",")
        .additionalQueryStringParams(null)
        .useBasicAuthenticationWithAccessCodeGrant(false).build();
    

    @Override
    public void addResourceHandlers(final ResourceHandlerRegistry registry) 
    registry.addResourceHandler("swagger-ui.html").addResourceLocations(
        "classpath:/META-INF/resources/");
    registry.addResourceHandler("/webjars/**").addResourceLocations(
        "classpath:/META-INF/resources/webjars/");
    

    private ApiKey apiKey() 
    return new ApiKey("apiKey", "Authorization", "header");
    

    private SecurityContext securityContext() 
    return SecurityContext.builder().securityReferences(defaultAuth())
        .forPaths(PathSelectors.any()).build();
    

    private List<SecurityReference> defaultAuth() 
    AuthorizationScope authorizationScope = new AuthorizationScope(
        "global", "accessEverything");
    AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
    authorizationScopes[0] = authorizationScope;
    return Arrays.asList(new SecurityReference("apiKey",
        authorizationScopes));
    


参考 - this github issue answer by JotaroJewstar

【讨论】:

【参考方案3】:

从 Springfox 3.0.0 开始,这种授权有专用的 HttpAuthenticationScheme 配置。

JWT 授权配置示例:

@Bean
public Docket jwtSecuredDocket() 
    HttpAuthenticationScheme authenticationScheme = HttpAuthenticationScheme
            .JWT_BEARER_BUILDER
            .name("JWT Token")
            .build();
    
    return new Docket(DocumentationType.OAS_30)
            // <...>
            .securitySchemes(Collections.singletonList(authenticationScheme));

这是一个更方便的解决方案,因为您不必每次都在 Swagger UI 的身份验证模式中输入“Bearer”关键字 - 新机制为您填补了这一点(这是以前解决方案的一个缺点)。

重要提示

根据commit introducing this feature 的消息,必须将案卷的DocumentationType 设置为OAS_30 才能使其正常工作。

【讨论】:

【参考方案4】:

我正在使用最新版本的 springfox(现在是 3.0.0),您可以使用特定的承载身份验证和 HttpAuthenticationScheme 而不是 ApiKey:

HttpAuthenticationScheme
    .JWT_BEARER_BUILDER
    .name("JWT")
    .build()

【讨论】:

这段代码放在哪一部分?你能提供更多的上下文吗?我正在尝试在 springfox 3.0.0 中设置授权标头 @mindOf_L 当然。我在配置类中的案卷初始化时应用它。示例:lang-kt @Configuration class SwaggerConfig ... @Bean fun docket(): Docket = Docket(...) .apiInfo(...) ... ..securitySchemes(listOf(HttpAuthenticationScheme.JWT_BEARER_BUILDER...) 这会在映射安全定义时引发空指针异常。无法使用这个 这是否适用于 documentationType.Swagger2 或 OAS30 无法正常工作 - 获取空指针。看起来错误就在这里:在 springfox.documentation.swagger2.mappers.SecurityMapper.toSecuritySchemeDefinitions(SecurityMapper.java:53) ~[springfox-swagger2-3.0.0.jar:3.0.0]【参考方案5】:

使用 swagger 版本 2.9.2 对我有用的解决方案如下:

package com.example.springsocial;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import com.google.common.base.Predicates;
import com.google.common.collect.Lists;

import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class SwaggerConfiguration   

    @Bean
    public Docket docket() 
        return new Docket(DocumentationType.SWAGGER_2)
                    .ignoredParameterTypes(AuthenticationPrincipal.class)
                    .select()
                    .apis(Predicates.not(RequestHandlerSelectors.basePackage("org.springframework.boot")))
                    .paths(PathSelectors.any()).build()
                    .securitySchemes(Lists.newArrayList(apiKey()))
                    .securityContexts(Arrays.asList(securityContext()));

    

    private ApiKey apiKey() 
        return new ApiKey("apiKey", "Authorization", "header");
    

    private SecurityContext securityContext() 
        return SecurityContext.builder().securityReferences(defaultAuth())
            .forPaths(PathSelectors.any()).build();
    

    private List<SecurityReference> defaultAuth() 
        AuthorizationScope authorizationScope = new AuthorizationScope(
            "global", "accessEverything");
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        authorizationScopes[0] = authorizationScope;
        return Arrays.asList(new SecurityReference("apiKey",
            authorizationScopes));
        

POM.XML

<!-- SWAGGER  -->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-bean-validators</artifactId>
            <version>2.9.2</version>
        </dependency>

SecurityConfig.JAVA

package com.example.springsocial.config;

import com.example.springsocial.security.*;
import com.example.springsocial.security.oauth2.CustomOAuth2UserService;
import com.example.springsocial.security.oauth2.HttpCookieOAuth2AuthorizationRequestRepository;
import com.example.springsocial.security.oauth2.OAuth2AuthenticationFailureHandler;
import com.example.springsocial.security.oauth2.OAuth2AuthenticationSuccessHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(
        securedEnabled = true,
        jsr250Enabled = true,
        prePostEnabled = true
)
public class SecurityConfig extends WebSecurityConfigurerAdapter 

    @Autowired
    private CustomUserDetailsService customUserDetailsService;

    @Autowired
    private CustomOAuth2UserService customOAuth2UserService;

    @Autowired
    private OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler;

    @Autowired
    private OAuth2AuthenticationFailureHandler oAuth2AuthenticationFailureHandler;

    @Autowired
    private HttpCookieOAuth2AuthorizationRequestRepository httpCookieOAuth2AuthorizationRequestRepository;

    @Bean
    public TokenAuthenticationFilter tokenAuthenticationFilter() 
        return new TokenAuthenticationFilter();
    

    /*
      By default, Spring OAuth2 uses HttpSessionOAuth2AuthorizationRequestRepository to save
      the authorization request. But, since our service is stateless, we can't save it in
      the session. We'll save the request in a Base64 encoded cookie instead.
    */
    @Bean
    public HttpCookieOAuth2AuthorizationRequestRepository cookieAuthorizationRequestRepository() 
        return new HttpCookieOAuth2AuthorizationRequestRepository();
    

    @Override
    public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception 
        authenticationManagerBuilder
                .userDetailsService(customUserDetailsService)
                .passwordEncoder(passwordEncoder());
    

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


    @Bean(BeanIds.AUTHENTICATION_MANAGER)
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception 
        return super.authenticationManagerBean();
    

    @Override
    protected void configure(HttpSecurity http) throws Exception 
        http
                .cors()
                    .and()
                .sessionManagement()
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and()
                .csrf()
                    .disable()
                .formLogin()
                    .disable()
                .httpBasic()
                    .disable()
                .exceptionHandling()
                    .authenticationEntryPoint(new RestAuthenticationEntryPoint())
                    .and()
                .authorizeRequests()
                    .antMatchers("/",
                        "/error",
                        "/favicon.ico",
                        "/**/*.png",
                        "/**/*.gif",
                        "/**/*.svg",
                        "/**/*.jpg",
                        "/**/*.html",
                        "/**/*.css",
                        "/**/*.js",
                        // swagger
                        "/swagger-ui.html**", "/swagger-resources/**",
                        "/v2/api-docs**", "/webjars/**"
                         )
                        .permitAll()
                    .antMatchers("/auth/**", "/oauth2/**")
                        .permitAll()
                    .anyRequest()
                        .authenticated()
                    .and()
                .oauth2Login()
                    .authorizationEndpoint()
                        .baseUri("/oauth2/authorize")
                        .authorizationRequestRepository(cookieAuthorizationRequestRepository())
                        .and()
                    .redirectionEndpoint()
                        .baseUri("/oauth2/callback/*")
                        .and()
                    .userInfoEndpoint()
                        .userService(customOAuth2UserService)
                        .and()
                    .successHandler(oAuth2AuthenticationSuccessHandler)
                    .failureHandler(oAuth2AuthenticationFailureHandler);

        // Add our custom Token based authentication filter
        http.addFilterBefore(tokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
    

    @Override
    public void configure(WebSecurity web) throws Exception 
        web.ignoring().antMatchers("/common/**", "/v2/api-docs", "/configuration/ui", "/swagger-resources",
                "/configuration/security", "/swagger-ui.html", "/webjars/**");
    


【讨论】:

【参考方案6】:

我使用 springfox-swagger2 version-2.9.2 和以下配置,通过 swagger-ui 传递 JWT 令牌可以正常工作。

private ApiKey apiKey()     
    return new ApiKey("apiKey", Authorization, "header"); 


@Bean
public Docket api() 
    return new Docket(DocumentationType.SWAGGER_2).select()
       .apis(RequestHandlerSelectors.basePackage("com.mycompany.dept.controller"))
       .paths(PathSelectors.any())
       .build().apiInfo(metaData()).securitySchemes(Lists.newArrayList(apiKey()));

在控制器中我们需要使用@ApiOperation (value = "Get dummy by id.", authorizations = @Authorization(value="apiKey") )

请参考https://github.com/springfox/springfox/issues/2194

【讨论】:

【参考方案7】:

以下解决方案在 swagger 2.8.0 版本中对我有用。

在您的 Docket 配置中添加以下代码

@Bean
    public Docket api() 
        return new Docket(DocumentationType.SWAGGER_2)
                .securitySchemes(Collections.singletonList(new ApiKey("JWT", "Authorization", "header")))
                .securityContexts(Collections.singletonList(
                        SecurityContext.builder()
                                .securityReferences(
                                        Collections.singletonList(SecurityReference.builder()
                                                .reference("JWT")
                                                .scopes(new AuthorizationScope[0])
                                                .build()
                                        )
                                )
                                .build())
                )
                .select()
                .apis(RequestHandlerSelectors
                        .basePackage("com.test.controller"))
                .paths(PathSelectors.regex("/.*"))
                .build().apiInfo(apiEndPointsInfo());
    

在swagger UI中点击Authorize按钮后的文本框中,输入Bearer "XXXXXXXX(Token)"

【讨论】:

【参考方案8】:

另一个使用 springfox 3.0.0 的工作解决方案。

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.servlet.ServletContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.oas.annotations.EnableOpenApi;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.HttpAuthenticationScheme;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;

@Configuration
@EnableOpenApi
public class SwaggerConfig 
    
    @Autowired
    private ServletContext context;

    @Bean
    public Docket api()        
        HttpAuthenticationScheme authenticationScheme = HttpAuthenticationScheme.JWT_BEARER_BUILDER
                .name("Authorization")
                .build();

        return new Docket(DocumentationType.OAS_30)
                .select()
                .apis(RequestHandlerSelectors.any())
                .paths(PathSelectors.ant(context.getContextPath() + "/api/**"))
                .build()
                .securityContexts(Arrays.asList(securityContext()))
                .securitySchemes(Collections.singletonList(authenticationScheme));
    

    private SecurityContext securityContext() 
        return SecurityContext.builder().securityReferences(defaultAuth()).build();
    

    private List<SecurityReference> defaultAuth() 
        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        authorizationScopes[0] = authorizationScope;
        return Arrays.asList(new SecurityReference("Authorization", authorizationScopes));
    


【讨论】:

【参考方案9】:

Jorge Santos Neill 提供的解决方案对我有用。

唯一的变体是我的 SecurityConfig.java,如下所述

import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;

@EnableWebSecurity
@EnableResourceServer
@EnableOAuth2Sso
@Configuration

public class SecurityConfig extends WebSecurityConfigurerAdapter 

    @Override
    protected void configure(final HttpSecurity http) throws Exception 
        http.authorizeRequests().anyRequest().authenticated().and().csrf().disable();
    

    @Override
    public void configure(WebSecurity web) throws Exception 
    web.ignoring().antMatchers("/v2/api-docs", "/configuration/**", "/configuration/ui/**", "/swagger-resources/**",
            "/configuration/security/**", "/swagger-ui.html", "/webjars/**", "/health", "/csrf");

    


【讨论】:

以上是关于无法使用 Springfox 发送授权承载令牌的主要内容,如果未能解决你的问题,请参考以下文章

为啥我们更喜欢授权标头将承载令牌发送到服务器而不是 URL 编码等其他技术

AuthTokenAccessException:无法解码JWT承载:没有JWT承载存在于“授权”的请求头

云视界API的授权承载令牌。

使用授权承载请求时在 Firebase 函数中获取令牌

Swashbuckle 承载授权

发送带有承载令牌授权标头 (flask_restful + flask_jwt_extended) 的 GET 消息时出现“段不足”