Keycloak + spring cloud gateway + authentication and authorization - java

I'm trying to use keycloak for authentication and also authorization in the spring cloud gateway itself. But I'm getting the below error.
Caused by: org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'springSecurityFilterChain' defined in class path resource [poc/apigateway/SecurityConfig.class]:
Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException:
Failed to instantiate [org.springframework.security.web.server.SecurityWebFilterChain]:
Factory method 'springSecurityFilterChain' threw exception; nested exception is java.lang.IllegalArgumentException:
clientRegistrationRepository cannot be null
My application.properties
spring.application.name=api-gateway
spring.cloud.gateway.default-filters=TokenRelay
spring.cloud.gateway.routes[0].id=product-service
spring.cloud.gateway.routes[0].uri=http://localhost:8009
spring.cloud.gateway.routes[0].predicates[0]=Path=/api/product
spring.cloud.gateway.routes[1].id=order-service
spring.cloud.gateway.routes[1].uri=http://localhost:8008
spring.cloud.gateway.routes[1].predicates[0]=Path=/api/order
spring.security.oauth2.client.provider.keycloak.issuer-uri=http://localhost:8080/auth/realms/microservice-realm
spring.security.oauth2.client.registration.spring-cloud-gateway-client.client-id=spring-cloud-gateway-client
spring.security.oauth2.client.registration.spring-cloud-gateway-client.client-secret=d1b3670c-f1c3-480c-9cda-8e107aec7d5b
spring.security.oauth2.client.registration.spring-cloud-gateway-client.scope=openid, profile, roles
spring.security.oauth2.client.registration.spring-cloud-gateway-client.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.spring-cloud-gateway-client.redirect-uri=http://localhost:8005/login/oauth2/code/spring-cloud-gateway-client
spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8080/auth/realms/microservice-realm
keycloak.realm=microservice-realm
keycloak.resource=spring-cloud-gateway-client
keycloak.auth-server-url=http://localhost:8080/auth
keycloak.public-client=true
keycloak.securityConstraints[0].authRoles[0]=app-user
keycloak.securityConstraints[0].securityCollections[0].patterns[0]=/api/*
My build.gradle
plugins {
id 'org.springframework.boot' version '2.5.4'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}
group = 'poc'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
repositories {
mavenCentral()
}
ext {
set('springCloudVersion', "2020.0.3")
set('keycloakVersion', '4.8.3.Final')
}
dependencies {
implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
implementation 'org.springframework.boot:spring-boot-starter-security'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
// Keycloak
implementation 'org.keycloak:keycloak-spring-boot-starter'
}
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
mavenBom "org.keycloak.bom:keycloak-adapter-bom:${keycloakVersion}"
}
}
test {
useJUnitPlatform()
}
My SecurityConfig.java
#Configuration
#EnableWebFluxSecurity
public class SecurityConfig {
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http.authorizeExchange(exchanges -> exchanges.anyExchange().authenticated())
.oauth2Login(withDefaults());
http.csrf().disable();
return http.build();
}
}
The issue comes when the application is booting up. What I'm trying to do here is do the authentication and role bases authorization in the API gateway itself.

The Keycloak adapter doesn't work with WebFlux, only with MVC, so you should remove this dependency:
mavenBom "org.keycloak.bom:keycloak-adapter-bom:${keycloakVersion}"
You can use
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
and configure the Keycloak as the Oauth server:
spring:
security:
oauth2:
client:
registration:
keycloak:
provider: keycloak
client-id: web
client-secret: '{cipher}xxxx'
authorization-grant-type: authorization_code
redirect-uri: ${keycloak-client.server-url}/login/oauth2/code/keycloak
scope: openid
provider:
keycloak:
authorization-uri: ${keycloak-client.server-url}/realms/${keycloak-client.realm}/protocol/openid-connect/auth
token-uri: ${keycloak-client.server-url}/realms/${keycloak-client.realm}/protocol/openid-connect/token
user-info-uri: ${keycloak-client.server-url}/realms/${keycloak-client.realm}/protocol/openid-connect/userinfo
jwk-set-uri: ${keycloak-client.server-url}/realms/${keycloak-client.realm}/protocol/openid-connect/certs
user-name-attribute: name
user-info-authentication-method: header
resourceserver:
jwt:
jwk-set-uri: ${keycloak-client.server-url}/realms/${keycloak-client.realm}/protocol/openid-connect/certs
keycloak-client:
server-url: https://keycloakURL/auth
realm: your-realm
And the FilterChain:
#Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
http
.authorizeExchange()
.pathMatchers(constraintsConfig.getOpenUri())
.permitAll()
.and()
.authorizeExchange()
.anyExchange().authenticated()
.and()
.oauth2ResourceServer()
.jwt();
return http.build();
}

Related

Swagger not working with spring-boot 2.7.6

I use Spring Boot 2.7.6 and I tried to configure Swagger but it is not working and I do not know why. I tried to do the same configuration than the answers given for other questions related to Swagger but it does not work. I will try to give all needed information to explain my configuration.
I have this dependency in my pom.xml:
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
I have these properties:
server.servlet.context-path=/my-api-web
server.port=8081
Here is my swagger configuration:
#Configuration
public class SwaggerConfig implements WebMvcConfigurer {
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2).select()
.apis(RequestHandlerSelectors.basePackage("com.test.api.controller")).paths(PathSelectors.any())
.build();
}
#Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
registry.addResourceHandler("/swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/swagger-ui.html");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
Here is my Spring security configuration:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(final WebSecurity web) throws Exception {
web.ignoring().antMatchers("/v2/api-docs", "/configuration/**", "/swagger*/**", "/webjars/**")
.antMatchers(HttpMethod.OPTIONS, "/**");
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
// configure provider and filter
http.requestMatchers().antMatchers("/enpoint1/**", "/endpoint2/**").and()
.authenticationProvider(this.preAuthenticatedAuthenticationProvider())
.addFilter(this.userContextFilter());
// configure what is authenticated and what is anonymous
http.authorizeRequests().antMatchers("/endpoint1/**", "/endpoint2/**").authenticated() //
.antMatchers("/**", "/v2/api-docs", "/configuration/**", "/swagger*/**", "/webjars/**").permitAll() //
.anyRequest().permitAll();
http.csrf().disable();
http.exceptionHandling().authenticationEntryPoint(this.authenticationEntryPoint());
}
...
}
When I try to connect to http://localhost:8081/my-api-web/v2/api-docs , I have a JSON but I also see this error in the console on back-end side:
ee4321ce-8c76-4da9-a3da-b8a86da13155 ERROR 2023-02-13 09:51:55.287 springfox.documentation.swagger2.mappers.ReferenceModelSpecificationToPropertyConverter - Unable to find a model that matches key ModelKey{qualifiedModelName=ModelName{namespace='java.lang', name='Void'}, viewDiscriminator=null, validationGroupDiscriminators=[], isResponse=true}
When I try to access to swagger-ui, it does not work. I tried https://localhost:8081/my-api-web/swagger-ui/ and http://localhost:8081/my-api-web/swagger-ui/index.html .
For the second one I have this error on front-end side:
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Mon Feb 13 09:55:12 CET 2023
There was an unexpected error (type=Not Found, status=404).
I also saw this error on back-end side but I do not know if it is linked:
java.lang.IllegalArgumentException: Invalid character found in method name [0x160x030x010x020x000x010x000x010xfc0x030x030xd2f0xbd0x130x07?L0xa99F0x0e0xfbu0xa40xae0xde'0xc370xf70xad0x990xcf0x830xe290xa30xac0x840xd60x0c0x18 ]. HTTP method names must be tokens
at org.apache.coyote.http11.Http11InputBuffer.parseRequestLine(Http11InputBuffer.java:419)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:271)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:891)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1784)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:750)
I do not know how to make swagger UI work for my case.
I also saw an answer with springdoc-openapi-ui but I do not know if it is useful for me to use swagger or if it is a replacement?

How to programmatically configure keycloak with spring cloud gateway api?

I am building one spring cloud gateway and in that I am implementing Keycloak Security everything works fine but need to do programmatically instead of writing in yml file there can be multiple Realms.
Below are the dependencies which I am using:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
And my application Api gateway start application code is as below:
#SpringBootApplication
#ComponentScan(basePackages = {"com", "com.b","com.auth","com.security"})
public class APIGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(APIGatewayApplication.class, args);
}
#Bean
public KeycloakConfigResolver keycloakConfigResolver() {
return new PathBasedKeycloakConfigResolver();
}
}
Security Config http code is as below:
#Configuration
public class SecurityConfig {
#Bean
public SecurityWebFilterChain springSecurityFilterChain (ServerHttpSecurity http) {
http
.authorizeExchange()
.anyExchange()
.authenticated()
.and()
.oauth2Login(); // to redirect to oauth2 login page.
return http.build();
}
}
and in my application.yml file I am adding below configuration:
spring:
security:
oauth2:
client:
provider:
my-keycloak-provider:
issuer-uri: http://localhost:8280/auth/realms/Default
registration:
keycloak-spring-gateway-client:
provider: my-keycloak-provider
client-id: Default
client-secret: 8ZRUH62Pfhfde6uqasD8dfgdhvqWt03K6
authorization-grant-type: authorization_code
redirect-uri: '{baseUrl}/app'
main:
web-application-type: reactive
application:
name: app
cloud:
gateway:
default-filters:
- TokenRelay
So we can in configuration file I am manually adding configs for one Realms but there can be multiple realms in that how to do it programmatically dynamic? .

How to support multiple Spring Security authentication providers in a single application and switch between them

In my Java 11, Spring Boot 2.7.3, Maven application, I am using Spring Security in order to authenticate my users. To do this, I included the following dependency in my pom.xml file:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.7.3</version>
</dependency>
And then coded the following security configuration class:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
#EnableWebSecurity
#Configuration
public class SecurityConfiguration {
#Bean
public AuthenticationProvider authenticationProvider() {
return new DocumentumAuthenticationProvider();
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/content/login")
.permitAll()
.and()
.logout()
.logoutUrl("/content/logout")
.logoutSuccessUrl("/content/logout")
.permitAll();
return http.build();
}
#Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().antMatchers("**/content/css/**" ,"/content/js/**" ,"/content/images/**" ,"/error/**");
}
#Bean
public BCryptPasswordEncoder encodePWD() {
return new BCryptPasswordEncoder();
}
}
Note that my custom authentication provider, DocumentumAuthenticationProvider, extends AbstractUserDetailsAuthenticationProvider. The user is prompted for credentials, which are then authenticated against a Documentum repository.
The above approach works fine.
Next, I wanted to implement SSO against Azure AD, so I removed the above security configuration class and custom authentication provider from the code set, and replaced the above pom.xml dependency with the following entries:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
<version>2.7.3</version>
</dependency>
<dependency>
<groupId>com.azure.spring</groupId>
<artifactId>spring-cloud-azure-starter-active-directory</artifactId>
</dependency>
I then added the following entries to my application.properties file, using values from a new application registration that I configured after logging into my Azure AD portal:
spring.cloud.azure.active-directory.enabled=true
spring.cloud.azure.active-directory.profile.tenant-id=xxxxxxxxxxxx
spring.cloud.azure.active-directory.credential.client-id=yyyyyyyyyyyy
spring.cloud.azure.active-directory.credential.client-secret=zzzzzzzzzz
Again, this all works fine.
The goal is now to have both these authentication approaches available together in my application and to make them switchable. Not surprisingly, if I simply merge the code snippets above all together, the application will not start because I have a WebSecurityConfigurerAdapter as well as SecurityFilterChain, which is not supportable:
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [javax.servlet.Filter]: Factory method 'springSecurityFilterChain' threw exception; nested exception is java.lang.IllegalStateException: Found WebSecurityConfigurerAdapter as well as SecurityFilterChain. Please select just one.
at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185)
at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653)
... 21 common frames omitted
Caused by: java.lang.IllegalStateException: Found WebSecurityConfigurerAdapter as well as SecurityFilterChain. Please select just one.
Many thanks for reading. I'd be very grateful for any suggestions / guidance as to how to proceed so that I can have both authentication providers sitting side by side in my code, with the ability to switch between them with a configuration setting.

Getting "An error occurred while attempting to decode the Jwt: Couldn't retrieve remote JWK set: " error when trying to decode the jwt token

this is my Security configuration file for resource server
#Configuration
#EnableWebSecurity
public class SecureSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionCreationPolicy(STATELESS)
.and()
.authorizeRequests()
.anyRequest()
.permitAll()
.antMatchers("api/**")
.authenticated()
.and()
.oauth2ResourceServer()
.jwt();
}
}
and those are dependencies for spring boot application (resource server app.)
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-core</artifactId>
<version>5.5.3</version>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
here is the jwk-set-url in the config yml file, to which my application is connecting at start up
spring:
security:
oauth2:
resourceserver:
jwt:
jwk-set-uri: http://localhost:8083/.well-known/openid-configuration/jwks
And via postman now when I'm getting the token from auth server via postman and trying to request to my server, I'm getting this kind of error
Bearer error="invalid_token", error_description="An error occurred while attempting to decode the Jwt: Couldn't retrieve remote JWK set: org.springframework.web.client.HttpClientErrorException$NotFound: 404 null", error_uri="https://tools.ietf.org/html/rfc6750#section-3.1"
who has faced such kind of issue?
Change the "Hostname Verification" to "none" in SSL properties tab.

Spring OIDC with OpenAM

I'd like to handle an authorization code grant flow with spring.
It "works" for the redirections, on arriving on my app, correctly redirected to openam, authN and grant authZ, submit, redirected to redirectUri.
But when I come back in the redirectUri, it seems Spring doesn't go inside my method (doesn't stop on my breakpoint), and the security context is probably empty, so not authenticated, so redirected back to auth.
Here's my config, am I missing something ?
REST api in Springboot, with angular frontend
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath />
</parent>
...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
#Configuration
#EnableWebSecurity
public class SecurityConfigOAuth2 extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
.and()
.authorizeRequests()
.antMatchers("/REST/v1/authorization-code/**").permitAll()
.anyRequest()
.authenticated()
.and()
.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.oauth2Login()
.and()
.oauth2Client()
;
}
}
server:
port: 8080
servlet:
context-path: /OAuth2Client
spring:
application:
name: OAuth2Client
security:
basic:
enabled: false
oauth2:
client:
registration:
openam:
clientId: MyClientId
scope:
- openid
- profile
- email
authorizationGrantType: authorization_code
---
spring:
profiles: dev
security:
oauth2:
client:
provider:
openam:
tokenUri: {openam-dev}/access_token
authorizationUri: {openam-dev}/authorize
userInfoUri: {openam-dev}/userinfo
tokenInfoUri: {openam-dev}/tokeninfo
jwkSetUri: {openam-dev}/jwk_uri
registration:
openam:
clientSecret: MyClientSecret-dev
redirectUri: "{baseUrl}/REST/v1/authorization-code/callback"
logging:
level:
org.springframework.security: DEBUG
org.springframework.web.client.RestTemplate: DEBUG

Categories

Resources