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?
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? .
I want to integrate Keycloak with spring boot application. The problem is that at the end, I got 403 forbidden error when calling the protected endpoints.
Following is my decoded JWT token, which is issued by Keycloak. I have a client, which is named clientApp1, and a realm role, which is named clientApp1User and mapped to the created user. Following is my decoded JWT token:
{
alg: "RS256",
typ: "JWT",
kid: "ZWDbgcSI8nD2Yq4LA6hxYcsTbnf6y6Zj8PKyUobE_qE"
}.
{
exp: 1666444432,
iat: 1666444132,
jti: "e6883855-ef20-4fac-95dd-8f13bd0ae552",
iss: "http://localhost:12500/auth/realms/sampleRealm",
aud: "account",
sub: "80e1e45f-49fb-4a5a-9a60-b0057d291c53",
typ: "Bearer",
azp: "clientApp1",
session_state: "c22af762-7be9-4150-94d5-8bd35065ac57",
acr: "1",
allowed-origins: [
"http://localhost:11501"
],
realm_access: {
roles: [
"clientApp1User",
"offline_access",
"uma_authorization",
"default-roles-samplerealm"
]
},
resource_access: {
account: {
roles: [
"manage-account",
"manage-account-links",
"view-profile"
]
}
},
scope: "email profile",
sid: "c22af762-7be9-4150-94d5-8bd35065ac57",
email_verified: false,
name: "user1FirstName User1LastName",
preferred_username: "user1",
given_name: "user1FirstName",
family_name: "User1LastName"
}.
[signature]
Moreover, here is my pom.xml file:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>ResourceServerSample</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>ResourceServerSample</name>
<description>ResourceServerSample</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</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-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Since, I want to use Security annotations to secure my end points I have set the security configuration as following:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(
prePostEnabled = true,
securedEnabled = true,
jsr250Enabled = true)
public class SecurityConfig {
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests()
.anyRequest().permitAll()
.and().oauth2ResourceServer().jwt();
http.csrf().disable();
return http.build();
}
Finally, in order to protect my endpoints I have used annotations like following:
#RestController
public class TestControllers {
// Public endpoint
#GetMapping("/welcome")
public ResponseEntity<String> welcome() {
return ResponseEntity.status(HttpStatus.OK).body("Welcome to the unprotected endpoint");
}
// #RolesAllowed("clientApp1User")
// #Secured("clientApp1User")
#PreAuthorize("hasAuthority('clientApp1User')")
#GetMapping("/clientApp1User")
public ResponseEntity<String> clientApp1User() {
return ResponseEntity.status(HttpStatus.OK).body("clientApp1User protected endpoint sends its regards");
}
#PreAuthorize("hasAuthority('SCOPE_email')")
#GetMapping("/testScope")
public ResponseEntity<String> testScope() {
return ResponseEntity.status(HttpStatus.OK).body("testScope protected endpoint sends its regards");
}
}
The problem that I face is that the endpoint, which is protected with #RolesAllowed("clientApp1User") or #Secured("clientApp1User") or #PreAuthorize("hasAuthority('clientApp1User')") returns 403 forbidden, when it's called with a valid access token.
On the other hand endpoints with annotations like #PreAuthorize("hasAuthority('SCOPE_email')") or #PreAuthorize("hasAuthority('SCOPE_profile')") return 200 Ok.
I believe spring boot can not accurately parse the JWT token and only excepts values in scope claim with the prefix <SCOPE_> and as an authority.
Can any one help me to fix the problem and use the RolesAllowed/Secured/PreAuthorize annotations to secure the endpoint with declared roles in realm_access and resource_access claims?
Roles are private claims: it is neither in OAuth2 nor OpenID specs and each authorization-server provider uses its own.
You have to provide your own Converter<Jwt, AbstractAuthenticatonToken> #Bean to map authorities from realm_access.roles (and maybe resource_access.clientApp1.roles if you enable client level roles in Keycloak) to everride Spring-boot default which turns Jwt into JwtAuthenticationToken (which can be fine) with authorities mapped from scope claim, adding SCOPE_ prefix (which is not what you want).
Complete samples here: https://github.com/ch4mpy/spring-addons/tree/master/samples/tutorials which cover various servlets scenarios with #controller tests.
For reactive apps or secured #Service & #Repository tests, see in https://github.com/ch4mpy/spring-addons/tree/master/samples (but it contain less explanations than tutorials, so start with tutorials and move up to samples for your exact use-case)
Sample
With the help of spring-boot 3 starters from the repo linked above, configuration for Keycloak can be as simple as (just adapt properties for any other OIDC provider):
<dependency>
<groupId>com.c4-soft.springaddons</groupId>
<!-- depending on your use-case, you might replace !-->
<!-- "webmvc" with "webflux" (for reactive apps) and !-->
<!-- "jwt" with "introspecting" (for access-token introsepection instead of JWT decoding) -->
<artifactId>spring-addons-webmvc-jwt-resource-server</artifactId>
<version>6.0.4</version>
</dependency>
#Configuration
#EnableMethodSecurity()
public class SecurityConfig {
}
#This illustrates configuration for accepting identities from both
# a Keycloak instance with authorities mapped from realm_access.roles and resource_access.account.roles
# an Auth0 domain with authorities mapped from roles and permissions
com.c4-soft.springaddons.security.issuers[0].location=https://localhost:8443/realms/master
com.c4-soft.springaddons.security.issuers[0].authorities.claims=realm_access.roles,resource_access.account.roles
com.c4-soft.springaddons.security.issuers[1].location=https://dev-ch4mpy.eu.auth0.com/
com.c4-soft.springaddons.security.issuers[1].authorities.claims=roles,permissions
com.c4-soft.springaddons.security.cors[0].path=/**
com.c4-soft.springaddons.security.cors[0].allowed-origins=https://localhost,https://localhost:8100,https://localhost:4200
com.c4-soft.springaddons.security.permit-all=/actuator/health/readiness,/actuator/health/liveness,/welcome
Bonus
As already spoiled, in tutorials and samples, you'll see how to mock OAuth2 identities (with authorities) during unit and integration tests.
#WebMvcTest(controllers = SampleController.class)
#AutoConfigureAddonsWebSecurity // this is required only if you use one of the starters above
#Import({ SecurityConfig.class })
class SampleControllerTest {
#Test
void whenAnonymousThenGetWelcomeIsOk() throws Exception {
mockMvc.perform(get("/sample")).andExpect(status().isOk());
}
#Test
void whenAnonymousThenGetClientApp1UserUnauthorized() throws Exception {
mockMvc.perform(get("/clientApp1User")).andExpect(status().isUnauthorized());
}
#Test
#WithMockJwtAuth("clientApp1User")
void whenClientApp1UserThenGetClientApp1UserOk() throws Exception {
mockMvc.perform(get("/clientApp1User")).andExpect(status().isOk());
}
}
You can have a look at this example on github.
The problem right now is that you need to add your roles to the Security Context of Spring Boot.
public static class CustomJwtConfigure implements Converter<Jwt, JwtAuthenticationToken> {
#Override
public JwtAuthenticationToken convert(Jwt jwt) {
var tokenAttributes = jwt.getClaims();
var jsonObject = (JSONObject) tokenAttributes.get(REALM);
var roles = (JSONArray) jsonObject.get(ROLES);
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
roles.forEach(role -> grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_" + role)));
return new JwtAuthenticationToken(jwt, grantedAuthorities);
}
}
Link to github account
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.
I get this problem thrown which says-the type org.springframework.security.web.DefaultSecurityFilterChain cannot be resolved. It is indirectly referenced from required .class files.
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.User.UserBuilder;
#Configuration
#EnableWebSecurity
public class DemoSecurityconfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
UserBuilder users=User.withDefaultPasswordEncoder();
auth.inMemoryAuthentication()
.withUser(users.username("John").password("john123").roles("EMPLOYEE"))
.withUser(users.username("Mac").password("mac123").roles("MANAGER"))
.withUser(users.username("Lily").password("lily123").roles("ACCOUNTANT"));
}
}```
DefaultSecurityFilterChain is located in spring-security-web-x.y.z.RELEASE.jar. This error is most probably because this class cannot be found from the class-path.
So go to class path to check if this jar is really included .If not and you are using spring-boot , you can use spring-boot-starter-security starter which will automatically include it.
If you are not using spring-boot, make sure the following dependency is included.
For Maven, pom.xml should include
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>
And for Gradle , build.gradle should include :
dependencies {
compile "org.springframework.security:spring-security-web"
}