如何在 spring boot rest 应用程序中使用 Swagger ui 使用密码流配置 oAuth2

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

我有 spring boot rest api(资源),它使用另一个 spring 启动授权服务器,我已将 Swagger 配置添加到资源应用程序中以获得快速的 documentation/test平台休息API。我的 Swagger 配置如下所示:

@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", “xxxx@example.com”))
                .license("license here”)
                .licenseUrl("https://www.example.com")
                .version("1.0.0")
                .build();
    }

}

我从授权服务器获取访问令牌的方法是使用 http POST 到此 link,并在 header 中对 clientid/clientpass 进行基本授权:

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

响应类似于:

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

在 Swagger 中 UI 我可以看到一个授权按钮,它会打开一个对话框来发出授权请求,但它不起作用,并且将我引导至 link,如下所示,

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

我在这里缺少什么?

到目前为止,使用 oAuth2 授权的最佳方法是使用 Swagger Editor,我在 Docker(来自 here)中快速安装了 Swagger Editor,然后使用导入参数下载API JSON 描述符(您的 API 应该包括 CORS 过滤器),然后我可以获得 Swagger 文档和一个界面,我可以在其中添加使用 curl 获得的令牌,post man,或者 Firefox 休息客户端。

我现在用的link是这样的

http://docker.example.com/#/?import=http://mywebserviceapi.example.com:8082/v2/api-docs&no-proxy

Swagger Editor 中输入令牌的界面如下所示:

如果有更好的解决方案或解决方法,请post在这里回答。

8 个月后,Swagger 终于支持密码流 UI,这是适合我的最终代码和设置:

1) Swagger 配置:

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", "hasson@example.com"))
                .license("Open Source").licenseUrl("https://www.example.com").version("1.0.0").build();
    }

}

2) 在 POM 中使用这个 Swagger UI 版本 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) 在application.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) 在授权服务器中添加一个CORS过滤器:

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.
    }
}

如果您 运行 使用这些设置,您将在 link http://apiServer.example.com:8080/swagger-ui.html#/ 中获得授权按钮(如果您 运行 使用 8080),如下所示:

然后当你点击授权按钮时你会得到下面的对话,为你的username/password和客户ID和客户密码添加数据,类型必须是请求正文,我不是知道为什么,但这对我有用,尽管我认为它应该是基本身份验证,因为这是发送客户端机密的方式,无论如何,这就是 Swagger-ui 与密码流和所有 API 一起工作的方式] 端点再次工作。快乐招摇!!! :)

我不确定您遇到了什么问题,但是 Authorize 按钮适用于 swagger 版本 2.7.0,尽管我必须手动获取 JWT 令牌。

首先,我点击了授权令牌,然后我插入令牌,如下所示,

这里的关键是我的令牌是 JWT,我无法在 Bearer ** 之后插入令牌值并将 **api_key 名称更改为 授权 并且我通过以下 Java 配置实现,

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

关于 scope separator 似乎有一个错误,默认情况下是 : 。在我的配置中,我试图将其修改为 : Bearer 但那没有发生,所以我必须在 UI 上输入它。

这是 swagger-ui 2.6.1 上的一个错误,它每次都会发送 vendorExtensions 范围。这会导致请求超出范围,从而导致请求被拒绝。由于 swagger 无法获取访问令牌,因此无法通过 oauth2

升级maven应该可以解决问题。最低版本应为 2.7.0