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
Related
Environment: Spring Boot 2.3.1, Java 11
I have tried out a few things already (also comparing with the sample-app by spring), but so far I have been unsuccessful in creating a WebClient that requires a ReactiveClientRegistrationRepository.
I get the following exception when starting up my spring-boot application:
required a bean of type 'org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository'
The way I understand spring-boot-autoconfigure it should use the ReactiveOAuth2ClientAutoConfiguration, as in the yml-file the required properties are given.
Following some code-snippets, I can provide more if something is missing to get the whole context
Main-Class
#Slf4j
#SpringBootApplication
#EnableConfigurationProperties(MyAppConfigurationProperties.class)
public class MyApp{
public static void main(final String[] args) {
SpringApplication.run(MyApp.class, args);
}
}
Configuration:
#Configuration
//#Import(ReactiveOAuth2ClientAutoConfiguration.class) // in the test it works with this, but should not be required: spring-boot-autoconfigure
public class MyRestClientConfig {
#Bean
WebClient myWebClient(WebClient.Builder builder, ReactiveClientRegistrationRepository clientRegistrations) {
//content not relevant to this problem
}
}
Configuration for security
#EnableGlobalMethodSecurity(securedEnabled = true)
#EnableWebSecurity
#EnableWebFluxSecurity
public class SecurityConfig {
}
application.yml
spring:
security:
oauth2:
client:
registration:
providerid:
authorization-grant-type: "client_credentials"
client-id: "myClientId"
client-secret: "mySecret"
user-info-authentication-method: header
provider:
providerid:
token-uri: "working token-uri"
I tried with different dependencies, so some may not be required. Which dependencies are actually required?
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework.security.oauth.boot</groupId>-->
<!-- <artifactId>spring-security-oauth2-autoconfigure</artifactId>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.security.oauth</groupId>-->
<!-- <artifactId>spring-security-oauth2</artifactId>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-oauth2-client</artifactId>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.security</groupId>-->
<!-- <artifactId>spring-security-oauth2-core</artifactId>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.security</groupId>-->
<!-- <artifactId>spring-security-oauth2-jose</artifactId>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-webflux</artifactId>-->
<!-- </dependency>-->
In an integration-test the Spring-Boot-Application starts up
#EnableConfigurationProperties
#Import(ReactiveOAuth2ClientAutoConfiguration.class) // in the test it works with this, but should not be required: spring-boot-autoconfigure, can be omitted if added in MyRestClientConfig
#ComponentScan(basePackages = "com.mycompany")
public class ManualClientTester {
}
EDIT 1:
Debug of Positive Matches for Autoconfiguration
In test where it works:
============================
CONDITIONS EVALUATION REPORT
============================
Positive matches:
-----------------
ReactiveOAuth2ClientAutoConfiguration matched:
- #ConditionalOnClass found required classes 'reactor.core.publisher.Flux', 'org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity', 'org.springframework.security.oauth2.client.registration.ClientRegistration' (OnClassCondition)
- NoneNestedConditions 0 matched 1 did not; NestedCondition on ReactiveOAuth2ClientAutoConfiguration.NonServletApplicationCondition.ServletApplicationCondition not a servlet web application (ReactiveOAuth2ClientAutoConfiguration.NonServletApplicationCondition)
ReactiveOAuth2ClientConfigurations.ReactiveClientRegistrationRepositoryConfiguration matched:
- OAuth2 Clients Configured Condition found registered clients myClientId (ClientsConfiguredCondition)
- #ConditionalOnMissingBean (types: org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; SearchStrategy: all) did not find any beans (OnBeanCondition)
ReactiveOAuth2ClientConfigurations.ReactiveOAuth2ClientConfiguration matched:
- #ConditionalOnBean (types: org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository; SearchStrategy: all) found bean 'clientRegistrationRepository' (OnBeanCondition)
ReactiveOAuth2ClientConfigurations.ReactiveOAuth2ClientConfiguration#authorizedClientRepository matched:
- #ConditionalOnMissingBean (types: org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository; SearchStrategy: all) did not find any beans (OnBeanCondition)
ReactiveOAuth2ClientConfigurations.ReactiveOAuth2ClientConfiguration#authorizedClientService matched:
- #ConditionalOnMissingBean (types: org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientService; SearchStrategy: all) did not find any beans (OnBeanCondition)
When starting my spring boot application:
============================
CONDITIONS EVALUATION REPORT
============================
Negative matches:
-----------------
ReactiveOAuth2ClientAutoConfiguration:
Did not match:
- NoneNestedConditions 1 matched 0 did not; NestedCondition on ReactiveOAuth2ClientAutoConfiguration.NonServletApplicationCondition.ServletApplicationCondition found 'session' scope (ReactiveOAuth2ClientAutoConfiguration.NonServletApplicationCondition)
Matched:
- #ConditionalOnClass found required classes 'reactor.core.publisher.Flux', 'org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity', 'org.springframework.security.oauth2.client.registration.ClientRegistration' (OnClassCondition)
EDIT 2:
After changing as suggested, I have now the following:
#EnableReactiveMethodSecurity
#EnableWebFluxSecurity
public class SecurityConfig {
}
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
Furthermore all used versions of spring-projects:
<spring-amqp.version>2.2.7.RELEASE</spring-amqp.version>
<spring-batch.version>4.2.4.RELEASE</spring-batch.version>
<spring-boot.version>2.3.1.RELEASE</spring-boot.version>
<spring-data-releasetrain.version>Neumann-SR1</spring-data-releasetrain.version>
<spring-framework.version>5.2.7.RELEASE</spring-framework.version>
<spring-hateoas.version>1.1.0.RELEASE</spring-hateoas.version>
<spring-integration.version>5.3.1.RELEASE</spring-integration.version>
<spring-kafka.version>2.5.2.RELEASE</spring-kafka.version>
<spring-ldap.version>2.3.3.RELEASE</spring-ldap.version>
<spring-restdocs.version>2.0.4.RELEASE</spring-restdocs.version>
<spring-retry.version>1.2.5.RELEASE</spring-retry.version>
<spring-security.version>5.3.3.RELEASE</spring-security.version>
<spring-session-bom.version>Dragonfruit-RELEASE</spring-session-bom.version>
<spring-ws.version>3.0.9.RELEASE</spring-ws.version>
<spring.boot.version>2.3.1.RELEASE</spring.boot.version>
<spring.cloud.version>Hoxton.SR5</spring.cloud.version>
The problem still exists.
I ran into the same problem and noticed that the application created a ClientRegistrationRepository instead of a ReactiveClientRegistrationRepository. Somewhere in Spring boot the #EnableWebSecurity was added (we need the #EnableWebFluxSecurity in this case).
To fix the problem I've added the following property:
spring.main.web-application-type: reactive
If you're also using #SpringBootTest to test your application you also need to add the property there.
#SpringBootTest(properties = ["spring.main.web-application-type=reactive]")
or by setting the web environment to NONE
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
The reason why this happens is also explained in this answer: Error when using #EnableWebFluxSecurity in springboot
I'm still not happy about my solution, but I ended up doing the following:
#Bean
public ReactiveClientRegistrationRepository reactiveClientRegistrationRepository(OAuth2ClientProperties oAuth2ClientProperties) {
List<ClientRegistration> clientRegistrations = new ArrayList<>();
// because autoconfigure does not work for an unknown reason, here the ClientRegistrations are manually configured based on the application.yml
oAuth2ClientProperties.getRegistration()
.forEach((k, v) -> {
String tokenUri = oAuth2ClientProperties.getProvider().get(k).getTokenUri();
ClientRegistration clientRegistration = ClientRegistration
.withRegistrationId(k)
.tokenUri(tokenUri)
.clientId(v.getClientId())
.clientSecret(v.getClientSecret())
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.build();
clientRegistrations.add(clientRegistration);
});
return new InMemoryReactiveClientRegistrationRepository(clientRegistrations);
}
I use the spring-properties for OAuth and then create the ReactiveClientRegistrationRepository based on those properties.
Works this way:
#Bean
public WebClient webClient(ClientRegistrationRepository clientRegistrationRepository) {
InMemoryReactiveClientRegistrationRepository registrationRepository = new InMemoryReactiveClientRegistrationRepository(clientRegistrationRepository.findByRegistrationId("REG_ID"));
InMemoryReactiveOAuth2AuthorizedClientService clientService = new InMemoryReactiveOAuth2AuthorizedClientService(registrationRepository);
AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager clientManager = new AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager(registrationRepository, clientService);
return WebClient.builder()
.baseUrl(BASEURL)
.filter(new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientManager))
.build();
}
Properties:
spring.security.oauth2.client.registration.REG_ID.client-id=CLIENT_ID
spring.security.oauth2.client.registration.REG_ID.client-name=CLIENT_NAME
spring.security.oauth2.client.registration.REG_ID.client-secret=SECRET
spring.security.oauth2.client.registration.REG_ID.authorization-grant-type=client_credentials
spring.security.oauth2.client.registration.REG_ID.scope=SCOPE
spring.security.oauth2.client.provider.REG_ID.issuer-uri=PATH_TO_.well-known/openid-configuration_SITE
EDIT:
Add the following before the return statement:
ServerOAuth2AuthorizedClientExchangeFilterFunction clientExchangeFilterFunction = new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientManager);
clientExchangeFilterFunction.setDefaultClientRegistrationId("REG_ID");
And replace filter in the return statement with:
.filter(clientExchangeFilterFunction)
I solved this by writing this code
#Bean("oauthWebClient")
WebClient webClient(ClientRegistrationRepository clientRegistrations) {
InMemoryReactiveClientRegistrationRepository registrationRepository = new InMemoryReactiveClientRegistrationRepository(
clientRegistrations.findByRegistrationId("reg-id"));
ServerOAuth2AuthorizedClientExchangeFilterFunction oauth =
new ServerOAuth2AuthorizedClientExchangeFilterFunction(
registrationRepository,
new UnAuthenticatedServerOAuth2AuthorizedClientRepository());
oauth.setDefaultClientRegistrationId("reg-id");
return WebClient.builder()
.filter(oauth)
.build();
}
Update:
Looks like the "user-info-authentication-method" (userInfoAuthenticationMethod) is part of the Provider and not the Registration. And please remove the double quotes too.
spring:
security:
oauth2:
client:
registration:
providerid:
authorization-grant-type: client_credentials
client-id: myClientId
client-secret: mySecret
provider:
providerid:
token-uri: <working token-uri>
user-info-authentication-method: header
Also a suggestion - to avoid possible conflicting/ incompatible dependencies, please use dependency management like this and try to have all spring boot starters. eg the spring security library comes as part of both spring-boot-starter-oauth2-client and spring-boot-starter-oauth2-resource-server:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.1.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Just these 2 dependencies should do the work: (these are picked from Gradle file, please change them to POM equivalent)
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
For tests, probably you may need this:
testImplementation 'org.springframework.security:spring-security-test:5.3.3.RELEASE'
You should not mix the two together:
#EnableWebSecurity
#EnableWebFluxSecurity
If your application is reactive, then just use #EnableWebFluxSecurity.
And coming to #EnableGlobalMethodSecurity(securedEnabled = true) , this is described here, and is recommended to remove that and use this instead:
#EnableReactiveMethodSecurity
it's difficult to provide relevant answer without a stacktrace.
Seem that Spring boot cant create ReactiveClientRegistrationRepository from your properties file.
Try to add a provider property on your client.
oauth2:
client:
registration:
registrationId:
provider: providerId
client-id: clientId
client-secret: secret
authorization-grant-type: client_credentials
ReactiveClientRepositoryRegistration Bean needs to be defined explicitly. You can refer to the spring documentation
https://docs.spring.io/spring-security/reference/reactive/oauth2/login/core.html#webflux-oauth2-login-register-reactiveclientregistrationrepository-bean
I have a REST application using Spring Boot 2.2.4.RELEASE.
My REST controller is annotated like
#RestController
#RequestMapping("/")
My REST controller has #GetMapping, #PostMapping, etc., it works as expected.
Now I want to integrate Swagger in latest version.
https://github.com/swagger-api/swagger-core
https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Getting-started
The Maven dependencies shown there I added to my pom.xml:
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-core</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-models</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-integration</artifactId>
<version>2.1.1</version>
</dependency>
According to the 'Getting Started', just by adding the dependencies I should see the generated OpenAPI at http://localhost:8080/openapi.json but nothing shows up there.
Is Swagger (swagger-core,... in version 2.1.1) usable with Spring Boot web applications?
There is a project SpringFox, but it is not up to date. Also springdoc-openapi is available. But using Swagger directly would be my first thought.
You'll need a SwaggerConfig file in your code.
Configuration needed in Swagger for Java goes like this:
(Note that this is just the basic File and you can Configure it however you want.)
#EnableSwagger2
#Configuration
public class SwaggerConfig {
public static String bucketName;
#Value("${swagger.config.basePackage}")
public void setName(String name) {
bucketName = name;
}
#Bean
public Docket classifiedApi()
{
ArrayList<ApiKey> apiKeys=new ArrayList<>();
apiKeys.add(apiKey());
return new Docket(DocumentationType.SWAGGER_2).securitySchemes(apiKeys)
.select()
.apis(RequestHandlerSelectors.basePackage(bucketName))
.build()
.apiInfo(metaData());
}
private ApiKey apiKey() {
return new ApiKey("Api Key", Constants.JWTToken.API_KEY, "header");
}
private ApiInfo metaData() {
return new ApiInfoBuilder()
.title("APPNAME REST API")
.description("Spring Boot REST API for APPNAME")
.contact(new Contact("YOURNAME ", "Coming Soon ...", "CONTACT ADDRESS"))
.build();
}
}
Can someone answer this silly question - How to configure Thymeleaf in Spring boot release 2.1.4?
I have declared the right dependencies:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
Also the config:
#SpringBootApplication
#ComponentScan("org.mystuff.myproj")
#EnableAutoConfiguration
public class Init extends SpringBootServletInitializer{
And the controller looks regular:
#Controller
#RequestMapping("/a")
public class IndexController {
private static final Logger logger = LoggerFactory.getLogger(IndexController.class);
#PostConstruct
private void test() {
logger.info("********************************************************************");
}
#RequestMapping("/")
private String index() {
return "index2";
}
I do see that the #Controller bean gets initiated (the "*****..."), but when I try to locate in the logs the "mapped" or atleast something related, the only thing I find is:
2019-04-23 15:55:15 WARN [localhost-startStop-1] JpaBaseConfiguration$JpaWebConfiguration$JpaWebMvcConfiguration.openEntityManagerInViewInterceptor: spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2019-04-23 15:55:16 INFO [localhost-startStop-1] WelcomePageHandlerMapping.<init>: Adding welcome page: ServletContext resource [/index.html]
And I'm failing to find an answer to the "What has changed" question.
After a while I realized that Spring Boot 2.1.4 requires TomCat 9, while I was using 8.5.
After this I started to get progress, but still the Thymeleaf isn't working, and if /templates has a index.html, the default Resolver is used, which ignores Thymeleaf's "fragments" and stuff (loads like a regular html page).
I'm having problems to set up a global OkHttp interceptor for my #FeignClient beans. I'm not experiencing any error, but the interceptor is being ignored.
My understanding is that Spring Cloud's auto configuration should pick the OkHttpClient.Builder bean that I'm declaring and use it to create the underlying OkHttpClient instances, but I might be wrong about this.
Here are the relevant parts of my Spring app:
#SpringBootApplication
#EnableFeignClients(defaultConfiguration = FeignConfig.class)
#EnableCircuitBreaker
public class MyApp {
public static void main(String[] args) {
SpringApplication.run(MyApp.class);
}
}
#Configuration
public class FeignConfig {
#Bean
public MyInterceptor myInterceptor() {
return new MyInterceptor();
}
#Bean
public OkHttpClient.Builder okHttpClientBuilder(MyInterceptor interceptor) {
return new OkHttpClient.Builder().addInterceptor(interceptor);
}
}
public class MyInterceptor implements okhttp3.Interceptor {
#Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
System.out.println("Hey there, this is my request: " + request);
Response response = chain.proceed(request);
System.out.println("Hey there, this is my response: " + response);
return response;
}
}
The intercept method above is never called. I need MyInterceptor to be a Spring bean, because I need to inject other dependencies to it.
#FeignClient(name = "myClient", fallback = MyClientFallback.class)
public interface MyClient {
// method declarations
}
#Component
public class MyClientFallback implements MyClient {
// method fallback implementations
}
Here's the relevant part of my application.properties file:
feign.hystrix.enabled = true
feign.okhttp.enabled = true
ribbon.eureka.enabled = false
ribbon.eager-load.enabled = true
ribbon.eager-load.clients = myClient
myClient.ribbon.listOfServers = <IP_LIST>
myClient.ribbon.ServerListRefreshInterval = 10000
As you see from the properties declared above, I'm not using Eureka and I'm using Ribbon to load balance my rest client. I'm also using Hystrix to enable fallback responses and I have set the feign.okhttp.enabled property to true.
Below is the info about dependecies config and versions...
Spring Boot version is 2.0.3.RELEASE and Spring Cloud version is Finchley.SR1, while OkHttp version is 3.11.0.
In my pom.xml file, I have this spring-cloud-dependencies config:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Finchley.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
...
</dependencies>
</dependencyManagement>
I have also included the following Spring Boot and Spring Cloud dependencies, along with the OkHttp dependency:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.11.0</version>
</dependency>
...
</dependencies>
You should provide an OkHttpClient bean as stated in the doc:
The OkHttpClient and ApacheHttpClient feign clients can be used by setting feign.okhttp.enabled or feign.httpclient.enabled to true, respectively, and having them on the classpath. You can customize the HTTP client used by providing a bean of either ClosableHttpClient when using Apache or OkHttpClient whe using OK HTTP.
https://github.com/OpenFeign/feign/blob/master/okhttp/src/main/java/feign/okhttp/OkHttpClient.java
The solution is to let Spring auto configuration do its job.
In order for that to happen, the following dependency must be removed from the pom.xml file:
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.11.0</version>
</dependency>
And the following one must be manually included:
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
Once this is done, everything works as expected with the provided configuration.
Solution is to use OkHttpClient. Add pom.xml dependencies:
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
</dependency>
and configure a bean:
#Configuration
public class FeignConfiguration {
#Bean
public OkHttpClient client() {
return new OkHttpClient();
}
}
Explanation: For 401, 407 and some other HTTP-status responses, bodies are replaced with null by HTTP clients used in Open Feign by default.
From OpenFeign: Currently in the feign.Default client there is a streaming mode enabled. You can see in the sun.net.www.protocol.http.HttpURLConnection following lines of code :
if (respCode == HTTP_UNAUTHORIZED) {
if (streaming()) {
disconnectInternal();
throw new HttpRetryException (RETRY_MSG2, HTTP_UNAUTHORIZED);
}
}
So if the streaming is enabled and you have 401 HTTP response code you will get empty errorStream, because there is no initialization. The feign client will try to get the errorStream as a body because there is a check
if (status >= 400) {
stream = connection.getErrorStream();
} else { stream = connection.getInputStream(); }
I'm trying to integrate my Spring Boot version 2.0.1.RELEASE with Swagger.
From this blog post it seemed like it will be easy by just adding two Maven dependencies and everything should work.
So I added the following dependencies to the pom:
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.8.0</version>
</dependency>
And created the SwaggerConfig bean:
#Configuration
#EnableSwagger2
public class SwaggerConfig {
#Bean
public Docket api() {
Docket docket = new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build();
return docket;
}
}
And in the properties file I ended up with these 3 entries during the attempts to make it work:
spring.application.name=cat-service
management.server.servlet.context-path=/cat-service
server.servlet.contextPath=/cat-service
But at the end, when accessing
http://localhost:8080/cat-service/api/v2/api-docs
or the UI page at
http://localhost:8080/cat-service/swagger-ui.html
I get a page not found error.
I found this issues in the swagger github page and this question in stackoverflow but I was not able to change my 404 error.
I was able to make it work with Spring boot version 2.0.4.RELEASE and this blog post:
I added these dependencies:
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
And this configuration file:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
#Configuration
#EnableSwagger2
public class SpringFoxConfig {
#Bean
public Docket apiDocket() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build();
}
}
And it worked.
The Swagger UI can be reached at /swagger-ui.html#
Please check the reference: https://springfox.github.io/springfox/docs/current/
"2.1.3. Migrating from existing 2.x version"
You can remove springfox-swagger2 and springfox-swagger-ui from your pom.xml and add springfox-boot-starter instead (for example version 3.0.0). Also you can remove the #EnableSwagger2 annotations
And: "swagger-ui location has moved from https://host/context-path/swagger-ui.html to https://host/context-path/swagger-ui/index.html OR https://host/context-path/swagger-ui/ for short. This makes it work much better with pulling it as a web jar and turning it off using configuration properties if not needed."
First add SwaggerConfig.java file at the same package of your springboot file like the following example.
#Configuration
#EnableSwagger2
#EnableWebMvc
public class SwaggerConfig extends WebMvcConfigurerAdapter {
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build();
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
try this
http://localhost:8080/spring-security-rest/api/swagger-ui.html
or
http://localhost:8080/spring-security-rest/swagger-ui.html
If that does not work, try to change the path in application.properties
Add this to application.properties:
server.servlet-path=/loop-service
and try the following urls:
http://localhost:8080/loop-service/swagger-ui.html (UI Docs)
http://localhost:8080/loop-service/v2/api-docs (JSON Docs)
Result :
OpenAPI
Just use springdoc-openapi-ui instead.
The Maven dependency:
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.6.11</version>
</dependency>
Or Gradle:
implementation 'org.springdoc:springdoc-openapi-ui:1.6.11'
Links
UI: http://localhost:8080/swagger-ui.html
Json: http://localhost:8080/v3/api-docs
Yaml: http://localhost:8080/v3/api-docs.yaml
That's really all there is to it... No annotations / configuration needed. Cheers!
For Spring Security
If you are using spring security, make sure you can reach these paths for it to work:
"/swagger-ui.html" <-- for UI
"/swagger-ui/**" <-- for UI redirects
"/v3/api-docs/**" <-- for json docs and openapi configuration
"/v3/api-docs.yaml" <-- for yaml docs
For more information:
https://www.baeldung.com/spring-rest-openapi-documentation
I also had the same issue (404 Not Found with springfox 3.0.0). By setting the logging level to "DEBUG", I was able to see endpoints for /v3/api-docs and they worked, but there was nothing about "swagger-ui".
I finally found https://github.com/springfox/springfox/issues/3285, which indicates that:
the new url in 3.0.0 is /swagger-ui/index.html or /swagger-ui/ rather than /swagger-ui.html"
Could they not have added a debug log to indicate where the swagger UI is available?
Springfox swagger UI opens at path /swagger-ui/index.html.
Make sure you are allowing correct ResourceLocations and path excluded from interceptors
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/swagger-ui/**").addResourceLocations("classpath:/META-INF/resources/swagger-ui.html");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
Note: i'm using Springfox swagger version 3.0.0 and Springboot version 2.5.3
This worked for me, I used the WebMvcConfigurer instead of the WebMvcConfigurerAdapter because that class is already deprecated.
#Configuration
#EnableSwagger2
public class SwaggerConfig implements WebMvcConfigurer {
#Bean
public Docket productApi() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("com.illary.controller"))
.paths(PathSelectors.any())
.build()
.apiInfo(metaData());
}
private ApiInfo metaData() {
return new ApiInfoBuilder()
.title("Spring Boot Swagger App")
.description("\"Spring Boot Swagger Server App\"")
.version("1.0.0")
.license("Apache License Version 2.0")
.licenseUrl("https://www.apache.org/licenses/LICENSE-2.0\"")
.build();
}
public ApiInfo apiInfo() {
final ApiInfoBuilder builder = new ApiInfoBuilder();
builder.title("Swagger Test App").version("1.0").license("(C) Copyright Test")
.description("The API provides a platform to query build test swagger api");
return builder.build();
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
For Spring Boot, just use the dependency below, it's all it needs to work on the URL /swagger-ui/ (the trailing slash is mandatory).
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
Before trying that I was trying to use the classic dependencies of swagger2 and swagger-ui, and none of the suggested URLs were working.
Another possibility is the location of your Swagger config file; you need to place it at the same package or a subpackage of the spring boot file's.
Like the above picture:
Don't forget to change server.contextPath to server.servlet.contextPath if you upgrade Spring Boot to 2+.
Solution: You just need to remove #EnableWebMvc from the configuration classes.
Description: #EnableWebMvc turn on the class org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport. In spring-boot, there is an autoconfiguration class org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration, which has the annotation #ConditionalOnMissingBean(WebMvcConfigurationSupport.class).
What we get in the end: By adding #EnableWebMvc to the project, we ourselves become responsible for everything, since we turn off spring-boot auto-configuration.
After adding below line in application.properties, it started working
spring.web.resources.static-locations: classpath:/webapp/
But I'm not sure why do we have to add this.
Adding the code, which I think could be relevant. Dependencies are as below:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
...
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<!-- Swagger Dependencies -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
And main class as
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
#SpringBootApplication
#EnableSwagger2
public class ProxyApplication {
public static void main(String[] args) {
SpringApplication.run(ProxyApplication.class, args);
}
#Bean
public Docket api() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.basePackage("com.ghsatpute.proxy"))
.paths(PathSelectors.any())
.build();
}
}
Adding this if it can help somebody. This URL worked for me
http://localhost:8003/v2/api-docs
and for swagger-ui this url worked for me out of the box:
http://localhost:8003/swagger-ui.html
Check if you are using right configured url for swagger specs. The urls like http://localhost:8080/spring-security-rest/api/swagger-ui/
didn't work , and I had been getting 404.
Below are the steps to enable swagger in spring boot application:
Add springfox-swagger2 and springfox-swagger-ui dependencies.
To enable the Swagger2 in your project you should use #EnableSwagger2
Define a docket bean ad below
#Bean
public Docket docket(){
return new Docket(DocumentationType.SWAGGER_2).groupName("group-name").apiInfo(apiInfo()).select().paths(predicate()).build();
}
private Predicate<String> predicate() {
return or(regex("/api/v1.*"), regex("/api/v2.*"));
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder().title("The title of the your choice")
.contact("your#email.com").license("Licence name ").version("1.0").build();
}
Swagger UI url pattern:
localhost:<server.port>/<server.servlet.context-path>/swagger-ui.html
For anyone still looking for this, using OpenAPI 3.0, not Springfox, I got it running using WebMvcConfigurationSupport without the need of #EnableWebMvc.
The only Dependency needed:
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.6.8</version>
</dependency>
I initially just got the JSON and a 404 on the UI.
What ultimately worked was adding the right resource handlers to my WebConfig and configuring my SecurityConfig to allow unrestricted access for everyone.
The resource handlers in my WebConfig:
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/swagger-ui/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/swagger-ui/4.10.3/swagger-initializer.js")
.addResourceLocations("classpath:/META-INF/resources/webjars/swagger-ui/4.10.3/index.html")
.addResourceLocations("classpath:/META-INF/resources/webjars/swagger-ui/4.10.3/swagger-ui.css")
.addResourceLocations("classpath:/META-INF/resources/webjars/swagger-ui/4.10.3/index.css")
.addResourceLocations("classpath:/META-INF/resources/webjars/swagger-ui/4.10.3/swagger-ui-bundle.js")
.addResourceLocations("classpath:/META-INF/resources/webjars/swagger-ui/4.10.3/swagger-ui-standalone-preset.js");
}
Important was to add each one specifically (with version and everything, ** didn't work). Otherwise it wouldn't run in the cloud.
I also needed to add the paths "/v3/api-docs/**", "/swagger-ui/**" my SecurityConfig to allow access to these resources. So in my case I added those here:
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.addFilterBefore(...
.authorizeRequests().antMatchers("/v3/api-docs/**", "/swagger-ui/**", "...")...
}
and:
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(HttpMethod.GET, "/v3/api-docs/**", "/swagger-ui/**", "...");
}
It became working for me after removing #EnableWebMvc
Faced same issue just resolved with change in dependency
Refer https://www.vojtechruzicka.com/documenting-spring-boot-rest-api-swagger-springfox/
compile "io.springfox:springfox-swagger2:2.9.2"
Previously was using - Not working
compile group: 'io.springfox', name: 'springfox-swagger2', version: '3.0.0'
I was stuck for a day with this issue. My issue was incorrect port configuration. Check your application.yml/application.properties file for port configuration. There I had mistakenly added both
server.port , management.server.port with different values 🤣 .