Java 如何在 Spring Boot Rest 应用程序中使用 Swagger ui 配置带有密码流的 oAuth2

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/42054384/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 23:53:48  来源:igfitidea点击:

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

javaspring-bootswaggerswagger-uispringfox

提问by Hasson

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:

我有 spring boot rest api(资源),它使用另一个 spring boot 授权服务器,我已将 Swagger 配置添加到资源应用程序中,以便为 rest 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", “[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 POST 到此链接,并在 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,

在 Swagger UI 中,我可以看到一个授权按钮,它打开一个对话框来发出授权请求,但它不起作用并将我定向到如下链接,

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

Swagger UI 有一个授权按钮

采纳答案by Hasson

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

8 个月后,终于在 Swagger UI 中支持密码流,这是对我有用的最终代码和设置:

1) Swagger Config:

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", "[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:

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) in the application.properties add the following properties:

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) in the Authorisation server add a CORS filter:

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

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:

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

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!!! :)

然后当您点击授权按钮时,您将看到以下对话框,添加您的用户名/密码和客户端 ID 和客户端密码的数据,类型必须是请求正文,我不知道为什么,但这是有效的和我一起,虽然我认为它应该是基本身份验证,因为这是发送客户端机密的方式,无论如何这就是 Swagger-ui 与密码流一起工作的方式,并且您的所有 API 端点都再次工作。快乐大摇大摆!!!:)

enter image description here

在此处输入图片说明

回答by Hasson

The best way so far to work with oAuth2 Authorisation is by using Swagger Editor, I have installed Swagger Editor quickly in Docker (from here), then used the import parameter to download the API JSON descriptor (your API should include CORS filter), then I can get Swagger Documentation and an interface where I can add a token which I get using curl, postman, or Firefox rest client.

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

The link which I am using now looks like this

我现在使用的链接看起来像这样

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

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

the interface in Swagger Editor to enter the token looks like this:

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

enter image description here

在此处输入图片说明

if there are better solutions, or workaround please post your answer here.

如果有更好的解决方案或解决方法,请在此处发布您的答案。

回答by Sabir Khan

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

我不确定您的问题是什么,但授权按钮对我来说适用于 2.7.0 版的 swagger,尽管我必须手动获取 JWT 令牌。

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_keyname to Authorizationand that I achieved with below Java configuration ,

这里的关键是我的令牌是 JWT 并且我无法在Bearer **之后插入令牌值并将 **api_key名称更改Authorization并且我使用以下 Java 配置实现了这一点,

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

There seems a bug in swagger about scope separatorwhich 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 .

关于范围分隔符的swagger 似乎有一个错误,默认情况下是:。在我的配置中,我尝试将其修改为: Bearer但没有发生,所以我必须在 UI 上输入。

回答by barisc

This is a bug on swagger-ui 2.6.1 which sends vendorExtensions scope everytime. And that causes the requests get out of scope which results in rejected requests. Since swagger can't get the access token it can't pass oauth2

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

Upgrading on maven should solve the problem. Minimum version should be 2.7.0

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