How do I configure a custom URL for the springdoc swagger-ui HTML page? - spring

After adding the springdoc-openapi-ui dependency to my Spring project (not Spring Boot) OpenAPI V3 documentation is generated and can be viewed using the default swagger-ui page: localhost:8080/swagger-ui.html. Because the springdoc documentation replaces previous Swagger documentation I want to make it available at the same URL, localhost:8080/docs/index.html. Based on the springdoc documentation I get the impression that can be done by using the springdoc.swagger-ui.path option in the application.properties:
springdoc.swagger-ui.path=/docs/index.html
However, where I would expect to be able to navigate to the API documentation by going to localhost:8080/docs/index.html I get a 404 instead, localhost:8080/swagger-ui.html still works but now redirects to http://localhost:8080/docs/swagger-ui/index.html?configUrl=/restapi/v3/api-docs/swagger-config.
How can I configure my project too make the swagger-ui page available through a custom URL, i.e. localhost:8080/docs/index.html instead of the default localhost:8080/swagger-ui.html?
Edit
After trying some more to get it working and looking through the available information online, such as the springdoc FAQ (mentioned in this answer by H3AR7B3A7) I couldn't get it working. I've decided to go with a different solution which should have the same effect. The springdoc.swagger-ui.path option allows specifying a custom URL but, as I understand it, going to the custom URL redirects a user to the standard localhost:8080/swagger-ui.html page. So the redirect is now configured manually:
#RequestMapping("/docs/index.html")
public void apiDocumentation(HttpServletResponse response) throws IOException {
response.sendRedirect("/swagger-ui.html");
}

I had a similar task for Spring Boot 2.5.6 and springdoc-openapi-webflux-ui 1.5.12. I've found several possible solutions for myself. Maybe it will be helpful for somebody else.
Set springdoc.swagger-ui.path directly
The straightforward way is to set property springdoc.swagger-ui.path=/custom/path. It will work perfectly if you can hardcode swagger path in your application.
Override springdoc.swagger-ui.path property
You can change default swagger-ui path programmatically using ApplicationListener<ApplicationPreparedEvent>. The idea is simple - override springdoc.swagger-ui.path=/custom/path before your Spring Boot application starts.
#Component
public class SwaggerConfiguration implements ApplicationListener<ApplicationPreparedEvent> {
#Override
public void onApplicationEvent(final ApplicationPreparedEvent event) {
ConfigurableEnvironment environment = event.getApplicationContext().getEnvironment();
Properties props = new Properties();
props.put("springdoc.swagger-ui.path", swaggerPath());
environment.getPropertySources()
.addFirst(new PropertiesPropertySource("programmatically", props));
}
private String swaggerPath() {
return "/swagger/path"; //todo: implement your logic here.
}
}
In this case, you must register the listener before your application start:
#SpringBootApplication
#OpenAPIDefinition(info = #Info(title = "APIs", version = "0.0.1", description = "APIs v0.0.1"))
public class App {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(App.class);
application.addListeners(new SwaggerConfiguration());
application.run(args);
}
}
Redirect using controller
You can also register your own controller and make a simple redirect (the same as what you suggest, but in my case, I need to use the WebFlux approach):
#RestController
public class SwaggerEndpoint {
#GetMapping("/custom/path")
public Mono<Void> api(ServerHttpResponse response) {
response.setStatusCode(HttpStatus.PERMANENT_REDIRECT);
response.getHeaders().setLocation(URI.create("/swagger-ui.html"));
return response.setComplete();
}
}
The problem with such an approach - your server will still respond if you call it by address "/swagger-ui.html".

Apparantly the library integrates natively only with spring-boot applications like you mentioned in your comment.
If you want to use spring, it's possible but the integration details aren't documented, because it really depends on the version/module and the nature of you spring application.
You can check the FAQ to see if it answers your questions.
There are some more answers here on SO.

I solved by a clear code.
The problem is in webflux that security need permitAll for some uri resources so I solved in this mode in WebFlux Security I image the same in Springboot no WebFlux:
.authorizeExchange().pathMatchers(
// START To show swagger 3:
"/swagger-ui.html",
"/webjars/swagger-ui/**",
"/v3/api-docs/swagger-config",
"/v3/api-docs", // This is the one URI resource used by openApi3 too.
// END To show swagger 3:
"/other-uri-permitAll",
...
"/other-uri-permitAll_N",
.permitAll()
.and()
.authorizeExchange().pathMatchers(
"/other-uri-authenticated-only_1",
"...",
"/other-uri-authenticated-only_1")
.authenticated()
.and()
.authenticationManager(myAuthenticationManager)
.securityContextRepository(mySecurityContextRepository)
.authorizeExchange().anyExchange().authenticated()
.and()
.build();

Related

Restrict API access by domain name [duplicate]

I have one war file for my application and I will be using 2 domains to access it. For example I want to access admin.jsp using admin.mydomain.com/adminpage and other jsp pages I want to access with local.mydomain.com.
Also, admin.jsp should be only accessible via admin.mydomain.com and not via local.mydomain.com. How to do this in spring-security / spring-mvc? Is there a support in spring framework for this?
Any help on this would be helpful. Thanks.
You can implement RequestMatcher, and maybe like
HostOnlyRequestMatch(String relativePath, String hostname)
and then override the boolean matches(HttpServletRequest request) method, and if the relativePath and hostname are same with request, return true.
Add the requestMatcher to http like this:
http
.authorizeRequests()
.requestMatcher(new HostOnlyRequestMatch("/admin", "admin.mydomain.com")).permitAll()
.antMatchers("/admin").denyAll();
One way would be to configure proxy (e.g. Nginx) to route your requests to your application server (e.g Tomcat) properly. Read here for more details https://www.nginx.com/resources/admin-guide/reverse-proxy/
You can get the requested url from request object in you mvc controller and if it is not form correct domain then you can throw or show proper error based on your project. Following is the code snippet
#Controller
#RequestMapping("/adminpage")
public class AdminPageController{
#RequestMapping(method = RequestMethod.GET)
public String getAdminPage(HttpServletRequest request) {
String url = request.getRequestURL().toString();
if(!url.contains("admin.mydomain.com")) {
throw RuntimeException("Not accessible through this domain.");
// You can implement your own logic of showing error here
}
}
}

Springboot, CXF 3.2.7, Swagger2Feature: Authorization header does not appear in the request headers

I'm trying to integrate Swagger 2 into my API, which is implemented with CXF newest version: 3.2.7.
I tried lots of tutorials, the CXF official documentation, others too (e.g. Spring Boot, Apache CXF, Swagger under JAX-RS).
The swagger official website does not help for me. The swagger OpenAPI 2.0 authentication doc is not working, neighter the OpenAPI 3.0.
It is not working with component schemes of Open API 3.0.0, so i stayed with the apiKeyDefinition.
The one, which is working now can be found in this thread, in the answer of #Naoj:
CXF Swagger2Feature adding securityDefinitions
With this solution the swagger ui appeared and also the Autorize button is showing.
I fill the authentication form, and after that, I try to send requests with the swagger-ui. The problem is, that the Authorization header does not appear in the request, so I got 401 response.
In the pom:
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-rs-service-description-swagger</artifactId>
<version>3.2.7</version>
</dependency>
<dependency>
<groupId>org.webjars</groupId>
<artifactId>swagger-ui</artifactId>
<version>3.20.1</version>
</dependency>
My swagger configuration looks like this:
#Configuration
public class SwaggerConfig {
#Bean
#ConfigurationProperties("swagger")
public Swagger2Feature swagger() {
return new ExtendedSwagger2Feature();
}
#Bean
#DependsOn("jaxRsServer")
public ServletContextInitializer initializer() {
return servletContext -> {
BeanConfig scanner = (BeanConfig) ScannerFactory.getScanner();
Swagger swagger = scanner.getSwagger();
servletContext.setAttribute("swagger", swagger);
};
}
}
The extended swagger feature is the following:
#Provider(value = Provider.Type.Feature, scope = Provider.Scope.Server)
public class ExtendedSwagger2Feature extends Swagger2Feature {
#Override
protected void addSwaggerResource(Server server, Bus bus) {
super.addSwaggerResource(server, bus);
BeanConfig config = (BeanConfig) ScannerFactory.getScanner();
Swagger swagger = config.getSwagger();
swagger.securityDefinition("Bearer", new ApiKeyAuthDefinition("authorization", In.HEADER));
}
}
I try to configure Bearer JWT token based authentication. My application.yml contains the following:
swagger:
basePath: /rest
title: Backend Application
description: Swagger documentation of Backend Application REST services
license:
licenceUrl:
contact:
resourcePackage: my.resource.package
scan: true
apiKeyAuthDefinition:
name: Authorization
in: header
type: http
I import the SwaggerConfig into my #SpringBootApplication class like this:
#Import(SwaggerConfig.class)
It is working, as i see, swagger appeared and the title and description field is filled with the properties of my yml.
What am I missing? Any suggestions would be appretiated.
Thanks in advance.
You can simplify your code and remove ExtendedSwagger2Feature and the initializer(). Modify your swagger() method as follows and the output will be the same:
#Bean
#ConfigurationProperties("swagger")
public Swagger2Feature swagger() {
Swagger2Feature swagger2Feature = new Swagger2Feature();
swagger2Feature.setSecurityDefinitions(Collections.singletonMap("bearerAuth",
new ApiKeyAuthDefinition("Authorization", In.HEADER)));
return swagger2Feature;
}
The reason for the token not being added to your request, is that securityDefinitions is just a declaration for the available schemes. You need to apply it to the operation by adding (to your TestResource interface):
#Api( authorizations = #Authorization( value = "bearerAuth" ))
You will notice a lock icon next to the operation in Swagger UI. Currently it's not there.
Btw. you should use the newer OpenApiFeature instead of the old Swagger2Feature. Happy to answer questions, if you have problems with it.

How to filter request based on domain in spring-mvc

I have one war file for my application and I will be using 2 domains to access it. For example I want to access admin.jsp using admin.mydomain.com/adminpage and other jsp pages I want to access with local.mydomain.com.
Also, admin.jsp should be only accessible via admin.mydomain.com and not via local.mydomain.com. How to do this in spring-security / spring-mvc? Is there a support in spring framework for this?
Any help on this would be helpful. Thanks.
You can implement RequestMatcher, and maybe like
HostOnlyRequestMatch(String relativePath, String hostname)
and then override the boolean matches(HttpServletRequest request) method, and if the relativePath and hostname are same with request, return true.
Add the requestMatcher to http like this:
http
.authorizeRequests()
.requestMatcher(new HostOnlyRequestMatch("/admin", "admin.mydomain.com")).permitAll()
.antMatchers("/admin").denyAll();
One way would be to configure proxy (e.g. Nginx) to route your requests to your application server (e.g Tomcat) properly. Read here for more details https://www.nginx.com/resources/admin-guide/reverse-proxy/
You can get the requested url from request object in you mvc controller and if it is not form correct domain then you can throw or show proper error based on your project. Following is the code snippet
#Controller
#RequestMapping("/adminpage")
public class AdminPageController{
#RequestMapping(method = RequestMethod.GET)
public String getAdminPage(HttpServletRequest request) {
String url = request.getRequestURL().toString();
if(!url.contains("admin.mydomain.com")) {
throw RuntimeException("Not accessible through this domain.");
// You can implement your own logic of showing error here
}
}
}

Swagger 2.0 support in Grails 3+ (3.0.11) not working

I am running Grails 3.0.11 and want to create Swagger-documentation for my REST endpoints. I added the SwaggyDoc-plugin to the dependencies in my build.gradle script by adding:
compile "org.grails.plugins:swaggydoc:0.26.0".
In IntelliJ I see the Swaggydoc-dependency added to my list of libraries.
After starting my Grails-application via the grails run-app command and opening my application by entering http://localhost:8080/api/ I get an 404 error telling the page does not exist.
Do I need to configure something more or to run something special to generate documentation? I already tried to open a ticket in the Git-project and contacting the author with no succes.
Update1: there seems to be a Grails 3-plugin (found at Versioneye?) which I added:
compile "org.grails.plugins:swaggydoc-grails3:0.26.0"
It does work half, by default some sort of Pet-demo is visible and it is failing on constraints in a domain and enums. Doesn't seem to work very well actually.
Update2: As pointed out by Dilip Krishnan I tried to use SpringFox, first I added the dependencies to my Gradle build file:
compile("io.springfox:springfox-swagger2:2.3.1")
compile("io.springfox:springfox-swagger-ui:2.3.1")
Then I added a new class called ApiDocumentationConfiguration with the following code:
#Configuration
#EnableSwagger2
public class ApiDocumentationConfiguration {
#Bean
public Docket documentation() {
return new Docket(DocumentationType.SWAGGER_2)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build();
}
#Bean
public UiConfiguration uiConfig() {
return UiConfiguration.DEFAULT;
}
private ApiInfo metadata() {
return new ApiInfoBuilder()
.title("My awesome API")
.description("Some description")
.version("1.0")
.contact("my-email#domain.org")
.build();
}
}
My Grails resources file contains the following code:
beans = {
apiDocumentationConfiguration(ApiDocumentationConfiguration)
}
Last step was starting the application and trying to load the end point which shows the Swagger front end:
http://localhost:8080/swagger-ui.html
It behind the scenes tries to load an other end point (containing the JSON I guess?) which loads
http://localhost:8080/v2/api-docs
This does show JSON data and I get end points for things like a basic error controller, health mvc, metrics mvc et cetera. But not my own annotated user controller which is annotated like follows:
#Api(value = "users", description = "Endpoint for user management")
class UserController {
// GET all users
#ApiOperation(value = "doStuff", nickname = "doStuff", response = User.class)
def index() {
respond User.list()
}
}
Seems I am almost there, but still missing something, is my annotation wrong or doesn't it scan my controllers?
Update3: in contact with one of the authors of SpringFox (Dilip Krishnan) to add support for Grails 3+ to SpringFox, see ticket. The reason it doesn't currently work is because SpringFox looks at MVC annotation, an adapter needs to be written to retrieve the endpoints from the controllers in Grails.
I have successfully used swaggydocs in both 2.4.x projects and 3.1.4.
In order to make it work in 3.x (tested on 3.1.4) you have to add
compile "org.grails.plugins:swaggydoc-grails3:0.26.0"
to gradle dependencies section. That makes swaggy available in your project.
Then add annotations to your controllers
#Api("test methods")
class TestController {
#ApiOperation(value = "some method description")
#ApiResponses([
#ApiResponse(code = 405, message = "Bad method. Only POST is allowed"),
#ApiResponse(code = 401, message = "Unauthorized"),
#ApiResponse(code = 400, message = "Invalid request json")
])
def testGetMethod() {
render([status: "OK"] as JSON)
}
Then mark your methods allowedMethods as follows
class TestController {
static allowedMethods = [testGetMethod: "GET", testPostMethod: "POST"]
NOTE this is really important - otherwise swaggy will mark every your method as GET. Swaggy doesn't respect neither httpMethod in ApiOperation annotation nor http method in url mappings.
Finally add your controller to urlmappings as swaggy checks url mappings to look for URLS. Note camelCase!
//NOTE small camelCase
//swaggy won't see urls correctly if you start controller name with capital letter
"/api/test/"(controller: "test", action: "testGetMethod")
"/api/test/"(controller: "test", action: "testPostMethod")
You can also add some api info in application.yml
swaggydoc:
contact: rafal#pydyniak.pl
description: sample swaggy app
You can find sample app (with dummy methods but point was to make swaggy work) at my github https://github.com/RafalPydyniak/Swaggy-example.
Also there are some more docs on how to use this api on http://rahulsom.github.io/swaggydoc/. I just wanted to show you how to install it (as it's quite tricky to make everything work)
Hope it helps!
I followed the same 2 steps:
1) add swagger2 dependencies
2) provide configuation
The problem is that sprinfox does not scan grails url mappings (see https://github.com/springfox/springfox/issues/1169#issuecomment-252259284)
To fix that I added standard spring annotations:
#Controller()
#RequestMapping(value="/path")
on the controller and
#RequestMapping(value="/resource")
on the method. After that sprinfox started to pick up my documentation.

Grails Spring Security AbstractPreAuthenticatedProcessingFilter redirect

I am trying to do PreAuthentication using Spring Security Grails plugin. I read the pre authentication documentation given below, but could not find anything concrete for my situation
http://static.springsource.org/spring-security/site/docs/3.0.x/reference/preauth.html
In my situation, we have a agent which parses the SAML request and gives a map after successful authentication. Recommendation is to use this jar. Hence, I extended AbstractPreAuthenticatedProcessingFilter and try to do this
#Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
userInfo = agent.readToken(request);
if (!userInfo){
WebUtils.retrieveGrailsWebRequest().getCurrentResponse().sendRedirect(ssoUrl)
}
return userInfo
}
I have placed myFilter under src/groovy and registered this filter in BootStrap
def init = { servletContext ->
SpringSecurityUtils.clientRegisterFilter(
'myFilter', SecurityFilterPosition.PRE_AUTH_FILTER.order)
}
Its getting loaded correctly, but filter is not issuing a redirect. First of all, I wanted to check if this is the right approach and if it is, how to get redirect working.
I have asked the same question in grails user forum
Any help is greatly appreciated.
Update:
Final configuration which worked for me
Wrote MyAuthenticationService which implements AuthenticationUserDetailsService as suggested. You also have to define preAuthenticatedAuthenticationProvider which wraps your custom service
resources.groovy
securityFilter(MySSOAuthFilters){ bean ->
authenticationManager = ref('authenticationManager')
grailsApplication = ref('grailsApplication')
}
customUserDetailsService(MyAuthenticationService)
preAuthenticatedAuthenticationProvider(org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider) {
preAuthenticatedUserDetailsService = ref('customUserDetailsService')
}
I was not able to do <form-login> because in Grails, this is done using
grails.plugins.springsecurity.auth.loginFormUrl config parameter which only accepts relative url.
What I ended up doing is grails.plugins.springsecurity.auth.loginFormUrl = '/login/index'
In LoginController
def index() {
if(springSecurityService.isLoggedIn()){
log.info("User is logged in")
return redirect(controller: 'mycontroller', action: 'list')
}
log.info("user is not logged in...redirect to sso.")
return redirect(url: ssoUrl)
}
Hope this helps
A couple of things I see that need to be changed.
First, do not to send a redirect in the preauth filter just simply return null. The preauth filter is only used to return a subject that your AuthenticationUserDetailsService can use to create the UserDetails object by implementing the method below in your implementation of the AuthenticationUserDetailsService .
public UserDetails loadUserDetails(AbstractAuthenticationToken token) {
return createUserFromSubject((Subject) token.getPrincipal());
}
Second, set the form login page as part of your configuration. This will be used to redirect to if no subject exists.
<form-login login-page="http://url_youwanttoredirect_to_on_auth_req" authentication-failure-url="http://url_youwanttoredirect_to_on_auth_req"/>

Resources