Spring-Boot and DialogFlow : Why I receive an anonymous request on my webhook defined in DialogFlow? - spring-boot

I'm creating a web application with Spring Boot that defines a REST API accessible via OAuth2 authentication for use with Google Assistant
I configured DialogFlow (Webhook fulfillment configured with the URL to the endpoint of my REST API)
I configured Actions on Google: I configured the Account Linking section with OAuth information (client ID, client Secret, Authorization URL, Token URL, Scopes ...)
I tested my application with my smartphone via the Google Home application.
It tells me: "Before I can use "My App", I need to associate your "My App" account with Google. Do you agree with that?"
I say, "Yes."
I then have access to my web application for OAuth authentication.
I validate, and it says: "Perfect! Your "My App" account is now connected to Google"
Then I write the sentence "Turn on my TV", it then calls the fulfillment webhook that calls my REST API.
Only I'm getting a request that doesn't seem right. I have an error indicating that the user is anonymous. It is as if the access-token had not been transmitted in the'Authorization' header.
I can't find a way to get the complete request (Header + Body) that is sent.
I also tested on the Actions on Google Simulator but I only see the request body, not the headers. I looked at Google's logs but I don't have any more details.
Here are the logs :
19:47:41.263 [https-jsse-nio-9443-exec-6] DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Request 'POST /api/fulfillment' doesn't match 'GET /**
19:47:41.264 [https-jsse-nio-9443-exec-6] DEBUG o.s.s.w.u.m.AntPathRequestMatcher - Request '/api/fulfillment' matched by universal pattern '/**'
19:47:41.264 [https-jsse-nio-9443-exec-6] DEBUG o.s.s.w.a.i.FilterSecurityInterceptor - Secure object: FilterInvocation: URL: /api/fulfillment; Attributes: [#oauth2.throwOnError(#oauth2.hasScope('write'))]
19:47:41.264 [https-jsse-nio-9443-exec-6] DEBUG o.s.s.w.a.i.FilterSecurityInterceptor - Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken#2629f42a: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails#ffffc434: RemoteIpAddress: 35.184.134.60; SessionId: null; Granted Authorities: ROLE_ANONYMOUS
19:47:41.268 [https-jsse-nio-9443-exec-6] DEBUG o.s.s.w.a.ExceptionTranslationFilter - Access is denied (user is anonymous); redirecting to authentication entry point
org.springframework.security.access.AccessDeniedException: Insufficient scope for this resource
at org.springframework.security.oauth2.provider.expression.OAuth2SecurityExpressionMethods.throwOnError(OAuth2SecurityExpressionMethods.java:72)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.springframework.expression.spel.support.ReflectiveMethodExecutor.execute(ReflectiveMethodExecutor.java:120)
at org.springframework.expression.spel.ast.MethodReference.getValueInternal(MethodReference.java:111)
at org.springframework.expression.spel.ast.MethodReference.access$000(MethodReference.java:54)
at org.springframework.expression.spel.ast.MethodReference$MethodValueRef.getValue(MethodReference.java:391)
at org.springframework.expression.spel.ast.CompoundExpression.getValueInternal(CompoundExpression.java:89)
at org.springframework.expression.spel.ast.SpelNodeImpl.getTypedValue(SpelNodeImpl.java:116)
at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:306)
at org.springframework.security.access.expression.ExpressionUtils.evaluateAsBoolean(ExpressionUtils.java:26)
at org.springframework.security.web.access.expression.WebExpressionVoter.vote(WebExpressionVoter.java:52)
at org.springframework.security.web.access.expression.WebExpressionVoter.vote(WebExpressionVoter.java:33)
at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:63)
at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:233)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:124)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:91)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:119)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:137)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:170)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter.doFilter(OAuth2AuthenticationProcessingFilter.java:176)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:66)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178)
at org.springframework.security.web.debug.DebugFilter.invokeWithWrappedRequest(DebugFilter.java:90)
at org.springframework.security.web.debug.DebugFilter.doFilter(DebugFilter.java:77)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:357)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:270)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:109)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:613)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:803)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:790)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1468)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
Caused by: org.springframework.security.oauth2.common.exceptions.InsufficientScopeException: Insufficient scope for this resource
at org.springframework.security.oauth2.provider.expression.OAuth2SecurityExpressionMethods.throwOnError(OAuth2SecurityExpressionMethods.java:71)
... 81 common frames omitted
19:47:41.271 [https-jsse-nio-9443-exec-6] DEBUG o.s.s.w.a.ExceptionTranslationFilter - Calling Authentication entry point.
19:47:41.275 [https-jsse-nio-9443-exec-6] DEBUG o.s.s.o.p.e.DefaultOAuth2ExceptionRenderer - Written [error="unauthorized", error_description="Full authentication is required to access this resource"] as "application/json;charset=UTF-8" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter#1ff3a97]
19:47:41.276 [https-jsse-nio-9443-exec-6] DEBUG o.s.s.w.c.SecurityContextPersistenceFilter - SecurityContextHolder now cleared, as request processing completed
I tested on "OAuth 2.0 Playground" (https://developers.google.com/oauthplayground), and it works perfectly!
Is this due to the fact that my App hasn't been released and I'm still in test mode?
Anybody got any ideas?

This is because Google isn't sending the bearer token in the Authorization header (for various reasons, but at least partially because some services are using this to authorize the service - not the user of the service). It sends it as part of the JSON body.
If you are using the Action SDK, you'll find this in user.accessToken. In Dialogflow, this will be under originalDetectIntentRequest.payload.user.accessToken.

Following Prisoner's answer, I created a custom org.springframework.security.oauth2.provider.authentication.TokenExtractor to manage the access-token present in the request body.
Here is the Kotlin code:
class BodyTokenExtractor : BearerTokenExtractor() {
private val logger = LogFactory.getLog(BodyTokenExtractor::class.java)
override fun extractToken(request: HttpServletRequest): String? {
var token: String? = null
if (HttpMethod.POST.matches(request.method)) {
token = extractBodyToken(request)
}
if (token == null) {
logger.debug("Token not found in body. Trying request headers.")
token = super.extractToken(request)
} else {
request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_TYPE, OAuth2AccessToken.BEARER_TYPE)
}
return token
}
/**
* Extract the OAuth token from the request body.
*
* #param request The request.
* #return The token, or null if no OAuth authorization header was supplied.
*/
protected fun extractBodyToken(request: HttpServletRequest): String? {
try {
val requestBody = IOUtils.toString(request.reader)
val request = JacksonFactory().fromString(requestBody, Map::class.java)
val originalDetectIntentRequest = request["originalDetectIntentRequest"] as Map<String, Object>
if (originalDetectIntentRequest != null) {
val payload = originalDetectIntentRequest["payload"] as Map<String, Object>
if (payload != null) {
val user = payload["user"] as Map<String, Object>
if (user != null) {
return user["accessToken"] as String?
}
}
}
} catch (e: IOException) {
logger.debug("An error occurred while reading the request body: " + e.message, e)
}
return null
}
}
This class is then called in the Resource Server :
#Configuration
#EnableResourceServer
class OAuthResourceServerConfig() : ResourceServerConfigurerAdapter() {
...
#Throws(Exception::class)
override fun configure(resources: ResourceServerSecurityConfigurer) {
resources.resourceId(resourceId).tokenStore(tokenStore()).tokenExtractor(tokenExtractor())
}
private fun tokenStore(): TokenStore {
return JdbcTokenStore(dataSource)
}
private fun tokenExtractor(): TokenExtractor {
return BodyTokenExtractor()
}
...
}
It is important to define a custom filter to allow to read multiple times the request body :
#Bean
fun multiReadFilter(): FilterRegistrationBean<*> {
val registrationBean = FilterRegistrationBean<CachedRequestWrapperFilter>()
val multiReadRequestFilter = CachedRequestWrapperFilter()
registrationBean.filter = multiReadRequestFilter
registrationBean.order = SecurityProperties.DEFAULT_FILTER_ORDER - 2
registrationBean.urlPatterns = Arrays.asList("/api/*")
return registrationBean
}
class CachedRequestWrapperFilter : Filter {
#Throws(ServletException::class)
override fun init(config: FilterConfig) {
// nothing goes here
}
#Throws(java.io.IOException::class, ServletException::class)
override fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
val requestWrapper = CachedHttpServletRequest(request as HttpServletRequest)
// Pass request back down the filter chain
chain.doFilter(requestWrapper, response)
}
override fun destroy() {
/* Called before the Filter instance is removed from service by the web container*/
}
}
public class CachedHttpServletRequestextends HttpServletRequestWrapper {
private ByteArrayOutputStream cachedContent;
public CachedHttpServletRequest(HttpServletRequest request) throws IOException {
// Read the request body and populate the cachedContent
}
#Override
public ServletInputStream getInputStream() throws IOException {
// Create input stream from cachedContent
// and return it
}
#Override
public BufferedReader getReader() throws IOException {
// Create a reader from cachedContent
// and return it
}
}
There are plenty of examples explaining how to wrap the request and read multiple times.
Example: http://www.myjavarecipes.com/tag/how-to-read-request-twice/
By doing that, it works perfectly!

Related

Keycloak + Spring Boot + Spring Security does somehow token validation 2 times if the token is invalid

i'm using spring security with keycloak and if i do a request on a specific endpoint with an invalid token, it looks like the token validation is done 2 times, i've also tried to implement my own authentication provider which uses the logic of the keycloak authentication provider and overrided the BearerTokenRequestAuthenticator which does the token validation but it still does the same thing..i'm not sure if maybe the problem comes from some sort of bean definitions
Here are same logs where you can see that the string "Verifying access_token" appears 2 times.
2021-04-16 16:35:18,220 DEBUG 16672 --- [org.keycloak.adapters.PreAuthActionsHandler]: adminRequest http://localhost:7006/gateway/core/rest/api/core/initPayment/100356
2021-04-16 16:35:18,220 DEBUG 16672 --- [org.apache.catalina.authenticator.AuthenticatorBase]: Security checking request POST /gateway/core/rest/api/core/initPayment/100356
2021-04-16 16:35:18,220 DEBUG 16672 --- [org.apache.catalina.realm.RealmBase]: No applicable constraints defined
2021-04-16 16:35:18,220 DEBUG 16672 --- [org.apache.catalina.authenticator.AuthenticatorBase]: Not subject to any constraint
2021-04-16 16:35:18,220 DEBUG 16672 --- [org.keycloak.adapters.tomcat.AbstractAuthenticatedActionsValve]: AuthenticatedActionsValve.invoke /gateway/core/rest/api/core/initPayment/100356
2021-04-16 16:35:18,220 DEBUG 16672 --- [org.keycloak.adapters.AuthenticatedActionsHandler]: AuthenticatedActionsValve.invoke http://localhost:7006/gateway/core/rest/api/core/initPayment/100356
2021-04-16 16:35:18,220 DEBUG 16672 --- [org.keycloak.adapters.AuthenticatedActionsHandler]: Policy enforcement is disabled.
2021-04-16 16:35:18,220 DEBUG 16672 --- [org.springframework.security.web.FilterChainProxy]: /core/rest/api/core/initPayment/100356 at position 1 of 15 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
2021-04-16 16:35:18,220 DEBUG 16672 --- [org.springframework.security.web.FilterChainProxy]: /core/rest/api/core/initPayment/100356 at position 2 of 15 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
2021-04-16 16:35:18,221 DEBUG 16672 --- [org.springframework.security.web.FilterChainProxy]: /core/rest/api/core/initPayment/100356 at position 3 of 15 in additional filter chain; firing Filter: 'HeaderWriterFilter'
2021-04-16 16:35:18,221 DEBUG 16672 --- [org.springframework.security.web.FilterChainProxy]: /core/rest/api/core/initPayment/100356 at position 4 of 15 in additional filter chain; firing Filter: 'CorsFilter'
2021-04-16 16:35:18,221 DEBUG 16672 --- [org.springframework.security.web.FilterChainProxy]: /core/rest/api/core/initPayment/100356 at position 5 of 15 in additional filter chain; firing Filter: 'KeycloakPreAuthActionsFilter'
2021-04-16 16:35:18,221 DEBUG 16672 --- [org.keycloak.adapters.PreAuthActionsHandler]: adminRequest http://localhost:7006/gateway/core/rest/api/core/initPayment/100356
2021-04-16 16:35:18,221 DEBUG 16672 --- [org.springframework.security.web.FilterChainProxy]: /core/rest/api/core/initPayment/100356 at position 6 of 15 in additional filter chain; firing Filter: 'KeycloakAuthenticationProcessingFilter'
2021-04-16 16:35:18,221 DEBUG 16672 --- [org.springframework.security.web.util.matcher.OrRequestMatcher]: Trying to match using Ant [pattern='/sso/login']
2021-04-16 16:35:18,221 DEBUG 16672 --- [org.springframework.security.web.util.matcher.AntPathRequestMatcher]: Checking match of request : '/core/rest/api/core/initPayment/100356'; against '/sso/login'
2021-04-16 16:35:18,221 DEBUG 16672 --- [org.springframework.security.web.util.matcher.OrRequestMatcher]: Trying to match using RequestHeaderRequestMatcher [expectedHeaderName=Authorization, expectedHeaderValue=null]
2021-04-16 16:35:18,221 DEBUG 16672 --- [org.springframework.security.web.util.matcher.OrRequestMatcher]: matched
2021-04-16 16:35:18,221 DEBUG 16672 --- [org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter]: Request is to process authentication
2021-04-16 16:35:18,221 DEBUG 16672 --- [org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter]: Attempting Keycloak authentication
2021-04-16 16:35:18,221 DEBUG 16672 --- [org.keycloak.adapters.BearerTokenRequestAuthenticator]: Found [1] values in authorization header, selecting the first value for Bearer.
2021-04-16 16:35:18,221 DEBUG 16672 --- [org.keycloak.adapters.BearerTokenRequestAuthenticator]: Verifying access_token
2021-04-16 16:35:18,222 DEBUG 16672 --- [org.keycloak.adapters.BearerTokenRequestAuthenticator]: Failed to verify token
2021-04-16 16:35:18,222 DEBUG 16672 --- [org.keycloak.adapters.RequestAuthenticator]: Bearer FAILED
2021-04-16 16:35:18,222 DEBUG 16672 --- [org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter]: Auth outcome: FAILED
2021-04-16 16:35:18,223 DEBUG 16672 --- [org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter]: Authentication request failed: org.keycloak.adapters.springsecurity.KeycloakAuthenticationException: Invalid authorization header, see WWW-Authenticate header for details
org.keycloak.adapters.springsecurity.KeycloakAuthenticationException: Invalid authorization header, see WWW-Authenticate header for details
at org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter.attemptAuthentication(KeycloakAuthenticationProcessingFilter.java:162) ~[keycloak-spring-security-adapter-9.0.0.jar:9.0.0]
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:212) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
at org.keycloak.adapters.springsecurity.filter.KeycloakPreAuthActionsFilter.doFilter(KeycloakPreAuthActionsFilter.java:96) ~[keycloak-spring-security-adapter-9.0.0.jar:9.0.0]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:92) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:92) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:77) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:358) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:271) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:93) ~[spring-boot-actuator-2.3.9.RELEASE.jar:2.3.9.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at org.keycloak.adapters.tomcat.AbstractAuthenticatedActionsValve.invoke(AbstractAuthenticatedActionsValve.java:67) ~[spring-boot-container-bundle-9.0.0.jar:9.0.0]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at org.keycloak.adapters.tomcat.AbstractKeycloakAuthenticatorValve.invoke(AbstractKeycloakAuthenticatorValve.java:181) ~[spring-boot-container-bundle-9.0.0.jar:9.0.0]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:346) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:887) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1684) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) ~[na:na]
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) ~[na:na]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]
2021-04-16 16:35:18,223 DEBUG 16672 --- [org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter]: Updated SecurityContextHolder to contain null Authentication
2021-04-16 16:35:18,223 DEBUG 16672 --- [org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter]: Delegating to authentication failure handler org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationFailureHandler#331b3993
2021-04-16 16:35:18,223 DEBUG 16672 --- [org.springframework.security.web.header.writers.HstsHeaderWriter]: Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher#784a6674
2021-04-16 16:35:18,223 DEBUG 16672 --- [org.springframework.security.web.context.SecurityContextPersistenceFilter]: SecurityContextHolder now cleared, as request processing completed
2021-04-16 16:35:18,223 DEBUG 16672 --- [org.apache.catalina.core.ContainerBase.[Tomcat].[localhost]]: Processing ErrorPage[errorCode=0, location=/error]
2021-04-16 16:35:18,223 DEBUG 16672 --- [org.springframework.security.web.FilterChainProxy]: /error at position 1 of 15 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
2021-04-16 16:35:18,224 DEBUG 16672 --- [org.springframework.security.web.FilterChainProxy]: /error at position 2 of 15 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
2021-04-16 16:35:18,225 DEBUG 16672 --- [org.springframework.security.web.FilterChainProxy]: /error at position 3 of 15 in additional filter chain; firing Filter: 'HeaderWriterFilter'
2021-04-16 16:35:18,225 DEBUG 16672 --- [org.springframework.security.web.FilterChainProxy]: /error at position 4 of 15 in additional filter chain; firing Filter: 'CorsFilter'
2021-04-16 16:35:18,225 DEBUG 16672 --- [org.springframework.security.web.FilterChainProxy]: /error at position 5 of 15 in additional filter chain; firing Filter: 'KeycloakPreAuthActionsFilter'
2021-04-16 16:35:18,225 DEBUG 16672 --- [org.keycloak.adapters.PreAuthActionsHandler]: adminRequest http://localhost:7006/gateway/error
2021-04-16 16:35:18,225 DEBUG 16672 --- [org.springframework.security.web.FilterChainProxy]: /error at position 6 of 15 in additional filter chain; firing Filter: 'KeycloakAuthenticationProcessingFilter'
2021-04-16 16:35:18,225 DEBUG 16672 --- [org.springframework.security.web.util.matcher.OrRequestMatcher]: Trying to match using Ant [pattern='/sso/login']
2021-04-16 16:35:18,225 DEBUG 16672 --- [org.springframework.security.web.util.matcher.AntPathRequestMatcher]: Checking match of request : '/error'; against '/sso/login'
2021-04-16 16:35:18,225 DEBUG 16672 --- [org.springframework.security.web.util.matcher.OrRequestMatcher]: Trying to match using RequestHeaderRequestMatcher [expectedHeaderName=Authorization, expectedHeaderValue=null]
2021-04-16 16:35:18,225 DEBUG 16672 --- [org.springframework.security.web.util.matcher.OrRequestMatcher]: matched
2021-04-16 16:35:18,225 DEBUG 16672 --- [org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter]: Request is to process authentication
2021-04-16 16:35:18,225 DEBUG 16672 --- [org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter]: Attempting Keycloak authentication
2021-04-16 16:35:18,225 DEBUG 16672 --- [org.keycloak.adapters.BearerTokenRequestAuthenticator]: Found [1] values in authorization header, selecting the first value for Bearer.
2021-04-16 16:35:18,225 DEBUG 16672 --- [org.keycloak.adapters.BearerTokenRequestAuthenticator]: Verifying access_token
2021-04-16 16:35:18,225 DEBUG 16672 --- [org.keycloak.adapters.BearerTokenRequestAuthenticator]: Failed to verify token
2021-04-16 16:35:18,225 DEBUG 16672 --- [org.keycloak.adapters.RequestAuthenticator]: Bearer FAILED
2021-04-16 16:35:18,225 DEBUG 16672 --- [org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter]: Auth outcome: FAILED
2021-04-16 16:35:18,226 DEBUG 16672 --- [org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter]: Authentication request failed: org.keycloak.adapters.springsecurity.KeycloakAuthenticationException: Invalid authorization header, see WWW-Authenticate header for details
org.keycloak.adapters.springsecurity.KeycloakAuthenticationException: Invalid authorization header, see WWW-Authenticate header for details
at org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter.attemptAuthentication(KeycloakAuthenticationProcessingFilter.java:162) ~[keycloak-spring-security-adapter-9.0.0.jar:9.0.0]
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:212) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
at org.keycloak.adapters.springsecurity.filter.KeycloakPreAuthActionsFilter.doFilter(KeycloakPreAuthActionsFilter.java:96) ~[keycloak-spring-security-adapter-9.0.0.jar:9.0.0]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:103) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:103) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:103) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178) ~[spring-security-web-5.3.8.RELEASE.jar:5.3.8.RELEASE]
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:358) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:271) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:103) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:103) ~[spring-web-5.2.13.RELEASE.jar:5.2.13.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:710) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:459) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:384) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:312) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at org.apache.catalina.core.StandardHostValve.custom(StandardHostValve.java:398) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at org.apache.catalina.core.StandardHostValve.status(StandardHostValve.java:257) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:179) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:346) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:887) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1684) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) ~[na:na]
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) ~[na:na]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.43.jar:9.0.43]
at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]
2021-04-16 16:35:18,226 DEBUG 16672 ---
Here is my security config class
#Configuration
#EnableWebSecurity
#ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
#KeycloakConfiguration
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
private static final String[] SWAGGER_RESOURCES_WHITELIST = {
"/authenticate/rest/api/authenticate/v2/api-docs",
"/core/rest/api/core/v2/api-docs",
"/v2/api-docs",
"/swagger-resources",
"/swagger-resources/**",
"/configuration/ui",
"/configuration/security",
"/swagger-ui.html",
"/webjars/**",
"/v3/api-docs/**",
"/swagger-ui/**",
"/swagger-ui.html"
};
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.cors().and().csrf().disable().sessionManagement().
sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()
.antMatchers("/authenticate/rest/api/authenticate/token").permitAll()
.antMatchers("/authenticate/rest/api/authenticate/refreshToken").permitAll()
.anyRequest().authenticated();
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
return source;
}
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new NullAuthenticatedSessionStrategy();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
/* remove default spring "ROLE_" prefix appending to keycloak's roles*/
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
// *************************** Avoid Bean redefinition ********************************
#Bean
public FilterRegistrationBean keycloakAuthenticationProcessingFilterRegistrationBean(
KeycloakAuthenticationProcessingFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(filter);
registrationBean.setEnabled(false);
return registrationBean;
}
#Bean
public FilterRegistrationBean keycloakPreAuthActionsFilterRegistrationBean(
KeycloakPreAuthActionsFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
registrationBean.setEnabled(false);
return registrationBean;
}
#Bean
public FilterRegistrationBean keycloakAuthenticatedActionsFilterBean(
KeycloakAuthenticatedActionsFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
registrationBean.setEnabled(false);
return registrationBean;
}
#Bean
public FilterRegistrationBean keycloakSecurityContextRequestFilterBean(
KeycloakSecurityContextRequestFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
registrationBean.setEnabled(false);
return registrationBean;
}
#Bean
#Override
#ConditionalOnMissingBean(HttpSessionManager.class)
protected HttpSessionManager httpSessionManager() {
return new HttpSessionManager();
}
}
I was able to solve this problem INDIRECTLY.
I was looking for a solution to customizing KeyCloak authentication exceptions.
Look at my answer here: Keycloak get 401 error, but spring security does not handle this error
I also noticed that while troubleshooting, an invalid expired token validation was done TWICE.
After I added my CustomKeycloakAuthenticationFailureHandler I realized that invalid tokens were only validated ONCE.
The default KeycloakAuthenticationFailureHandler has a line: response.sendError(401, "Unable to authenticate using the Authorization header");
If this line is executed for some reason you will experience the DOUBLE validation and response header. I removed this line for my solution, and added my own output response.
You can add JwtAuthorizationTokenFilter and call it before each request
In your security config use addFilterBefore()
// SecurityConfig changes
private final JwtAuthenticationEntryPoint unauthorizedHandler;
private final JwtAuthorizationTokenFilter authenticationTokenFilter;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
// we don't need CSRF because our token is invulnerable
.csrf().disable()
.exceptionHandling()
.defaultAuthenticationEntryPointFor(
getRestAuthenticationEntryPoint(),
new AntPathRequestMatcher("/**")
)
.authenticationEntryPoint(unauthorizedHandler).and()
// don't create session
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// system state endpoint
.antMatchers("/ping").permitAll()
// User authentication actions
.antMatchers("/auth" + "/**").permitAll()
.antMatchers("/**/*.css").permitAll()
.anyRequest().authenticated()
;
http.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
;
// disable page caching
http
.headers()
.frameOptions().sameOrigin()
.cacheControl();
}
JwtAuthorizationTokenFilter class
#Component
#AllArgsConstructor
public class JwtAuthorizationTokenFilter extends OncePerRequestFilter {
public static final String AUTHORIZATION_HEADER = HttpHeaders.AUTHORIZATION;
public static final String AUTHORIZATION_TOKEN_TYPE = "Bearer";
private final SSOManager ssoManager;
private final SimpleAuthorityMapper simpleAuthorityMapper = new SimpleAuthorityMapper();
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
String requestURI = request.getRequestURI();
boolean isRefreshRequest = ("/auth" + "/refresh").equals(requestURI);
final String requestHeader = request.getHeader(AUTHORIZATION_HEADER);
final String authenticationHeader = AUTHORIZATION_TOKEN_TYPE + " ";
if (requestHeader != null && requestHeader.startsWith(authenticationHeader)) {
try {
String authToken = requestHeader.substring(authenticationHeader.length());
AccessToken accessToken = isRefreshRequest ?
ssoManager.loadAccessTokenFromRefreshToken(authToken) :
ssoManager.loadAccessToken(authToken);
if (accessToken != null) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication instanceof AnonymousAuthenticationToken || authentication == null) {
// security context was null, so authorizing user
JwtKeycloakAuthenticationToken jwtKeycloakAuthenticationToken = createJwtKeycloakAuthenticationToken(accessToken);
SecurityContextHolder.getContext().setAuthentication(jwtKeycloakAuthenticationToken);
}
}
} catch (Exception e) {
// ToDo: improve error handling
// the token is expired and not valid anymore, TokenNotActiveException
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
return;
}
}
chain.doFilter(request, response);
}
public JwtKeycloakAuthenticationToken createJwtKeycloakAuthenticationToken(AccessToken accessToken) {
String username = accessToken.getPreferredUsername();
Map<String, AccessToken.Access> resourceAccess = accessToken.getResourceAccess();
UserPrincipal userPrincipal = new UserPrincipal(username);
Set<GrantedAuthority> authorities = getAuthorities(resourceAccess);
UserProfileDto userProfile = buildUserProfile(accessToken);
JwtKeycloakAuthenticationToken jwtKeycloakAuthenticationToken = new JwtKeycloakAuthenticationToken(userPrincipal, authorities, accessToken, userProfile);
return jwtKeycloakAuthenticationToken;
}
Set<GrantedAuthority> getAuthorities(Map<String, AccessToken.Access> resourceAccess) {
Set<GrantedAuthority> roles = new HashSet<>();
resourceAccess.forEach((key, value) -> {
Set<SimpleGrantedAuthority> realmRoles = value.getRoles().stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toSet());
roles.addAll(realmRoles);
});
Set<GrantedAuthority> grantedAuthorities = simpleAuthorityMapper.mapAuthorities(roles);
return grantedAuthorities;
}
public UserProfileDto buildUserProfile(AccessToken accessToken) {
return UserProfileDto.builder()
.userId(accessToken.getSubject())
.username(accessToken.getPreferredUsername())
.fullName(accessToken.getName())
.firstName(accessToken.getGivenName())
.lastName(accessToken.getFamilyName())
.email(accessToken.getEmail())
.roles(getResourceRoles(accessToken))
.build();
}
private static Set<String> getResourceRoles(AccessToken accessToken) {
final String realmClient = accessToken.getIssuedFor();
final Map<String, AccessToken.Access> resourceAccess = accessToken.getResourceAccess();
if (resourceAccess.containsKey(realmClient)) {
final AccessToken.Access access = resourceAccess.get(realmClient);
return access.getRoles();
}
return Collections.emptySet();
}
}
Some custom SSOManager interface we used:
public interface SSOManager {
/**
* This method will verify user access token and provide userId if token is valid. in case of
* invalid access token it will throw ProjectCommon exception with 401.
*/
AccessToken loadAccessToken(String token) throws TokenNotActiveException, VerificationException, NoSuchFieldException;
AccessToken loadAccessTokenFromRefreshToken(String token) throws TokenNotActiveException, VerificationException, NoSuchFieldException;
/**
* this method will do the user login with key cloak. after login it will provide access token object.
*/
AccessTokenResponse login(String userName, String password);
AccessTokenResponse refresh(String refreshToken);
void logout(String refreshToken);
}
And KeyCloakServiceImpl implementing SSOManager with all authenticating magic:
#Component
#Slf4j
public class KeyCloakServiceImpl implements SSOManager {
private RestTemplate restTemplate;
private final KeyCloakConnectionProvider keyCloakConnectionProvider;
#Autowired
public KeyCloakServiceImpl(KeyCloakConnectionProvider keyCloakConnectionProvider,
RestTemplateBuilder restTemplateBuilder) throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
this.keyCloakConnectionProvider = keyCloakConnectionProvider;
TrustStrategy acceptingTrustStrategy = (X509Certificate[] chain, String authType) -> true;
SSLContext sslContext = org.apache.http.ssl.SSLContexts.custom()
.loadTrustMaterial(null, acceptingTrustStrategy)
.build();
SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext);
CloseableHttpClient httpClient = HttpClients.custom()
.setSSLSocketFactory(csf)
.build();
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
this.restTemplate = restTemplateBuilder
.requestFactory(requestFactory)
.messageConverters(new MappingJackson2HttpMessageConverter(), new FormHttpMessageConverter())
.build();
}
private AccessToken getAccessToken(String accessToken, boolean checkActive) throws VerificationException, NoSuchFieldException {
try {
PublicKey publicKey = getPublicKey();
if (publicKey != null) {
String realmUrl = keyCloakConnectionProvider.getRealmUrl();
AccessToken token =
RSATokenVerifier.verifyToken(
accessToken,
publicKey,
realmUrl,
checkActive,
true);
return token;
} else {
log.error("KeyCloakServiceImpl:verifyToken: SSO_PUBLIC_KEY is NULL.");
throw new NoSuchFieldException("KeyCloakServiceImpl:verifyToken: SSO_PUBLIC_KEY is NULL.");
}
} catch (TokenNotActiveException e) {
throw e;
} catch (VerificationException e) {
throw e;
} catch (NoSuchFieldException e) {
throw e;
} catch (Exception e) {
throw e;
}
}
#Override
public AccessToken loadAccessToken(String accessToken) throws TokenNotActiveException, VerificationException, NoSuchFieldException {
return getAccessToken(accessToken, true);
}
#Override
public AccessToken loadAccessTokenFromRefreshToken(String accessToken) throws TokenNotActiveException, VerificationException, NoSuchFieldException {
return getAccessToken(accessToken, false);
}
/**
* This method will call keycloak service to user login. after successful login it will provide
* access token.
*/
#Override
public AccessTokenResponse login(String username, String password) {
try {
MultiValueMap<String, String> requestParams = new LinkedMultiValueMap<>();
requestParams.add("client_id", keyCloakConnectionProvider.getResource());
requestParams.add("username", username);
requestParams.add("password", password);
requestParams.add("grant_type", "password");
requestParams.add("client_secret", keyCloakConnectionProvider.getClientSecret());
requestParams.add("scope", "openid");
AccessTokenResponse keycloakAccessToken = queryKeycloakByParams(requestParams);
return keycloakAccessToken;
} catch (Exception e) {
log.info(e.getMessage(), e);
throw e;
}
}
#Override
public AccessTokenResponse refresh(String refreshToken) {
try {
MultiValueMap<String, String> requestParams = new LinkedMultiValueMap<>();
requestParams.add("client_id", keyCloakConnectionProvider.getResource());
requestParams.add("grant_type", "refresh_token");
requestParams.add("client_secret", keyCloakConnectionProvider.getClientSecret());
requestParams.add("refresh_token", refreshToken);
AccessTokenResponse keycloakAccessToken = queryKeycloakByParams(requestParams);
return keycloakAccessToken;
} catch (Exception e) {
log.info(e.getMessage(), e);
throw e;
}
}
private AccessTokenResponse queryKeycloakByParams(MultiValueMap<String, String> requestParams) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(requestParams, headers);
String url = keyCloakConnectionProvider.getOpenIdConnectTokenUrl();
AccessTokenResponse keycloakAccessToken = getAccessTokenResponse(request, url);
return keycloakAccessToken;
}
private AccessTokenResponse getAccessTokenResponse(HttpEntity<MultiValueMap<String, String>> request, String url) {
try {
ResponseEntity<AccessTokenResponse> response = restTemplate.postForEntity(url, request, AccessTokenResponse.class);
return response.getBody();
} catch (ResourceAccessException e) {
log.error("KeyCloak getAccessTokenResponse: " + e.getMessage());
try {
ResponseEntity<AccessTokenResponse> response = restTemplate.postForEntity(url, request, AccessTokenResponse.class);
return response.getBody();
} catch (Exception ex) {
throw ex;
}
} catch (Exception e) {
throw e;
}
}
#Override
public void logout(String refreshToken) {
try {
MultiValueMap<String, String> requestParams = new LinkedMultiValueMap<>();
requestParams.add("client_id", keyCloakConnectionProvider.getResource());
requestParams.add("client_secret", keyCloakConnectionProvider.getClientSecret());
requestParams.add("refresh_token", refreshToken);
logoutUserSession(requestParams);
} catch (Exception e) {
log.info(e.getMessage(), e);
throw e;
}
}
private void logoutUserSession(MultiValueMap<String, String> requestParams) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(requestParams, headers);
String url = keyCloakConnectionProvider.getOpenIdConnectLogoutUrl();
restTemplate.postForEntity(url, request, Object.class);
}
private PublicKey getPublicKey() {
PublicKey publicKey = keyCloakConnectionProvider.getPublicKey();
if (publicKey == null) {
LinkedHashMap publicKeyMap = requestKeyFromKeycloak(keyCloakConnectionProvider.getOpenIdConnectCertsUrl());
publicKey = KeyCloakRsaKeyLoader.getPublicKeyFromKeyCloak(publicKeyMap);
keyCloakConnectionProvider.setPublicKey(publicKey);
}
return publicKey;
}
/**
* This method will connect to keycloak server using API call for getting public key.
*
* #param url A string value having keycloak base URL
* #return Public key JSON response string
*/
private LinkedHashMap requestKeyFromKeycloak(String url) {
try {
ResponseEntity<LinkedHashMap> response = restTemplate.getForEntity(url, LinkedHashMap.class);
LinkedHashMap body = response.getBody();
if (body != null) {
return body;
} else {
log.error("KeyCloakRsaKeyLoader:requestKeyFromKeycloak: Not able to fetch SSO public key from keycloak server");
}
} catch (Exception e) {
log.error("KeyCloakRsaKeyLoader:requestKeyFromKeycloak: Exception occurred with message = " + e.getMessage());
}
return null;
}
}
And KeyCloakConnectionProvider to work with properties
#Component
#Slf4j
#AllArgsConstructor
public class KeyCloakConnectionProvider {
private static PropertiesCache cache = PropertiesCache.getInstance();
private KeycloakSpringBootProperties keycloakProperties;
public String getAuthServerUrl() {
return keycloakProperties.getAuthServerUrl();
}
public String getRealmUrl() {
return getAuthServerUrl()
+ "/realms/"
+ getRealm();
}
public String getOpenIdConnectUrl() {
return getRealmUrl() + "/protocol/openid-connect";
}
public String getOpenIdConnectTokenUrl() {
return getOpenIdConnectUrl() + "/token";
}
public String getOpenIdConnectLogoutUrl() {
return getOpenIdConnectUrl() + "/logout";
}
public String getOpenIdConnectCertsUrl() {
return getOpenIdConnectUrl() + "/certs";
}
public String getRealm() {
return keycloakProperties.getRealm();
}
public String getResource() {
return keycloakProperties.getResource();
}
public String getClientId() {
return getResource();
}
public String getClientSecret() {
return String.valueOf(keycloakProperties.getCredentials().get("secret"));
}
public int getConnectionPoolSize() {
return keycloakProperties.getConnectionPoolSize();
}
public PublicKey getPublicKey() {
return cache.getPublicKey();
}
public PublicKey setPublicKey(PublicKey publicKey) {
if (publicKey != null) {
cache.savePublicKey(publicKey);
}
return cache.getPublicKey();
}
}
Try to get what you was looking from this files :)

Jackson Serialisation of Request Body in Kotlin Controller not working with restTemplate, works with postman

I am having troubles figuring out why I am getting this error on server:
I built a simple Controller that accepts a Request Dto:
#ResponseBody
#PostMapping("/log")
fun logReuqest(#RequestBody request: Request) {
logger.info("Request: $request")
}
The Reuest and the nested DTOs look like this:
data class Request(val customer: Customer, val dealer: Dealer)
data class Customer(val id: String, val name: String)
data class Dealer( val id: String, val name: String)
The json I am sending is:
{
"customer" : {
"id" : "123",
"name" : "customer"
},
"dealer" : {
"id" : "123",
"name" : "dealer"
}
}
The problem: I send a reuquest from postman with "application/json" contentType header, it works perfectly. But whenever I deploy this app and it gets an incoming request from another service, I get this Jackson error:
nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot
construct instance of `com.service.dto.Request` (although at least one Creator exists): no
String-argument constructor/factory method to deserialize from String value
What is wrong with the code, why does it work from postman and whenever I deploy it doesn't work on when getting incoming requests from another service with restTemplate?
StackTrace:
org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot construct instance of `com.service.dto.Request` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('{
"customer" : {
"id" : "123",
"name" : "customer"
},
"dealer" : {
"id" : "123",
"name" : "dealer"
}'); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `com.service.dto.Request` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('{
"customer" : {
"id" : "123",
"name" : "customer"
},
"dealer" : {
"id" : "123",
"name" : "dealer"
}')
at [Source: (PushbackInputStream); line: 1, column: 1]
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:387)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:342)
at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver.readWithMessageConverters(AbstractMessageConverterMethodArgumentResolver.java:186)
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.readWithMessageConverters(RequestResponseBodyMethodProcessor.java:158)
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:131)
at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121)
at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:170)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:137)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:894)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1060)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:962)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:652)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:733)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
at org.apache.catalina.valves.RemoteIpValve.invoke(RemoteIpValve.java:764)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:346)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:887)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1684)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.base/java.lang.Thread.run(Unknown Source)
Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of `com.service.dto.Request` (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('{
"customer" : {
"id" : "123",
"name" : "customer"
},
"dealer" : {
"id" : "123",
"name" : "dealer"
}')
at [Source: (PushbackInputStream); line: 1, column: 1]
at com.fasterxml.jackson.databind.exc.MismatchedInputException.from(MismatchedInputException.java:63)
at com.fasterxml.jackson.databind.DeserializationContext.reportInputMismatch(DeserializationContext.java:1588)
at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1213)
at com.fasterxml.jackson.databind.deser.std.StdDeserializer._deserializeFromString(StdDeserializer.java:311)
at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromString(BeanDeserializerBase.java:1495)
at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:207)
at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:197)
at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:322)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4593)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3601)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:376)
... 56 common frames omitted
UPDATE:
The sender app that is sending the request has a configured restTemplate with a messageConverter. How can I solve it from the receiver app?
#Bean
public Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder() {
return createObjectMapperBuilder();
}
private MappingJackson2HttpMessageConverter jackson2HttpMessageConverter() {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setObjectMapper(jackson2ObjectMapperBuilder().build());
return converter;
}
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
RestTemplate restTemplate = builder.build();
restTemplate.setMessageConverters(Collections.singletonList(jackson2HttpMessageConverter()));
return restTemplate;
}
Your question is missing the actual restTemplate calls, so without that I'm guessing a bit...
I presume your example restTemplate is sending the JSON request body as a string.
That's what this causedBy is telling you:
Caused by: com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of com.service.dto.Request (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('{
Take a look at these two example REST endpoints and try them locally yourself
#PostMapping("/log")
fun logRequest(#RequestBody request: Request): Request {
println("/log request: $request")
return request
}
#PostMapping("/log/string")
fun logRequestString(#RequestBody request: String): String {
println("/log/string request: $request")
return request
}
Example RestTemplate calls
val restTemplate = RestTemplate().apply {
messageConverters = listOf(jackson2HttpMessageConverter())
}
// Sending payload as a JSON Object
val requestBody = Request(Customer("id", "name"), Dealer("id", "name"))
val response = restTemplate.postForEntity(url, HttpEntity(requestBody), Object::class.java)
// Server logs:
// -> /log request: Request(customer=Customer(id=id, name=name), dealer=Dealer(id=id, name=name))
// Sending payload as a string
val headers = HttpHeaders().apply {
contentType = MediaType.APPLICATION_JSON
}
val response2 = restTemplate.postForEntity(
"$url/string",
HttpEntity(jacksonObjectMapper().writeValueAsString(requestBody), headers),
Object::class.java
)
// Server logs:
// -> /log/string request: "{\"customer\":{\"id\":\"id\",\"name\":\"name\"},\"dealer\":{\"id\":\"id\",\"name\":\"name\"}}"
If you remove this endpoint, you'll see this same error no String-argument constructor
#PostMapping("/log/string")
fun logRequestString(#RequestBody request: String): String {
println("/log/string request: $request")
return request
}

Serving static content results in Request method 'GET' not supported

I want to serve static content from my static directory in my project resources, but I get error: org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'GET' not supported.
I have added configured Spring Security to ignore requests starting with "/static/". I have added resource handler to WebMvcConfig to look for the static content in various different locations, I have tried removing the resource handler altogether.
I have tried adding permitAll() to the "/static/" path.
I have googled the error message and various combinations of it to find solutions. This is where I came up with the Spring Security and WebMvcConfigs. Just that these did not solve the problem for me.
Finally I have tried placing static files into different locations. The directory structure is as follows:
\resources\static\hello.css
\resources\static\css\hello.css
\resources\templates\home.html
\resources\templates\error.html
Thymeleaf works correctly, taking the templates from the templates directory.
The WebMvcConfig:
// Config Thymeleaf
private static final String VIEWS = "classpath:templates/";
#Bean
public ViewResolver viewResolver() {
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
resolver.setTemplateEngine(templateEngine());
resolver.setCharacterEncoding("UTF-8");
return resolver;
}
private ISpringTemplateEngine templateEngine() {
SpringTemplateEngine engine = new SpringTemplateEngine();
engine.setTemplateResolver(templateResolver());
return engine;
}
private ITemplateResolver templateResolver() {
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
resolver.setApplicationContext(applicationContext);
resolver.setPrefix(VIEWS);
resolver.setSuffix(".html");
resolver.setTemplateMode(TemplateMode.HTML);
return resolver;
}
// Enable static resources
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry
.addResourceHandler("/static/**")
.addResourceLocations("/resources/static/", "classpath:static/", "/static/", "classpath:resources/static");
}
The Spring Security config:
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/static/**");
}
I expect to see my css file, but instead I see the error page generated by exception handler with the message "Request method 'GET' not supported"
Here is the debug trace from the point I request the css file:
2019-06-07 11:30:29.694 DEBUG 5398 --- [nio-8080-exec-2] o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/static/css/hello.css'; against '/static/**'
2019-06-07 11:30:29.694 DEBUG 5398 --- [nio-8080-exec-2] o.s.security.web.FilterChainProxy : /static/css/hello.css has an empty filter list
org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'GET' not supported
at org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.handleNoMatch(RequestMappingInfoHandlerMapping.java:200)
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.lookupHandlerMethod(AbstractHandlerMethodMapping.java:419)
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.getHandlerInternal(AbstractHandlerMethodMapping.java:365)
at org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.getHandlerInternal(AbstractHandlerMethodMapping.java:65)
at org.springframework.web.servlet.handler.AbstractHandlerMapping.getHandler(AbstractHandlerMapping.java:401)
at org.springframework.web.servlet.DispatcherServlet.getHandler(DispatcherServlet.java:1232)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1015)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:897)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:634)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:209)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:357)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:270)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:200)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:836)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1747)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
After hours of debugging I found the reason static content was not served!
It was because of a controller with mapping without the path:
#PostMapping()
After adding a path to the mapping, the configuration above works as it should.
Very weird that it messed up the serving of static files!
Big thanks to #Ganesh for his tip in Spring Boot not serving static content
thanks #Avaruuskadetti you saved hours of my time
DEBUG:
package org.springframework.web.servlet >>>> HandlerExecutionChain
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
Iterator var2 = this.handlerMappings.iterator();
while(var2.hasNext()) {
HandlerMapping hm = (HandlerMapping)var2.next();
if (this.logger.isTraceEnabled()) {
this.logger.trace("Testing handler map [" + hm + "] in DispatcherServlet with name '" + this.getServletName() + "'");
}
HandlerExecutionChain handler = hm.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}

redirect_uri using http instead of https

I'm using spring stack (Spring Boot 2.0.1.RELEASE) for creating a site that delegues user authentication/registration to Facebook via OAuth2. When I click the "login with facebook" button I get redirected to Facebook, but Spring Security OAuth2 is creating the redirect_uri parameter using http instead of https. The application uses https and I can't figure out where this "http" is coming from.
So, how can I make Spring create the redirect_uri parameter correctly?
UPDATE
Sorry for the original post. It was late and I wanted to have the question posted before going sleep :-)
Well, my application uses Spring Boot 2.0.1.RELEASE, which comes with Spring Security 2.0.1.RELEASE and Spring Security OAuth2 5.0.4.RELEASE. My application uses Facebook for registering and authenticating users. Currently I have a test environment running in AWS (Beanstalk) and using Amazon's SSL certificate.
When I first wrote the post my issue was that the redirect_uri parameter sent by my application (by SS actually) to Facebook had a http prefix, instead of https. This was causing an error in Facebook, which only accepts https redirect urls.
Reading the docs I found the spring.security.oauth2.client.registration.facebook.redirect-uri-template property, which I set to https://[my domain]/login/oauth2/code/{registrationId}. Now Facebook processes my authentication requests and posts back to my application.
However, with the previous parameter set, now the problem has changed. Now when the Facebook's callback hits my application at AWS I get the following exception (from the logs):
2018-04-11 10:51:23 [http-nio-5000-exec-5] DEBUG o.s.s.o.c.w.OAuth2LoginAuthenticationFilter - Request is to process authentication
2018-04-11 10:51:23 [http-nio-5000-exec-5] DEBUG o.s.s.authentication.ProviderManager - Authentication attempt using org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationProvider
2018-04-11 10:51:23 [http-nio-5000-exec-5] DEBUG o.s.s.authentication.ProviderManager - Authentication attempt using org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeAuthenticationProvider
2018-04-11 10:51:23 [http-nio-5000-exec-5] DEBUG o.s.s.o.c.w.OAuth2LoginAuthenticationFilter - Authentication request failed: org.springframework.security.oauth2.core.OAuth2AuthenticationException: [invalid_redirect_uri_parameter]
org.springframework.security.oauth2.core.OAuth2AuthenticationException: [invalid_redirect_uri_parameter]
at org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationProvider.authenticate(OAuth2LoginAuthenticationProvider.java:117)
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:174)
at org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter.attemptAuthentication(OAuth2LoginAuthenticationFilter.java:159)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:212)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter.doFilterInternal(OAuth2AuthorizationRequestRedirectFilter.java:128)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:66)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:105)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:56)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:215)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:178)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:357)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:270)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:109)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:81)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:496)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:803)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:790)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1459)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
Taking a look at the sources I found that the issue seems to be in the following test in the org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationProvider class:
if (!authorizationResponse.getRedirectUri().equals(authorizationRequest.getRedirectUri())) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_REDIRECT_URI_PARAMETER_ERROR_CODE);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
To check why this comparison is failing I checked the requests and responses using Chrome's developer tools. So, this is the call to Facebook:
https://www.facebook.com/v2.8/dialog/oauth?response_type=code&client_id=[REMOVED]&scope=public_profile%20email&state=[REMOVED]&redirect_uri=https://[REMOVED]/login/oauth2/code/facebook
Everything seems to be ok, the redirect_uri parameter is using https as expected and the complete redirect_uri seems correct.
And this is Facebook's callback:
https://[REMOVED]/login/oauth2/code/facebook?code=[REMOVED]
Once again, everything seems ok. However, SS is rejecting the user authentication because request and response redirect_uris are not matching.
And this is the issue. Any idea of what is going wrong here? Am I missing something?
I encountered the same error when I m setting up a Spring Boot application to authenticate users using Facebook OAuth2 implementation. Nginx (functions as reverse proxy) is configured to front the web app and also to offload the SSL cert.
Initially, I tried to customize the property: redirect-uri-template so that the redirect uri can be hard-coded with https://{domain}/login/oauth2/code/facebook (this is because Facebook only accepts HTTPS protocol for valid OAuth Redirect URI). It didnt work as I encountered the same error: OAuth2AuthenticationException: [invalid_redirect_uri_parameter]
Then, I found the proposed solution in link, which works for me. So, it is basically to set the OAuth2 Login Application with server.use-forward-headers=true and remove the customized property: redirect-uri-template.
Hope it helps :)
I faced with exact the same problem but with Google.
Having the following architecture of microservices
Google Auth Server
Zuul Gateway (:8080)
/ \
/ \
/ \
Other OAuth2Client (:5000)
while running at local machine everything works fine, but in AWS Elastic Beanstalk I catch the very same exception.
After debugging, I found out that in my case, when OAuth2Client is behind Zuul proxy (they implemented in separate microservices) I really get different redirect_uri values in the check inside OAuth2LoginAuthenticationProvider:
if (!authorizationResponse.getRedirectUri().equals(authorizationRequest.getRedirectUri())) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_REDIRECT_URI_PARAMETER_ERROR_CODE);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
So in my case in AWS I have following values:
authorizationResponse.getRedirectUri()
http://[INNER_AWS_ESB_IP]:5000/auth/login/oauth2/code/google
authorizationRequest.getRedirectUri()
https://[MY_PROJECT_DOMAIN_NAME]/auth/login/oauth2/code/google
where [INNER_AWS_ESB_IP] is an IP address of inner network in AWS Elastic Beanstalk and [MY_PROJECT_DOMAIN_NAME] is a domain name of my project, which is hardcoded in application.yml as redirect-uri-template parameter.
I have the following config in application.yml of my OAuth2Client microservice
server:
port: 5000
servlet:
contextPath: /auth
use-forward-headers: true
spring:
security:
oauth2:
resource:
filter-order: 3
client:
registration:
google:
client-id: [REMOVED]
client-secret: [REMOVED]
redirect-uri-template: ${MY_PROJECT_DOMAIN_NAME:http://localhost:8080}/auth/login/oauth2/code/google
scope: profile,email
Loreno, what kind of architecture do you have? Can you share your config?
UPDATE
Seems that problem is connected directly with implementation of Spring Security Oauth2 Client in version science 5.0
Problem can be reproduced, if launch Zuul Gateway microservice on some separate virtual machine and other microservices should be launched at local machine ☝️ So Google should be called from the browser on VM.
The solution which helps me to avoid this problem is to add custom Filter with custom HttpServletRequestWrapper which can override method and return "right" URL to satisfy the check in OAuth2LoginAuthenticationProvider.java:115 😃
In the application.yml of the Oauth2 client
myCloudPath: ${MY_PROJECT_DOMAIN_NAME:http://localhost:8080}
In the SecurityConfig
#Value("${myCloudPath}")
private String myCloudPath;
#Override
public void configure(HttpSecurity http) throws Exception {
http.
addFilterBefore(new MyCustomFilter(myCloudPath), OAuth2LoginAuthenticationFilter.class).
...
Filter
public class MyCustomFilter implements Filter {
private static final Logger logger = LogManager.getLogger(MyCustomFilter.class);
private String myCloudPath;
public MyCustomFilter(String myCloudPath) {
this.myCloudPath= myCloudPath;
}
#Override
public void init(FilterConfig filterConfiguration) throws ServletException {
logger.info("MyCustomFilter init");
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
request = new MyHttpServletRequestWrapper((HttpServletRequest) request, myCloudPath);
chain.doFilter(request, response);
}
#Override
public void destroy() {
logger.info("MyCustomFilter destroy");
}
}
HttpServletRequestWrapper
public class MyHttpServletRequestWrapper extends HttpServletRequestWrapper {
public final String redirectUrl;
public MyHttpServletRequestWrapper(HttpServletRequest request, String myCloudPath) {
super(request);
this.redirectUrl = myCloudPath + request.getRequestURI();
}
#Override
public StringBuffer getRequestURL() {
return new StringBuffer(redirectUrl);
}
}
We faced the same issue while running in OpenShift and authenticating against Microsoft Azure. Filtering seemed like hacking, the *.redirect-uri-template properties are now deprecated, and after returning from Azure the outgoing and incoming redirect URIs did not match.
After much searching, this simple entry in application.properties solved the issue:
server.forward-headers-strategy=framework
I also faced this problem and nothing worked after trying all solutions available on google and stack overflow. This resolved the problem for me:
I upgraded the Spring-boot version to "2.3.1.RELEASE" and rebuilt the application. Then added this property in application.yml (talking about google here but i think it will work for facebook, okta and other identity provider platforms) :
security:
oauth2:
client:
registration:
google:
clientId: 'your google client id'
clientSecret: 'your google client secret'
redirect-uri: https://www.yourdomain.com/login/oauth2/code/google # important
Defining redirect-uri , did the work and that was only possible if you are working on a spring-boot 2.3.x and above release.
Don't waste your time on "redirect-uri-template" as you will be facing problem when google redirects the user-agent back to your application. Spring will still be authenticating the redirect-uri internally.
If you are working behind a reverse proxy (may be nginx) the this should be your server configuration :
location / {
absolute_redirect off;
proxy_ssl_server_name on;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_redirect off;
proxy_pass http://127.0.0.1:8080;
}
These settings on ngnix will make sure that Spring gets requests header which are coming in the request. I was using tomcat on port 8080 hence defined this in proxy_pass. You can change this for your application.
I hope this helps. Cheers.
For me works this.
I have set
redirect-uri-template: "{baseUrl}/login/oauth2/code/{registrationId}"
and wrote custom filter which chnge http to https
#Slf4j
public class LinkedInRewriteFilter extends OncePerRequestFilter {
private static final String GET_PROTOCOL = "://.*";
private static final String LINKED_IN = "linkedin";
private static final String HTTPS = "https";
#Value("${base-url}")
private String baseUrl;
#Value("${server.port}")
private int serverPort;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if (request.getRequestURL().toString().contains(LINKED_IN)) {
request = new LinkedInHttpServletRequestWrapper(request);
}
filterChain.doFilter(request, response);
}
public class LinkedInHttpServletRequestWrapper extends HttpServletRequestWrapper {
public LinkedInHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
}
#Override
public String getScheme() {
return baseUrl.replaceFirst(GET_PROTOCOL, StringUtils.EMPTY);
}
#Override
public int getServerPort() {
return HTTPS.equals(getScheme()) ? 443 : serverPort;
}
}
}
For me, I had to do these:
Step 1: Change your config file
If using application.properties
server.port=8081
server.use-forward-headers: true
If using yml
server:
port: 8081
use-forward-headers : true
Step 2: Update proxy
If using httpd
RequestHeader set X-Forwarded-Proto https
RequestHeader set X-Forwarded-Port 443
ProxyPreserveHost On
ProxyPass / http://localhost:8081/
ProxyPassReverse / http://localhost:8081/
If using nginx
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # Will add the user's ip to the request, some apps need this
proxy_set_header X-Forwarded-Proto $scheme; # will forward the protocole i.e. http, https
proxy_set_header X-Forwarded-Port $server_port; # Will forward the port
proxy_set_header Host $host; # !Important will forward the host address
proxy_pass http://localhost:8081/;
}
Step 3: Restart server and test
I think the new version of Spring Security just added new support for .redirect-uri replacing .redirect-uri-template.
Dependencies
I am using
org.springframework.boot:spring-boot-starter-web:2.3.2.RELEASE,org.springframework.boot:spring-boot-starter-oauth2-client:2.3.2.RELEASE,org.springframework.boot:spring-boot-starter-security:2.3.2.RELEASE.
Configuration File, e.g., .properties
In the configuration file, I could specify:
spring.security.oauth2.client.registration.google.redirect-uri=https://example.com/api/login/oauth2/code/google
Note that I did not use .redirect-uri-template
OAuth2 Flow
Then, Spring OAuth2 guides requests to the Google server with the URI like:
https://accounts.google.com/o/oauth2/v2/auth/oauthchooseaccount?response_type=...&
redirect_uri=https%3A%2F%2Fexample.com%2Fapi%2Flogin%2Foauth2%2Fcode%2Fgoogle
Note that redirect_uri=https%3A%2F%2Fexample.com has been encoded, where https:// protocol is used.
If you're using container apps or web apps contained over Linux, refer this answer. It could be caused by authentication redirecting to provider handle that's not served over HTTPS.

Jersey - Return customised error instead of stack trace when url has special characters

My Http Request Url has some special character like -
../MyOrder(Id='1234')?{#sleep(15)}
I am using following tech stack -
Jersey + Spring Boot and tomcat as server
Now I want this url to return some customized error instead of returning stack trace, I have tried Exception Mapper , here is the code
#Provider
public class JsonProcessingExceptionMapper implements ExceptionMapper{
public static class Error {
public String key;
public String message;
}
#Override
public Response toResponse(IllegalArgumentException exception) {
Error error = new Error();
error.key = "bad-json";
error.message = exception.getMessage();
return Response.status(Status.BAD_REQUEST).entity(error).build();
}
}
but in with it , or without this , I am getting following stack trace -
Apache Tomcat/7.0.54 - Error report HTTP Status 500 - Illegal character "#" at position 1 is not allowed as a start of a name in a path template "{#sleep(15)}$format=json".type Exception reportmessage Illegal character "#" at position 1 is not allowed as a start of a name in a path template "{#sleep(15)}$format=json".description The server encountered an internal error that prevented it from fulfilling this request.exception <pre>java.lang.IllegalArgumentException: Illegal character "#" at position 1 is not allowed as a start of a name in a path template "{#sleep(15)}$format=json".
org.glassfish.jersey.uri.internal.UriTemplateParser.parseName(UriTemplateParser.java:305)
org.glassfish.jersey.uri.internal.UriTemplateParser.parse(UriTemplateParser.java:235)
org.glassfish.jersey.uri.internal.UriTemplateParser.<init>(UriTemplateParser.java:107)
org.glassfish.jersey.uri.UriTemplate.createURIComponent(UriTemplate.java:891)
org.glassfish.jersey.uri.UriTemplate.createURIWithStringValues(UriTemplate.java:858)
org.glassfish.jersey.uri.UriTemplate.createURIWithStringValues(UriTemplate.java:797)
org.glassfish.jersey.uri.UriTemplate.createURI(UriTemplate.java:762)
org.glassfish.jersey.uri.internal.JerseyUriBuilder._build(JerseyUriBuilder.java:893)
org.glassfish.jersey.uri.internal.JerseyUriBuilder.build(JerseyUriBuilder.java:810)
org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:330)
org.glassfish.jersey.servlet.ServletContainer.service(ServletContainer.java:219)
org.springframework.boot.actuate.trace.WebRequestTraceFilter.doFilterInternal(WebRequestTraceFilter.java:110)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration$ApplicationContextFilterConfiguration$1.doFilterInternal(EndpointWebMvcAutoConfiguration.java:257)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:186)
org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:160)
org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:77)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99)
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
note The full stack trace of the root cause is available in the Apache Tomca

Resources