无法使用 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 一个简单的解决方法是键入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承载存在于“授权”的请求头
发送带有承载令牌授权标头 (flask_restful + flask_jwt_extended) 的 GET 消息时出现“段不足”