How to configure oAuth2 with password flow with Swagger ui in spring boot rest application

49,691

Solution 1

After 8 months, finally the password flow is supported in Swagger UI, here is the final code and settings which works for me:

1) Swagger Config:

package com.example.api;


import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.bind.annotation.RequestMethod;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.Contact;
import springfox.documentation.service.GrantType;
import springfox.documentation.service.OAuth;
import springfox.documentation.service.ResourceOwnerPasswordCredentialsGrant;
import springfox.documentation.service.ResponseMessage;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.builders.ResponseMessageBuilder;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger.web.ApiKeyVehicle;
import springfox.documentation.swagger.web.SecurityConfiguration;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.Collections;
import java.util.List;

import static com.google.common.collect.Lists.*;

@Configuration
@EnableSwagger2
public class SwaggerConfig {

    @Value("${app.client.id}")
    private String clientId;
    @Value("${app.client.secret}")
    private String clientSecret;
    @Value("${info.build.name}")
    private String infoBuildName;

    @Value("${host.full.dns.auth.link}")
    private String authLink;

    @Bean
    public Docket api() {

        List<ResponseMessage> list = new java.util.ArrayList<>();
        list.add(new ResponseMessageBuilder().code(500).message("500 message")
                .responseModel(new ModelRef("Result")).build());
        list.add(new ResponseMessageBuilder().code(401).message("Unauthorized")
                .responseModel(new ModelRef("Result")).build());
        list.add(new ResponseMessageBuilder().code(406).message("Not Acceptable")
                .responseModel(new ModelRef("Result")).build());

        return new Docket(DocumentationType.SWAGGER_2).select().apis(RequestHandlerSelectors.any())
                .paths(PathSelectors.any()).build().securitySchemes(Collections.singletonList(securitySchema()))
                .securityContexts(Collections.singletonList(securityContext())).pathMapping("/")
                .useDefaultResponseMessages(false).apiInfo(apiInfo()).globalResponseMessage(RequestMethod.GET, list)
                .globalResponseMessage(RequestMethod.POST, list);



    }

    private OAuth securitySchema() {

        List<AuthorizationScope> authorizationScopeList = newArrayList();
        authorizationScopeList.add(new AuthorizationScope("read", "read all"));
        authorizationScopeList.add(new AuthorizationScope("trust", "trust all"));
        authorizationScopeList.add(new AuthorizationScope("write", "access all"));

        List<GrantType> grantTypes = newArrayList();
        GrantType creGrant = new ResourceOwnerPasswordCredentialsGrant(authLink+"/oauth/token");

        grantTypes.add(creGrant);

        return new OAuth("oauth2schema", authorizationScopeList, grantTypes);

    }

    private SecurityContext securityContext() {
        return SecurityContext.builder().securityReferences(defaultAuth()).forPaths(PathSelectors.ant("/user/**"))
                .build();
    }

    private List<SecurityReference> defaultAuth() {

        final AuthorizationScope[] authorizationScopes = new AuthorizationScope[3];
        authorizationScopes[0] = new AuthorizationScope("read", "read all");
        authorizationScopes[1] = new AuthorizationScope("trust", "trust all");
        authorizationScopes[2] = new AuthorizationScope("write", "write all");

        return Collections.singletonList(new SecurityReference("oauth2schema", authorizationScopes));
    }

    @Bean
    public SecurityConfiguration securityInfo() {
        return new SecurityConfiguration(clientId, clientSecret, "", "", "", ApiKeyVehicle.HEADER, "", " ");
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder().title("My API title").description("")
                .termsOfServiceUrl("https://www.example.com/api")
                .contact(new Contact("Hasson", "http://www.example.com", "[email protected]"))
                .license("Open Source").licenseUrl("https://www.example.com").version("1.0.0").build();
    }

}

2) in POM use this Swagger UI version 2.7.0:

    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger2</artifactId>
        <version>2.7.0</version>
    </dependency>
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-swagger-ui</artifactId>
        <version>2.7.0</version>
    </dependency>
    <dependency>
        <groupId>io.springfox</groupId>
        <artifactId>springfox-bean-validators</artifactId>
        <version>2.7.0</version>
    </dependency>

3) in the application.properties add the following properties:

host.full.dns.auth.link=http://oauthserver.example.com:8081
app.client.id=test-client
app.client.secret=clientSecret
auth.server.schem=http

4) in the Authorisation server add a CORS filter:

package com.example.api.oauth2.oauth2server;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * Allows cross origin for testing swagger docs using swagger-ui from local file
 * system
 */
@Component
public class CrossOriginFilter implements Filter {
    private static final Logger log = LoggerFactory.getLogger(CrossOriginFilter.class);

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

        // Called by the web container to indicate to a filter that it is being
        // placed into service.
        // We do not want to do anything here.
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
            throws IOException, ServletException {

        log.info("Applying CORS filter");
        HttpServletResponse response = (HttpServletResponse) resp;
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "0");
        chain.doFilter(req, resp);
    }

    @Override
    public void destroy() {

        // Called by the web container to indicate to a filter that it is being
        // taken out of service.
        // We do not want to do anything here.
    }
}

If you run with these settings you will get the authorize button in the link http://apiServer.example.com:8080/swagger-ui.html#/ (if you run on 8080) as follows:

enter image description here

Then when you click on the authorize button you will get the following dialogue, add the data for your username/password and the client id and the client secret, the type has to be request body, I am not sure why but this is what works with me, although I thought it should be basic auth as this is how the client secret is sent, anyway this is how Swagger-ui works with password flow and all your API endpoints are working again. Happy swaggering!!! :)

enter image description here

Solution 2

I am not sure as what was the issue for you but Authorize button is working for me for swagger version 2.7.0, though I have to get JWT token manually.

First I make a hit for auth token then I insert token like below,

enter image description here

Key here is that my tokens are JWT and I was not able to insert token value after Bearer ** and changing **api_key name to Authorization and that I achieved with below Java configuration ,

@Bean
    public SecurityConfiguration securityInfo() {
        return new SecurityConfiguration(null, null, null, null, "", ApiKeyVehicle.HEADER,"Authorization",": Bearer");
    }

There seems a bug in swagger about scope separator which by default is : . In my config, I tried to modify it to : Bearerbut that is not happening so I have to enter that on UI .

Share:
49,691
Hasson
Author by

Hasson

Updated on November 06, 2020

Comments

  • Hasson
    Hasson over 3 years

    I have spring boot rest api (resources) which uses another spring boot authorisation server, I have added Swagger config to the resource application to get a nice and quick documentation/test platform for the rest API. my Swagger config looks like this:

    @Configuration
    @EnableSwagger2
    public class SwaggerConfig {    
    
        @Autowired
        private TypeResolver typeResolver;
    
        @Value("${app.client.id}")
        private String clientId;
        @Value("${app.client.secret}")
        private String clientSecret;
        @Value("${info.build.name}")
        private String infoBuildName;
    
        public static final String securitySchemaOAuth2 = "oauth2";
        public static final String authorizationScopeGlobal = "global";
        public static final String authorizationScopeGlobalDesc = "accessEverything";
    
        @Bean
        public Docket api() { 
    
            List<ResponseMessage> list = new java.util.ArrayList<ResponseMessage>();
            list.add(new ResponseMessageBuilder()
                    .code(500)
                    .message("500 message")
                    .responseModel(new ModelRef("JSONResult«string»"))
                    .build());
            list.add(new ResponseMessageBuilder()
                    .code(401)
                    .message("Unauthorized")
                    .responseModel(new ModelRef("JSONResult«string»"))
                    .build());
    
    
            return new Docket(DocumentationType.SWAGGER_2)  
              .select()                                  
              .apis(RequestHandlerSelectors.any())              
              .paths(PathSelectors.any())     
              .build()
              .securitySchemes(Collections.singletonList(securitySchema()))
              .securityContexts(Collections.singletonList(securityContext()))
              .pathMapping("/")
              .directModelSubstitute(LocalDate.class,String.class)
              .genericModelSubstitutes(ResponseEntity.class)
              .alternateTypeRules(
                  newRule(typeResolver.resolve(DeferredResult.class,
                          typeResolver.resolve(ResponseEntity.class, WildcardType.class)),
                      typeResolver.resolve(WildcardType.class)))
              .useDefaultResponseMessages(false)
              .apiInfo(apiInfo())
              .globalResponseMessage(RequestMethod.GET,list)
              .globalResponseMessage(RequestMethod.POST,list);
        }
    
    
        private OAuth securitySchema() {
    
            List<AuthorizationScope> authorizationScopeList = newArrayList();
            authorizationScopeList.add(new AuthorizationScope("global", "access all"));
    
            List<GrantType> grantTypes = newArrayList();
            final TokenRequestEndpoint tokenRequestEndpoint = new TokenRequestEndpoint("http://server:port/oauth/token", clientId, clientSecret);
            final TokenEndpoint tokenEndpoint = new TokenEndpoint("http://server:port/oauth/token", "access_token");
            AuthorizationCodeGrant authorizationCodeGrant = new AuthorizationCodeGrant(tokenRequestEndpoint, tokenEndpoint);
    
            grantTypes.add(authorizationCodeGrant);
    
            OAuth oAuth = new OAuth("oauth", authorizationScopeList, grantTypes);
    
            return oAuth;
        }
    
    
        private SecurityContext securityContext() {
            return SecurityContext.builder().securityReferences(defaultAuth())
                    .forPaths(PathSelectors.ant("/api/**")).build();
        }
    
        private List<SecurityReference> defaultAuth() {
    
            final AuthorizationScope authorizationScope =
                    new AuthorizationScope(authorizationScopeGlobal, authorizationScopeGlobalDesc);
            final AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
            authorizationScopes[0] = authorizationScope;
            return Collections
                    .singletonList(new SecurityReference(securitySchemaOAuth2, authorizationScopes));
        }
    
    
    
        private ApiInfo apiInfo() {
            return new ApiInfoBuilder()
                    .title(“My rest API")
                    .description(" description here … ”)
                    .termsOfServiceUrl("https://www.example.com/")
                    .contact(new Contact(“XXXX XXXX”,
                                         "http://www.example.com", “[email protected]”))
                    .license("license here”)
                    .licenseUrl("https://www.example.com")
                    .version("1.0.0")
                    .build();
        }
    
    }
    

    The way I get the access token from the Authorisation server is by using http POST to this link with basic authorisation in the header for clientid/clientpass:

    http://server:port/oauth/token?grant_type=password&username=<username>&password=<password>
    

    the response is something like:

    {
        "access_token": "e3b98877-f225-45e2-add4-3c53eeb6e7a8",
        "token_type": "bearer",
        "refresh_token": "58f34753-7695-4a71-c08a-d40241ec3dfb",
        "expires_in": 4499,
        "scope": "read trust write"
    }
    

    in Swagger UI I can see an Authorisation button, which opens a dialog to make the authorisation request, but it is not working and directing me to a link as following,

    http://server:port/oauth/token?response_type=code&redirect_uri=http%3A%2F%2Fserver%3A8080%2Fwebjars%2Fspringfox-swagger-ui%2Fo2c.html&realm=undefined&client_id=undefined&scope=global%2CvendorExtensions&state=oauth
    

    what I am missing here?

    Swagger UI has an Authorisation button

  • Sabir Khan
    Sabir Khan over 6 years
    A bit of formatting issue, I guess , newArrayList(); would be new ArrayList<>();
  • Ranjith
    Ranjith over 6 years
    Thanks @Hasson , Authorize button got enabled, but APIS's are not working for me. Seems not sending the token with the request. Can you please help again
  • user3529850
    user3529850 over 5 years
    could share class that extends ResourceServerConfigurerAdapter ? do you have .antMatchers("/swagger*", "/v2/**").permitAll() in there ?
  • Hasson
    Hasson over 5 years
    I have them in the class which extends WebSecurityConfigurerAdapter sorry cannot share that as it contains some security features in the application.
  • SAMUEL
    SAMUEL over 4 years
    Is it possible to configure that the swager-ui to request new access token with refresh token ?
  • Hasson
    Hasson over 4 years
    not to the best of my knowledge, it will be great if this functionality is available, please comment here if you find a way.
  • gorcajo
    gorcajo over 4 years
    If you want to use basic auth (recommended) instead creds into the request body, you have to allow CORS for /oauth/token and always give an HTTP 200 to OPTIONS /oauth/token. Sending client id & client secret into the request body is not recommended by OAuth 2.0.
  • Dhwanil Patel
    Dhwanil Patel about 4 years
    @Hasson, Thanks for the answer, But i got the same issue like "@Ranjith". I'm able to authenticate but the token not bind with requested APIs.
  • Dhwanil Patel
    Dhwanil Patel about 4 years
    curl -X GET "localhost:9001/api/v1/…" -H "accept: /"
  • Dhwanil Patel
    Dhwanil Patel about 4 years
    @Ranjith do you get any answer of your mentioned point in comment? I face the same issue.
  • Ranjith
    Ranjith over 3 years
    This was a bug and solved in the latest versions (2.9.2 and bove) of springfox
  • Irfan Nasim
    Irfan Nasim over 3 years
    do anybody have repository on git? i actually want to clone an go through it.