Loop redirect when login OAuth2.0 Login + Webflux Security - spring-boot

I am developing authentication and authorization in an environment where I use Spring Cloud Gateway Webflux + OAuth 2.0 the structure to achieve is the following:
As Authorization Server I have my own OAuth server that contains the /login page where I perform the authentication and it is also in charge of generating JWT and as Resource Server I have a WebFlux module that is also in charge of being the Gateway.
The Resource Server configuration is as follows:
application.yml
spring:
application:
name: spring-boot-gateway
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://adp-auth-provider/auth/oauth/token
client:
registration:
oauth:
client-name: oauth
client-id: first-client
client-secret: xxxxxx
provider: adp-auth-provider
authorization-grant-type: authorization_code
redirect-uri: /login
scope: read
provider:
adp-auth-provider:
authorization-uri: /auth/oauth/authorize
token-uri: http://adp-auth-provider/auth/oauth/token
user-info-uri: http://adp-auth-provider/userinfo
jwt-set-uri: http://adp-auth-provider/token_keys
WebFluxSecurityConfig.java
#Configuration(proxyBeanMethods = false)
#EnableWebFluxSecurity
#EnableReactiveMethodSecurity
public class WebFluxSecurityConfig {
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
return http
.httpBasic().disable()
.csrf().disable()
.authorizeExchange(exchanges -> exchanges
.pathMatchers(HttpMethod.GET, "/oauth2/authorization/**",
"/actuator",
"/actuator/**",
"/auth/login",
"/login")
.permitAll()
.anyExchange()
.authenticated()
.oauth2Login()
.and()
.build();
}
}
SpringGatewayApplication.java
#SpringBootApplication
public class SpringGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(SpringGatewayApplication.class, args);
}
}
When I type http://localhost in the browser, it redirects perfectly to the OAuth /login page, but when I enter my credentials it redirects me to the next page:
The requests appear to have been the right ones:
Does anyone know why I am not redirected to the index once I have logged in correctly? It stays on that page and if I click on oauth it redirects me to the same page again.
**EDIT:
Setting redirect-uri to the default "{baseUrl}/login/oauth2/code/{registrationId}" displays the following error:
2022-01-18 12:12:15.852 ERROR 2836 --- [ctor-http-nio-6] a.w.r.e.AbstractErrorWebExceptionHandler : [477242e5-1] 500 Server Error for HTTP GET "/login/oauth2/code/oauth?code=nTCRNi&state=Ub8jQjbp1baxhgsxcpNULMMHoV8z42bQsp62iL2jNV8%3D"
java.lang.IllegalStateException: No provider found for class org.springframework.security.oauth2.client.authentication.OAuth2AuthorizationCodeAuthenticationToken
at org.springframework.security.web.server.authentication.AuthenticationWebFilter.lambda$authenticate$6(AuthenticationWebFilter.java:123) ~[spring-security-web-5.6.0.jar:5.6.0]
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
*__checkpoint ⇢ org.springframework.security.oauth2.client.web.server.authentication.OAuth2LoginAuthenticationWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ org.springframework.security.oauth2.client.web.server.OAuth2AuthorizationRequestRedirectWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ org.springframework.security.web.server.context.ReactorContextWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ org.springframework.security.web.server.header.HttpHeaderWriterWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ org.springframework.security.config.web.server.ServerHttpSecurity$ServerWebExchangeReactorContextWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ org.springframework.security.web.server.WebFilterChainProxy [DefaultWebFilterChain]
*__checkpoint ⇢ org.springframework.boot.actuate.metrics.web.reactive.server.MetricsWebFilter [DefaultWebFilterChain]
*__checkpoint ⇢ HTTP GET "/login/oauth2/code/oauth?code=nTCRNi&state=Ub8jQjbp1baxhgsxcpNULMMHoV8z42bQsp62iL2jNV8%3D" [ExceptionHandlingWebHandler]
Original Stack Trace:
at org.springframework.security.web.server.authentication.AuthenticationWebFilter.lambda$authenticate$6(AuthenticationWebFilter.java:123) ~[spring-security-web-5.6.0.jar:5.6.0]
at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:44) ~[reactor-core-3.4.12.jar:3.4.12]
at reactor.core.publisher.Mono.subscribe(Mono.java:4400) ~[reactor-core-3.4.12.jar:3.4.12]
at reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onComplete(FluxSwitchIfEmpty.java:82) ~[reactor-core-3.4.12.jar:3.4.12]
at reactor.core.publisher.FluxHide$SuppressFuseableSubscriber.onComplete(FluxHide.java:147) ~[reactor-core-3.4.12.jar:3.4.12]
at reactor.core.publisher.MonoNext$NextSubscriber.onComplete(MonoNext.java:102) ~[reactor-core-3.4.12.jar:3.4.12]
at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.drain(FluxConcatMap.java:367) ~[reactor-core-3.4.12.jar:3.4.12]
at reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.innerComplete(FluxConcatMap.java:296) ~[reactor-core-3.4.12.jar:3.4.12]
at reactor.core.publisher.FluxConcatMap$ConcatMapInner.onComplete(FluxConcatMap.java:885) ~[reactor-core-3.4.12.jar:3.4.12]
at reactor.core.publisher.MonoFlatMap$FlatMapMain.secondComplete(MonoFlatMap.java:196) ~[reactor-core-3.4.12.jar:3.4.12]
at reactor.core.publisher.MonoFlatMap$FlatMapInner.onComplete(MonoFlatMap.java:268) ~[reactor-core-3.4.12.jar:3.4.12]
at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:142) ~[reactor-core-3.4.12.jar:3.4.12]
at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onComplete(Operators.java:2058) ~[reactor-core-3.4.12.jar:3.4.12]
at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onComplete(Operators.java:2058) ~[reactor-core-3.4.12.jar:3.4.12]
at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onComplete(FluxMapFuseable.java:150) ~[reactor-core-3.4.12.jar:3.4.12]
at reactor.core.publisher.MonoFlatMap$FlatMapMain.secondComplete(MonoFlatMap.java:196) ~[reactor-core-3.4.12.jar:3.4.12]
at reactor.core.publisher.MonoFlatMap$FlatMapInner.onComplete(MonoFlatMap.java:268) ~[reactor-core-3.4.12.jar:3.4.12]
at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onComplete(Operators.java:2058) ~[reactor-core-3.4.12.jar:3.4.12]
at reactor.core.publisher.MonoNext$NextSubscriber.onComplete(MonoNext.java:102) ~[reactor-core-3.4.12.jar:3.4.12]
at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:142) ~[reactor-core-3.4.12.jar:3.4.12]
at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:142) ~[reactor-core-3.4.12.jar:3.4.12]
at reactor.core.publisher.FluxPeek$PeekSubscriber.onComplete(FluxPeek.java:260) ~[reactor-core-3.4.12.jar:3.4.12]
at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:142) ~[reactor-core-3.4.12.jar:3.4.12]
at reactor.netty.channel.FluxReceive.onInboundComplete(FluxReceive.java:400) ~[reactor-netty-core-1.0.13.jar:1.0.13]
at reactor.netty.channel.ChannelOperations.onInboundComplete(ChannelOperations.java:419) ~[reactor-netty-core-1.0.13.jar:1.0.13]
at reactor.netty.channel.ChannelOperations.terminate(ChannelOperations.java:473) ~[reactor-netty-core-1.0.13.jar:1.0.13]
at reactor.netty.http.client.HttpClientOperations.onInboundNext(HttpClientOperations.java:702) ~[reactor-netty-http-1.0.13.jar:1.0.13]
at reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:93) ~[reactor-netty-core-1.0.13.jar:1.0.13]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:103) ~[netty-codec-4.1.70.Final.jar:4.1.70.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:324) ~[netty-codec-4.1.70.Final.jar:4.1.70.Final]
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:296) ~[netty-codec-4.1.70.Final.jar:4.1.70.Final]
at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:719) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:655) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:581) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493) ~[netty-transport-4.1.70.Final.jar:4.1.70.Final]
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:986) ~[netty-common-4.1.70.Final.jar:4.1.70.Final]
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.70.Final.jar:4.1.70.Final]
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.70.Final.jar:4.1.70.Final]
at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]
The application.yml now looks like this:
spring:
application:
name: spring-boot-gateway
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://localhost/auth/oauth/token
client:
registration:
oauth:
client-name: oauth
client-id: first-client
client-secret: xxxx
provider: adp-auth-provider
authorization-grant-type: authorization_code
redirect-uri: '{baseUrl}/login/oauth2/code/{registrationId}'
scope: read
provider:
adp-auth-provider:
authorization-uri: http://localhost/auth/oauth/authorize
token-uri: http://localhost/auth/oauth/token
user-info-uri: http://localhost/auth/me
user-name-attribute: sub

You have specified a redirect-uri for your client of /login. The page that says "Login with OAuth 2.0" is an auto-generated login page that Spring Security makes available by default under the /login endpoint. I don't think you intended to redirect there, but you currently have configured your client to do so.
The docs for OAuth 2.0 Login with WebFlux (Reactive) have recently been rewritten to align with the Servlet version, and are worth reading in their entirety.
Read the section of the docs on the Redirection Endpoint. Until you have a basic flow working, I'd recommend setting your redirect-uri to the default value of "{baseUrl}/login/oauth2/code/{registrationId}". Once things work, you can begin exploring how to customize this value. As the docs state, keep in mind that changing your redirect-uri property for a client also requires customizing the Redirection Endpoint in Spring Security to match.
If you also wish to customize the default Login Page, see the previous section of the docs, OAuth 2.0 Login Page.

The problem was occurring because the default authentication manager wasn't working for me, I had to implement one specifically for my problem.
#Configuration(proxyBeanMethods = false)
#EnableWebFluxSecurity
#EnableReactiveMethodSecurity
public class WebFluxSecurityConfig {
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http, AuthenticationManager authenticationManager) {
return http
.httpBasic().disable()
.csrf().disable()
.authorizeExchange(exchanges -> exchanges
.pathMatchers(HttpMethod.GET, "/oauth2/authorization/**",
"/actuator",
"/actuator/**",
"/auth/login",
"/login/**")
.permitAll()
.anyExchange()
.authenticated()
.oauth2Login()
.authenticationManager(authenticationManager)
.and()
.build();
}
}
I also had to modify the redirect-uri and leave it as '/login/oauth2/code/{registrationId}'.
spring:
application:
name: spring-boot-gateway
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://127.0.0.1/auth/oauth/token
client:
registration:
oauth:
client-name: oauth
client-id: first-client
client-secret: xxxxx
provider: adp-auth-provider
authorization-grant-type: authorization_code
redirect-uri: '/login/oauth2/code/{registrationId}'
scope: read
provider:
adp-auth-provider:
authorization-uri: /auth/oauth/authorize
token-uri: /auth/oauth/token
user-info-uri: /auth/me

Related

Spring Oauth2 Login not working after migrating to Spring Boot 3

After migration to Spring Boot 3 from 2.7.5, when trying to login and get into infinity loop in the login screen.
After debugging we found this exception:
org.springframework.security.oauth2.core.OAuth2AuthorizationException: [invalid_request] client_secret is must in DefaultAuthorizationCodeTokenResponseClient.getTokenResponse(OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest)
You can check our how is defined our SecurityFilter chain.
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.ignoringRequestMatchers(new CsrfIgnoreRequestMatcher())
)
.headers(headers -> headers
.cacheControl().disable()
.frameOptions().disable()
)
//Access configuration
.authorizeHttpRequests(authorizeRequest -> authorizeRequest
.requestMatchers(HttpMethod.OPTIONS).permitAll()
.requestMatchers(
LOGIN,
LOGOUT).permitAll()
)
.exceptionHandling(exceptionHandling -> exceptionHandling
.authenticationEntryPoint(new Http401UnauthorizedEntryPoint())
)
//######## OAUTH2-Login configuration ########
.oauth2Login(oAuth2Login -> oAuth2Login
.authorizationEndpoint(authorizationEndpoint -> authorizationEndpoint
.baseUri(LOGIN)
.authorizationRequestResolver(customOAuth2AuthorizationRequestResolver)
)
.loginProcessingUrl(LOGIN)
.userInfoEndpoint(userInfo -> userInfo.userAuthoritiesMapper(new RoleMapper()))
)
.logout(logout -> logout
.logoutUrl(LOGOUT)
.invalidateHttpSession(true)
.logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler(HttpStatus.OK))
);
return http.build();
}
Here are our application.yaml properties for the security:
spring:
security:
oauth2:
client:
provider:
customIdp:
authorization-uri: https://sso.company/app/login
jwk-set-uri: https://sso.company/oauth/nam/keys
token-uri: https://sso.company/oauth/nam/token?resourceServer=IdentityProviderRSUE&
user-info-uri: https://sso.company/oauth/nam/userinfo
user-name-attribute: cn
customIdpSso:
authorization-uri: https://sso.company/app/login
token-uri: ${spring.security.oauth2.client.provider.customIdp.tokenUri}
user-info-uri: ${spring.security.oauth2.client.provider.customIdp.userInfoUri}
user-name-attribute: ${spring.security.oauth2.client.provider.customIdp.userNameAttribute}
registration:
customIdp:
authorizationGrantType: authorization_code
clientAuthenticationMethod: basic
client-id: custom-client-id
clientName: Custom
client-secret: custom-client-secret
provider: customIdp
redirect-uri: "{baseUrl}/api/login"
scope: portal
customIdpSso:
authorizationGrantType: ${spring.security.oauth2.client.registration.customIdp.authorizationGrantType}
clientAuthenticationMethod: ${spring.security.oauth2.client.registration.customIdp.clientAuthenticationMethod}
clientId: ${spring.security.oauth2.client.registration.customIdp.clientId}
clientName: ${spring.security.oauth2.client.registration.customIdp.clientName}
client-secret: ${spring.security.oauth2.client.registration.customIdp.clientSecret}
provider: customIdpnosso
redirect-uri: ${spring.security.oauth2.client.registration.customIdp.redirect-uri}
scope: ${spring.security.oauth2.client.registration.customIdp.scope}
We migrated to new Spring Boot version and stoped using WebSecurityConfigurerAdapter.
If you need more information please tell us.
Due to https://docs.spring.io/spring-security/reference/5.8/migration/servlet/oauth2.html#_clientauthenticationmethod, the value for clientAuthenticationMethod should now be:
clientAuthenticationMethod: client_secret_basic
UPDATE: I've created https://github.com/spring-projects/spring-security/issues/12585 to look into making this clearer.

Springboot and KeycloakWebSecurityConfigurerAdapter

I'm trying to plug in Keycloak authentication in my web application on Springboot with KeycloakAdapter. But when i trying to login from application homepage, i get 401 error. Logfile contains this error:
2021-10-05 18:34:39,839 [http-nio-0.0.0.0-9090-exec-3] DEBUG o.k.a.s.f.KeycloakAuthenticationProcessingFilter - Request is to process authentication
2021-10-05 18:34:39,839 [http-nio-0.0.0.0-9090-exec-3] DEBUG o.k.a.s.f.KeycloakAuthenticationProcessingFilter - Attempting Keycloak authentication
2021-10-05 18:34:39,839 [http-nio-0.0.0.0-9090-exec-3] DEBUG o.apache.tomcat.util.http.Parameters - Set encoding to UTF-8
2021-10-05 18:34:39,839 [http-nio-0.0.0.0-9090-exec-3] DEBUG o.k.adapters.RequestAuthenticator - NOT_ATTEMPTED: bearer only
2021-10-05 18:34:39,839 [http-nio-0.0.0.0-9090-exec-3] DEBUG o.k.a.s.f.KeycloakAuthenticationProcessingFilter - Auth outcome: NOT_ATTEMPTED
2021-10-05 18:34:39,839 [http-nio-0.0.0.0-9090-exec-3] DEBUG o.k.a.s.f.KeycloakAuthenticationProcessingFilter - Authentication request failed: org.keycloak.adapters.springsecurity.KeycloakAuthenticationException: Authorization header not found, see WWW-Authenticate header
org.keycloak.adapters.springsecurity.KeycloakAuthenticationException: Authorization header not found, see WWW-Authenticate header
at org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter.attemptAuthentication(KeycloakAuthenticationProcessingFilter.java:168)
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.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:116)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.keycloak.adapters.springsecurity.filter.KeycloakPreAuthActionsFilter.doFilter(KeycloakPreAuthActionsFilter.java:86)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:334)
at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:92)
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:77)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
I think i need to get authorization token from Keycloak and call login endpoint with it, but i cant understand where and how to do this. And its starange, that i need authenticate before authenticate... Seems like I do something wrong. Colleagues, who integarates Keycloak with Springboot, give me a hand with this please.
My config:
#KeycloakConfiguration
#ComponentScan(
basePackageClasses = {KeycloakSecurityComponents.class},
excludeFilters = #ComponentScan.Filter(type = FilterType.REGEX, pattern = "org.keycloak.adapters.springsecurity.management.HttpSessionManager"))
public class SecurityConfiguration extends KeycloakWebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
super.configure(http);
http
.csrf().disable()
.authorizeRequests()
.antMatchers(WEBJARS_ENTRY_POINT).permitAll()
.antMatchers(DEVICE_API_ENTRY_POINT).permitAll()
.antMatchers(FORM_BASED_LOGIN_ENTRY_POINT).permitAll()
.antMatchers(PUBLIC_LOGIN_ENTRY_POINT).permitAll()
.antMatchers(TOKEN_REFRESH_ENTRY_POINT).permitAll()
.antMatchers(NON_TOKEN_BASED_AUTH_ENTRY_POINTS).permitAll()
.and()
.authorizeRequests()
.antMatchers(WS_TOKEN_BASED_AUTH_ENTRY_POINT).authenticated()
.antMatchers(TOKEN_BASED_AUTH_ENTRY_POINT).authenticated();
// #formatter:on
}
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(keycloakAuthenticationProvider());
}
}
And .yml
keycloak:
realm: "myRealm"
auth-server-url: "http://localhost:18080/auth"
ssl-required: "external"
resource: "myResource"
credentials:
secret: "xxxxxxxxxxxxxxxxxxxxxxxxx"
use-resource-role-mappings: "true"
bearer-only: "true"
I found the problem. It works if change parameter bearer-only to false in .yml
keycloak:
realm: "myRealm"
auth-server-url: "http://localhost:18080/auth"
ssl-required: "external"
resource: "myResource"
credentials:
secret: "xxxxxxxxxxxxxxxxxxxxxxxxx"
use-resource-role-mappings: "true"
bearer-only: "false"

Not able to bypass javax.net.ssl.SSLHandshakeException: General SSLEngine problem

I do understand it is not a best practice to bypass SSL certification
but for my local testing I need that. So I have two secured(https)
localhost and I want to access the second localhost from the first
one using spring gateway
For generating SSL certificate I have used this link
https://howtodoinjava.com/spring-boot/spring-boot-ssl-https-example/
To bypass SSL check I have added this piece of the code in the main class for both gateway and consumer
public static void main(String[] args) {
TrustManager[] trustAllCerts = new TrustManager[]{
new X509TrustManager() {
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public void checkClientTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
public void checkServerTrusted(
java.security.cert.X509Certificate[] certs, String authType) {
}
}
};
// Install the all-trusting trust manager
try {
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, trustAllCerts, new java.security.SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
} catch (Exception e) {
System.out.println("Error" + e);
}
SpringApplication.run(Gateway.class, args);
}
Yet I am still getting this handshake exception. What I am missing?
io.netty.handler.codec.DecoderException: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:477) ~[netty-codec-4.1.66.Final.jar:4.1.66.Final]
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:276) ~[netty-codec-4.1.66.Final.jar:4.1.66.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) [netty-transport-4.1.66.Final.jar:4.1.66.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) [netty-transport-4.1.66.Final.jar:4.1.66.Final]
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357) [netty-transport-4.1.66.Final.jar:4.1.66.Final]
at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410) [netty-transport-4.1.66.Final.jar:4.1.66.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379) [netty-transport-4.1.66.Final.jar:4.1.66.Final]
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365) [netty-transport-4.1.66.Final.jar:4.1.66.Final]
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919) [netty-transport-4.1.66.Final.jar:4.1.66.Final]
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166) [netty-transport-4.1.66.Final.jar:4.1.66.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:719) [netty-transport-4.1.66.Final.jar:4.1.66.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:655) [netty-transport-4.1.66.Final.jar:4.1.66.Final]
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:581) [netty-transport-4.1.66.Final.jar:4.1.66.Final]
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493) [netty-transport-4.1.66.Final.jar:4.1.66.Final]
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:986) [netty-common-4.1.66.Final.jar:4.1.66.Final]
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) [netty-common-4.1.66.Final.jar:4.1.66.Final]
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) [netty-common-4.1.66.Final.jar:4.1.66.Final]
at java.lang.Thread.run(Thread.java:748) [na:1.8.0_301]
Caused by: javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
at sun.security.ssl.Alert.createSSLException(Alert.java:131) ~[na:1.8.0_301]
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
|_ checkpoint ⇢ org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ HTTP GET "/consumer" [ExceptionHandlingWebHandler]
Apparently there is a hack in spring boot gateway to achieve this
spring.cloud.gateway.httpclient.ssl.use-insecure-trust-manager=true
You could check who is calling hostname verifier (via catching Exception with a breakpoint).. your stack might be not exactly the same as in tutorial someone wrote 1 year ago.
In my case I had to add
#Bean
public TLSProtocolConfigurer tlsProtocolConfigurer() {
TLSProtocolConfigurer tlsProtocolConfigurer = new TLSProtocolConfigurer();
tlsProtocolConfigurer.setSslHostnameVerification("allowAll");
return tlsProtocolConfigurer;
}
Have you checked that thread?
Unable to find valid certification path to requested target - error even after cert imported

Combine Saml2 and oAuth2 in the same authentication module

I have developed an authentication module in spring-boot based on spring-security that allowed user authentication via oAuth2 to external systems such as AAD, ADFS ...
Everything works correctly but a new client requests the use of Saml2 as an integration protocol.
Currently the module consists of the following parts
SecurityConfig.java
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf()
.disable()
.formLogin()
.disable()
.httpBasic()
.disable()
.exceptionHandling()
.authenticationEntryPoint(new RestAuthenticationEntryPoint())
.and()
// only allow access to specified URIs
.authorizeRequests()
.antMatchers("/auth/**", "/oauth2/**", "/public/**")
.permitAll()
// only allow access with fully authenticated requests
.anyRequest()
.fullyAuthenticated()
.and()
// configure OAuth2 login
.oauth2Login()
// configure token endpoint for hack
.tokenEndpoint()
.accessTokenResponseClient(getAccessTokenResponseClient())
.and()
// endpoint for authorization (the endpoint we expose and knows the third party to go to)
.authorizationEndpoint()
.baseUri(OAUTH2_AUTHORIZE_BASE_URI)
.authorizationRequestResolver(oauth2AuthorizationRequestResolver)
.authorizationRequestRepository(httpCookieOAuth2AuthorizationRequestRepository)
.and()
// endpoint for callback (where the third party service calls back after authenticating a user)
.redirectionEndpoint()
.baseUri("/oauth2/callback/*")
.and()
// the service to use
.userInfoEndpoint()
.userService(customOAuth2UserService)
.and()
.successHandler(oAuth2AuthenticationSuccessHandler)
.failureHandler(oAuth2AuthenticationFailureHandler);
// Add our custom Token based authentication filter
http.addFilterBefore(tokenAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
Application.yaml
spring:
security:
oauth2:
client:
registration:
example1:
clientId: -----------------
clientSecret: -----------------
redirectUriTemplate: -----------------
grant-type: authorization_code
authorizationGrantType: authorization_code
tokenName: code
authenticationScheme: query
clientAuthenticationScheme: form
example2:
clientId: -----------------
clientSecret: -----------------
tenant-id: -----------------
active-directory-groups: -----------------
redirectUriTemplate: -----------------
grant-type: authorization_code
authorizationGrantType: authorization_code
tokenName: code
authenticationScheme: query
clientAuthenticationScheme: form
My doubts regarding the integration with Saml2 are the following:
It is possible to combine both authentications in the same application, could you have something like this in Application.yaml?
spring:
security:
saml2:
relyingparty:
registration:
aad:
identityprovider:
entity-id: -----------------
verification.credentials:
- certificate-location: "classpath:certs/aad.cert"
singlesignon.url: -----------------
singlesignon.sign-request: false
okta:
identityprovider:
entity-id: -----------------
verification.credentials:
- certificate-location: "classpath:certs/okta.cert"
singlesignon.url: -----------------
singlesignon.sign-request: false
oauth2:
client:
registration:
example1:
clientId: -----------------
clientSecret: -----------------
redirectUriTemplate: -----------------
grant-type: authorization_code
authorizationGrantType: authorization_code
tokenName: code
authenticationScheme: query
clientAuthenticationScheme: form
example2:
clientId: -----------------
clientSecret: -----------------
tenant-id: -----------------
active-directory-groups: -----------------
redirectUriTemplate: -----------------
grant-type: authorization_code
authorizationGrantType: authorization_code
tokenName: code
authenticationScheme: query
clientAuthenticationScheme: form
If the previous configuration is possible, how would it be represented in "SecurityConfig.java -> configure (HttpSecurity http)"? Is it possible to enter saml2Login in the current configuration?
I have seen some incomplete examples where they talk about the use of "authenticationProvider" to implement this type of case. Does anyone know if this is effective?
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(oauth2AuthenticationProvider());
auth.authenticationProvider(saml2AuthenticationProvider());
auth.authenticationProvider(DDBBAuthenticationProvider());
}
Thanks for your help!
A key thing to a for is that your apps shouldn't need to know anything about SAML, as an old technology. You will want your UIs and APIs to be modern and use OAuth tokens.
Can't answer this one.
Sounds like you should be using an Authorization Server (AS) rather than doing too much in your own authentication module. Third party systems have built in support for many providers and have taken years to develop.
As an example see all of these options supported by the Curity product, which has a free community edition that you can download.
In terms of the general pattern:
Your apps speak OAuth and OpenID Connect and interact with the AS
If a business partner wants to use SAML logins against their own Identity Provider, you only need to make SAML config changes in the AS - with zero code changes to your apps

Issue Microservice routed to Gateway

I've built seperately :
Eureka Server,
Spring Cloud Gateway (+ Discovery Client),
Spring Web App (+ Discovery Client)
Eureka Server Main Class :
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
#SpringBootApplication
#EnableEurekaServer
public class ServiceRegistryApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceRegistryApplication.class, args);
}
}
and application.yml
server:
port: 8761
eureka:
client:
fetch-registry: false
register-with-eureka: false
Gateway Main Class :
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
#SpringBootApplication
#EnableEurekaClient
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
and application.yml
server:
port: 8080
spring:
application:
name: GATEWAY
cloud:
gateway:
routes:
-id: MICROSERVICE
uri: lb://MICROSERVICE
predicates:
- Path: /service/**
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defualtZone: http://localhost:8761/eureka
instance: localhost
Microservice Main Class :
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
#SpringBootApplication
#EnableEurekaClient
public class MicroserviceApplication {
public static void main(String[] args) {
SpringApplication.run(MicroserviceApplication.class, args);
}
}
Microservice Controller :
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#RequestMapping
#RestController
public class HelloController {
#GetMapping("/test")
public String getWelcomed() {
return "Welcome to Microservice !";
}
}
and application.yml
server:
port: 8099
spring:
application:
name: MICROSERVICE
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defualtZone: http://localhost:8761/eureka
instance: localhost
My issue now is that I can't access my microservice '/test' call through the gateway.
I keep getting this error :
Whitelabel Error Page This application has no configured error view,
so you are seeing this as a fallback.
Wed Dec 23 21:10:14 WEST 2020 [3c6a9e3d-9] There was an unexpected
error (type=Not Found, status=404).
org.springframework.web.server.ResponseStatusException: 404 NOT_FOUND
at
org.springframework.web.reactive.resource.ResourceWebHandler.lambda$handle$0(ResourceWebHandler.java:325)
Suppressed:
reactor.core.publisher.FluxOnAssembly$OnAssemblyException: Error has
been observed at the following site(s): |_ checkpoint ⇢
org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter
[DefaultWebFilterChain] |_ checkpoint ⇢
org.springframework.boot.actuate.metrics.web.reactive.server.MetricsWebFilter
[DefaultWebFilterChain] |_ checkpoint ⇢ HTTP GET "/service/test"
[ExceptionHandlingWebHandler] Stack trace: at
org.springframework.web.reactive.resource.ResourceWebHandler.lambda$handle$0(ResourceWebHandler.java:325)
at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:44)
at reactor.core.publisher.Mono.subscribe(Mono.java:4252) at
reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onComplete(FluxSwitchIfEmpty.java:75)
at
reactor.core.publisher.MonoFlatMap$FlatMapMain.onComplete(MonoFlatMap.java:174)
at
reactor.core.publisher.MonoNext$NextSubscriber.onComplete(MonoNext.java:96)
at
reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.drain(FluxConcatMap.java:359)
at
reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.onSubscribe(FluxConcatMap.java:211)
at
reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:161)
at
reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:86)
at reactor.core.publisher.Mono.subscribe(Mono.java:4252) at
reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.drain(MonoIgnoreThen.java:172)
at
reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:56)
at
reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:150)
at
reactor.core.publisher.FluxSwitchIfEmpty$SwitchIfEmptySubscriber.onNext(FluxSwitchIfEmpty.java:67)
at
reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:76)
at
reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.innerNext(FluxConcatMap.java:274)
at
reactor.core.publisher.FluxConcatMap$ConcatMapInner.onNext(FluxConcatMap.java:851)
at
reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:121)
at
reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:2344)
at
reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.request(FluxMapFuseable.java:162)
at
reactor.core.publisher.Operators$MultiSubscriptionSubscriber.set(Operators.java:2152)
at
reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onSubscribe(Operators.java:2026)
at
reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onSubscribe(FluxMapFuseable.java:90)
at reactor.core.publisher.MonoJust.subscribe(MonoJust.java:54) at
reactor.core.publisher.Mono.subscribe(Mono.java:4252) at
reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.drain(FluxConcatMap.java:441)
at
reactor.core.publisher.FluxConcatMap$ConcatMapImmediate.onSubscribe(FluxConcatMap.java:211)
at
reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:161)
at
reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:86)
at
reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64)
at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52)
at
reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64)
at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52)
at
reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64)
at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52)
at
reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64)
at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:52)
at reactor.core.publisher.Mono.subscribe(Mono.java:4252) at
reactor.core.publisher.MonoIgnoreThen$ThenIgnoreMain.drain(MonoIgnoreThen.java:172)
at
reactor.core.publisher.MonoIgnoreThen.subscribe(MonoIgnoreThen.java:56)
at
reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64)
at
reactor.netty.http.server.HttpServerHandle.onStateChange(HttpServerHandle.java:65)
at
reactor.netty.ReactorNetty$CompositeConnectionObserver.onStateChange(ReactorNetty.java:518)
at
reactor.netty.tcp.TcpServerBind$ChildObserver.onStateChange(TcpServerBind.java:278)
at
reactor.netty.http.server.HttpServerOperations.onInboundNext(HttpServerOperations.java:475)
at
reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:96)
at
io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at
io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at
io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
at
reactor.netty.http.server.HttpTrafficHandler.channelRead(HttpTrafficHandler.java:191)
at
io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at
io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at
io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
at
io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436)
at
io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:324)
at
io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:296)
at
io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251)
at
io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at
io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at
io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
at
io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
at
io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
at
io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
at
io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
at
io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
at
io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:719)
at
io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:655)
at
io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:581)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493) at
io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
at
io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at
io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base/java.lang.Thread.run(Thread.java:834)
I've tried hiding the gateway from my eureka server based on some suggestions I've found, yet the issue persists by addind this to my application.yml in gateway :
eureka:
client:
register-with-eureka: false
fetch-registry: false
controller should be
#RestController
#RequestMapping("/service")
public class HelloController
and the URL : http://localhost:8080/service/test
or you can leave #RequestMapping() in the controller and make the gateway using this path :
predicates:
-Path=/**
then the URL should be: http://localhost:8080/test

Resources