Spring boot 2 OAuth2“HTTP Content-Type 头必须是 application/json" [英] Spring boot 2 OAuth2 "The HTTP Content-Type header must be application/json"
问题描述
我正在创建一个 spring boot 2 OAuth2 客户端/服务器应用程序.授权服务器成功登录并重定向到客户端,但是当客户端收到重定向(http://localhost:8080/login/oauth2/code/xe?code=ACK4Ae&state=Jw-dCGYvJa6QV-fcoTGjgY-6FyUj%3D->a>) 客户端浏览器显示:
I am creating a spring boot 2 OAuth2 client/server application. The authorization server successfully logins and redirects to the client, but when the client receives the redirect (http://localhost:8080/login/oauth2/code/xe?code=ACK4Ae&state=Jw-dCGYvJa6QV-fcoTGjgY-6FyUyJHa-HBjWdsp3HM4%3D) the client browser displays:
Your login attempt was not successful, try again.
Reason: [invalid_token_response] An error occurred parsing the Access Token response: The HTTP Content-Type header must be application/json; charset=UTF-8
堆栈跟踪
Authentication request failed: org.springframework.security.oauth2.core.OAuth2AuthenticationException: [invalid_token_response] An error occurred parsing the Access Token response: The HTTP Content-Type header must be application/json; charset=UTF-8
org.springframework.security.oauth2.core.OAuth2AuthenticationException: [invalid_token_response] An error occurred parsing the Access Token response: The HTTP Content-Type header must be application/json; charset=UTF-8
at org.springframework.security.oauth2.client.endpoint.NimbusAuthorizationCodeTokenResponseClient.getTokenResponse(NimbusAuthorizationCodeTokenResponseClient.java:105) ~[spring-security-oauth2-client-5.0.0.RELEASE.jar:5.0.0.RELEASE]
at org.springframework.security.oauth2.client.endpoint.NimbusAuthorizationCodeTokenResponseClient.getTokenResponse(NimbusAuthorizationCodeTokenResponseClient.java:67) ~[spring-security-oauth2-client-5.0.0.RELEASE.jar:5.0.0.RELEASE]
at org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationProvider.authenticate(OAuth2LoginAuthenticationProvider.java:113) ~[spring-security-oauth2-client-5.0.0.RELEASE.jar:5.0.0.RELEASE]
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:174) ~[spring-security-core-5.0.0.RELEASE.jar:5.0.0.RELEASE]
at org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter.attemptAuthentication(OAuth2LoginAuthenticationFilter.java:129) ~[spring-security-oauth2-client-5.0.0.RELEASE.jar:5.0.0.RELEASE]
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:212) [spring-security-web-5.0.0.RELEASE.jar:5.0.0.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-5.0.0.RELEASE.jar:5.0.0.RELEASE]
at org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter.doFilterInternal(OAuth2AuthorizationRequestRedirectFilter.java:109) [spring-security-oauth2-client-5.0.0.RELEASE.jar:5.0.0.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.2.RELEASE.jar:5.0.2.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-5.0.0.RELEASE.jar:5.0.0.RELEASE]
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116) [spring-security-web-5.0.0.RELEASE.jar:5.0.0.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-5.0.0.RELEASE.jar:5.0.0.RELEASE]
at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:100) [spring-security-web-5.0.0.RELEASE.jar:5.0.0.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.2.RELEASE.jar:5.0.2.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-5.0.0.RELEASE.jar:5.0.0.RELEASE]
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:64) [spring-security-web-5.0.0.RELEASE.jar:5.0.0.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.2.RELEASE.jar:5.0.2.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-5.0.0.RELEASE.jar:5.0.0.RELEASE]
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105) [spring-security-web-5.0.0.RELEASE.jar:5.0.0.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-5.0.0.RELEASE.jar:5.0.0.RELEASE]
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56) [spring-security-web-5.0.0.RELEASE.jar:5.0.0.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.2.RELEASE.jar:5.0.2.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) [spring-security-web-5.0.0.RELEASE.jar:5.0.0.RELEASE]
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215) [spring-security-web-5.0.0.RELEASE.jar:5.0.0.RELEASE]
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178) [spring-security-web-5.0.0.RELEASE.jar:5.0.0.RELEASE]
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:357) [spring-web-5.0.2.RELEASE.jar:5.0.2.RELEASE]
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:270) [spring-web-5.0.2.RELEASE.jar:5.0.2.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.23.jar:8.5.23]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.23.jar:8.5.23]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) [spring-web-5.0.2.RELEASE.jar:5.0.2.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.2.RELEASE.jar:5.0.2.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.23.jar:8.5.23]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.23.jar:8.5.23]
at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:108) [spring-web-5.0.2.RELEASE.jar:5.0.2.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.2.RELEASE.jar:5.0.2.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.23.jar:8.5.23]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.23.jar:8.5.23]
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:81) [spring-web-5.0.2.RELEASE.jar:5.0.2.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.2.RELEASE.jar:5.0.2.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.23.jar:8.5.23]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.23.jar:8.5.23]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200) [spring-web-5.0.2.RELEASE.jar:5.0.2.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.0.2.RELEASE.jar:5.0.2.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-8.5.23.jar:8.5.23]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-8.5.23.jar:8.5.23]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199) [tomcat-embed-core-8.5.23.jar:8.5.23]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-8.5.23.jar:8.5.23]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:478) [tomcat-embed-core-8.5.23.jar:8.5.23]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140) [tomcat-embed-core-8.5.23.jar:8.5.23]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81) [tomcat-embed-core-8.5.23.jar:8.5.23]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) [tomcat-embed-core-8.5.23.jar:8.5.23]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342) [tomcat-embed-core-8.5.23.jar:8.5.23]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:803) [tomcat-embed-core-8.5.23.jar:8.5.23]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-8.5.23.jar:8.5.23]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868) [tomcat-embed-core-8.5.23.jar:8.5.23]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1459) [tomcat-embed-core-8.5.23.jar:8.5.23]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-8.5.23.jar:8.5.23]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [?:1.8.0_144]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [?:1.8.0_144]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-8.5.23.jar:8.5.23]
at java.lang.Thread.run(Thread.java:748) [?:1.8.0_144]
Caused by: com.nimbusds.oauth2.sdk.ParseException: The HTTP Content-Type header must be application/json; charset=UTF-8
at com.nimbusds.oauth2.sdk.util.ContentTypeUtils.ensureContentType(ContentTypeUtils.java:52) ~[oauth2-oidc-sdk-5.38.jar:5.38]
at com.nimbusds.oauth2.sdk.http.HTTPMessage.ensureContentType(HTTPMessage.java:133) ~[oauth2-oidc-sdk-5.38.jar:5.38]
at com.nimbusds.oauth2.sdk.http.HTTPResponse.ensureContentType(HTTPResponse.java:1) ~[oauth2-oidc-sdk-5.38.jar:5.38]
at com.nimbusds.oauth2.sdk.http.HTTPResponse.getContentAsJSONObject(HTTPResponse.java:369) ~[oauth2-oidc-sdk-5.38.jar:5.38]
at com.nimbusds.oauth2.sdk.AccessTokenResponse.parse(AccessTokenResponse.java:235) ~[oauth2-oidc-sdk-5.38.jar:5.38]
at com.nimbusds.oauth2.sdk.TokenResponse.parse(TokenResponse.java:74) ~[oauth2-oidc-sdk-5.38.jar:5.38]
at org.springframework.security.oauth2.client.endpoint.NimbusAuthorizationCodeTokenResponseClient.getTokenResponse(NimbusAuthorizationCodeTokenResponseClient.java:101) ~[spring-security-oauth2-client-5.0.0.RELEASE.jar:5.0.0.RELEASE]
... 60 more
服务器代码
@Configuration
@EnableAuthorizationServer
public class OAuth2AuthServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Override
public void configure(final ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient("xe").secret("password")
.authorizedGrantTypes("authorization_code", "refresh_token", "password").scopes("user")
.autoApprove(true);
}
@Override
public void configure(final AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
}
@Override
public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.tokenStore(tokenStore()).authenticationManager(authenticationManager);
}
@Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
}
@Configuration
@EnableWebSecurity
@Order(-20)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(final HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/login").permitAll().anyRequest().authenticated()
.and().formLogin().permitAll();
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser(User.withDefaultPasswordEncoder().username("tom").password("111").roles("USER"));
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
spring boot 2 客户端配置
The spring boot 2 client configuration
spring.security.oauth2.client.registration.xe.client-id=xe
spring.security.oauth2.client.registration.xe.client-secret=password
spring.security.oauth2.client.registration.xe.client-name=xe
spring.security.oauth2.client.registration.xe.provider=x-auth
spring.security.oauth2.client.registration.xe.scope=user
spring.security.oauth2.client.registration.xe.redirect-uri-template={baseUrl}/login/oauth2/code/{registrationId}
spring.security.oauth2.client.registration.xe.client-authentication-method=post
spring.security.oauth2.client.registration.xe.authorization-grant-type=authorization_code
spring.security.oauth2.client.provider.x-auth.authorization-uri=http://localhost:9090/auth/oauth/authorize
spring.security.oauth2.client.provider.x-auth.token-uri=http://localhost:9090/auth/oauth/token
spring.security.oauth2.client.provider.x-auth.user-info-uri=http://localhost:9090/auth/user
spring.security.oauth2.client.provider.x-auth.jwk-set-uri=http://localhost:9090/token_keys
spring.security.oauth2.client.provider.x-auth.user-name-attribute=username
在服务器pom中
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.M7</version>
<relativePath />
</parent>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.2.1.RELEASE</version>
</dependency>
在客户端 pom
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.M7</version>
</parent>
...
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</artifactId>
<version>5.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
<version>5.0.0.RELEASE</version>
</dependency>
推荐答案
NimbusAuthorizationCodeTokenResponseClient
在内部使用 Nimbus OAuth 2.0 SDK 类来发送令牌请求 (TokenRequest
) 并解析令牌响应 (TokenResponse
).
NimbusAuthorizationCodeTokenResponseClient
uses Nimbus OAuth 2.0 SDK classes internally to send the Token Request (TokenRequest
) and parse the Token response (TokenResponse
).
令牌响应通过 TokenResponse.parse()
解析,这是错误发生的地方,因为它检查以确保响应中的 Content-Type
标头可用并将其设置为 application/json
.这是 spring-security-oauth 的一个错误,我已经记录了这个问题 这里.
The Token Response is parsed via TokenResponse.parse()
and this is where the error happens as it checks to ensure the Content-Type
header is available in the response and that it's set to application/json
. This is a bug with spring-security-oauth and I've logged the issue here.
在修复此问题之前,spring-security-oauth 的一种可能解决方法是使用 @ControllerAdvice
拦截来自 TokenEndpoint
的响应并添加 Content-Type=application/json
响应头,如下:
Until this is fixed, a possible workaround for spring-security-oauth would be to use a @ControllerAdvice
to intercept the response from the TokenEndpoint
and add the Content-Type=application/json
header to the response, as follows:
@ControllerAdvice(assignableTypes = TokenEndpoint.class)
public class TokenEndpointResponseAdvice<T> implements ResponseBodyAdvice<T> {
@Override
public boolean supports(MethodParameter returnType,
Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
@Nullable
@Override
public T beforeBodyWrite(@Nullable T body,
MethodParameter returnType,
MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType,
ServerHttpRequest request,
ServerHttpResponse response) {
response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
return body;
}
}
以下是有关此解决方案的更多资源:
Here are some further resources regarding this solution:
注意:我自己还没有实际尝试过,但它应该可以工作.如果没有,那么将您的代码发布到 GitHub 并将其张贴在这里,我会检查它并让它为您工作.使用发生问题的相同代码库进行故障排除会更有效.
NOTE: I have not actually tried this out myself but it should work. If it doesn't, than publish your code to GitHub and post it here and I'll check it out and get it working for you. It's more efficient to troubleshoot using the same codebase where the issue is happening.
这篇关于Spring boot 2 OAuth2“HTTP Content-Type 头必须是 application/json"的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!