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

Java 如何在spring boot rest应用程序中使用Swagger ui配置具有密码流的oAuth2,java,spring-boot,swagger,swagger-ui,springfox,Java,Spring Boot,Swagger,Swagger Ui,Springfox,我有SpringBootRESTAPI(参考资料),它使用了另一个SpringBootAuthorization服务器,我在资源应用程序中添加了Swagger配置,为RESTAPI提供了一个好的、快速的文档/测试平台。我的招摇过市配置如下所示: @Configuration @EnableSwagger2 public class SwaggerConfig { @Autowired private TypeResolver typeResolver; @Va

我有SpringBootRESTAPI(参考资料),它使用了另一个SpringBootAuthorization服务器,我在资源应用程序中添加了Swagger配置,为RESTAPI提供了一个好的、快速的文档/测试平台。我的招摇过市配置如下所示:

@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();
    }

}
在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
我错过了什么


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

我现在使用的链接如下所示

http://docker.example.com/#/?import=http://mywebserviceapi.example.com:8082/v2/api-单据&无代理

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

@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();
    }

}


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

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

1) 招摇过市配置:

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();
    }

}
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.
    }
}
如果使用这些设置运行,您将获得链接中的授权按钮(如果在8080上运行),如下所示:

然后,当你点击授权按钮时,你会看到下面的对话,添加用户名/密码、客户端id和客户端密码的数据,类型必须是请求正文,我不知道为什么,但这是我的工作原理,尽管我认为这应该是基本身份验证,因为这是客户端密码的发送方式,不管怎样,这就是Swagger ui如何处理密码流,并且您的所有API端点都再次工作。快乐的大摇大摆!!!:)


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

首先我点击了auth令牌,然后插入如下令牌

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

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

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

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


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

我对.netcore应用程序和Identity Server有相同的问题。我可以使用postman或curl通过
http://:/connect/token
获取令牌,但招摇过市的用户界面会将我指向一个链接……请参阅我的解决方案,作为一个潜在的答案。我想,有点格式问题,
newArrayList()将是
新的ArrayList()谢谢@Hasson,授权按钮已启用,但API不适用于我。似乎没有随请求一起发送令牌。您可以帮助创建一个扩展
ResourceServerConfigurerAdapter
的共享类吗?你有
.antMatchers(“/swagger*”,“/v2/**”).permitAll()
吗?我在扩展
websecurityConfigureAdapter
的类中有它们,很抱歉无法共享,因为它在应用程序中包含一些安全功能。是否可以将swager ui配置为使用刷新令牌请求新的访问令牌?
host.full.dns.auth.link=http://oauthserver.example.com:8081
app.client.id=test-client
app.client.secret=clientSecret
auth.server.schem=http
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.
    }
}
@Bean
    public SecurityConfiguration securityInfo() {
        return new SecurityConfiguration(null, null, null, null, "", ApiKeyVehicle.HEADER,"Authorization",": Bearer");
    }