【问题标题】:How to configure oAuth2 with password flow with Swagger ui in spring boot rest application如何在 Spring Boot Rest 应用程序中使用 Swagger ui 配置 oAuth2 和密码流
【发布时间】:2017-06-22 14:29:26
【问题描述】:

我有 spring boot rest api (resources),它使用另一个 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", “xxxx@example.com”))
                .license("license here”)
                .licenseUrl("https://www.example.com")
                .version("1.0.0")
                .build();
    }

}

我从授权服务器获取访问令牌的方式是使用 http POST 到此链接,并在 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 中,我可以看到一个授权按钮,该按钮会打开一个对话框以发出授权请求,但它不起作用并将我引导至如下链接,

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

我在这里缺少什么?

【问题讨论】:

  • 我对 .netcore 应用程序和 Identity Server 有同样的问题。我可以使用邮递员或 curl 使用http://&lt;identityServerUrl&gt;:&lt;port&gt;/connect/token 获取令牌,但招摇的用户界面将我引导至链接...
  • 查看我作为潜在答案发布的解决方法。

标签: java spring-boot swagger swagger-ui springfox


【解决方案1】:

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

我现在使用的链接是这样的

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

Swagger Editor 中输入token的界面如下:

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

【讨论】:

    【解决方案2】:

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

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

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

    【讨论】:

    • 有点格式问题,我猜newArrayList(); 应该是new ArrayList&lt;&gt;();
    • 感谢@Hasson,授权按钮已启用,但 APIS 对我不起作用。似乎没有随请求发送令牌。你能再帮忙吗
    • 可以共享扩展 ResourceServerConfigurerAdapter 的类吗?你有.antMatchers("/swagger*", "/v2/**").permitAll()吗?
    • 我在扩展 WebSecurityConfigurerAdapter 的类中有它们,因为它包含应用程序中的一些安全功能,所以无法共享。
    • 是否可以配置 swager-ui 以使用刷新令牌请求新的访问令牌?
    【解决方案3】:

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

    首先我对 auth 令牌进行命中,然后像下面这样插入令牌,

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

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

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

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-03-16
        • 2014-05-07
        • 2023-02-07
        • 2021-09-27
        • 2015-11-18
        • 1970-01-01
        相关资源
        最近更新 更多