【问题标题】:Keycloak integration in SwaggerSwagger 中的 Keycloak 集成
【发布时间】:2017-06-14 14:28:37
【问题描述】:

我有一个受 Keycloak 保护的后端,我想通过 swagger-ui 访问它。 Keycloak 提供了 oauth2 隐式和访问代码流,但我无法使其工作。目前,Keycloak 的文档缺少关于 swagger.json 中的 authorizationUrltokenUrl 应该使用哪个 url。

Keycloak 中的每个领域都可以通过访问http://keycloak.local/auth/realms/REALM/.well-known/openid-configuration 来提供大量配置 url 列表

此外,我尝试通过添加以下行将 keycloak js-client 直接集成到 swagger-ui index.html 中:

<script src="keycloak/keycloak.js"></script>
<script>
  var keycloak = Keycloak('keycloak.json');
    keycloak.init({ onLoad: 'login-required' })
      .success(function (authenticated) {
        console.log('Login Successful');
        window.authorizations.add("oauth2", new ApiKeyAuthorization("Authorization", "Bearer " + keycloak.token, "header"));
      }).error(function () {
        console.error('Login Failed');
        window.location.reload();
      }
    );
 </script>

在“登录成功”之后我也尝试过类似的操作

swaggerUi.api.clientAuthorizations.add("key", new SwaggerClient.ApiKeyAuthorization("Authorization", "Bearer " + keycloak.token, "header"));

但它也不起作用。

有什么建议可以将 keycloak auth 集成到 swagger 中吗?

【问题讨论】:

  • 你有没有让这个工作?我也面临同样的问题。
  • 您想将keycloak 与swagger-UI 集成吗?你现在用 keycloak 保护你的招摇定义了吗?也许我可以帮助你

标签: oauth swagger swagger-ui keycloak jboss-tools


【解决方案1】:

Swagger-ui 可以使用implicit 认证模式与keycloak 集成。 您可以在 swagger-ui 上设置 oauth2,以便它会要求您进行身份验证,而不是直接向 swagger-ui 提供访问令牌。

第一件事,你的大摇大摆需要引用安全定义,如:

"securityDefinitions": {
    "oauth2": {
        "type":"oauth2",
        "authorizationUrl":"http://172.17.0.2:8080/auth/realms/master/protocol/openid-connect/auth",
        "flow":"implicit",
        "scopes": {
            "openid":"openid",
            "profile":"profile"
        }
    }
}

然后,你swagger-ui需要引用一些其他参数:用纯js,可以在index.html中使用

const ui = SwaggerUIBundle({ ...} );

ui.initOAuth({
    clientId: "test-uid",
    realm: "Master",
    appName: "swagger-ui",
    scopeSeparator: " ",
    additionalQueryStringParams: {"nonce": "132456"}
})

在这段代码中,

  • authorizationUrl 是您的 keycloak 领域的授权端点
  • 范围是您可以根据需要设置的东西
  • clientId 是在 keycloak 领域使用 implicit 模式参数化的客户端
  • 附加参数nonce应该是随机的,但是swagger-ui还没有使用。

如果你想在 Spring-boot 上执行所有这些,我在这里添加一个示例:

在这个框架上,你将主要使用 Springfox 的 swagger 和 swagger-ui web-jar。这是通过添加依赖项来完成的:

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.8.0</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.8.0</version>
</dependency>

通过在主类上添加注释 swagger2 来启用 Swagger:

@SpringBootApplication
@EnableSwagger2
public class TestSpringApplication {
    ...

然后您可以像这样设置Configuration 类:

@Configuration
public class SwaggerConfigurer {

    @Bean
    public SecurityConfiguration securityConfiguration() {

        Map<String, Object> additionalQueryStringParams=new HashMap<>();
        additionalQueryStringParams.put("nonce","123456");

        return SecurityConfigurationBuilder.builder()
            .clientId("test-uid").realm("Master").appName("swagger-ui")
            .additionalQueryStringParams(additionalQueryStringParams)
            .build();
    }

    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)
            .select()
            .apis(RequestHandlerSelectors.basePackage("com.example.testspring"))
            .paths(PathSelectors.any())
            .build().securitySchemes(buildSecurityScheme()).securityContexts(buildSecurityContext());
    }

    private List<SecurityContext> buildSecurityContext() {
        List<SecurityReference> securityReferences = new ArrayList<>();

        securityReferences.add(SecurityReference.builder().reference("oauth2").scopes(scopes().toArray(new AuthorizationScope[]{})).build());

        SecurityContext context = SecurityContext.builder().forPaths(Predicates.alwaysTrue()).securityReferences(securityReferences).build();

        List<SecurityContext> ret = new ArrayList<>();
        ret.add(context);
        return ret;
    }

    private List<? extends SecurityScheme> buildSecurityScheme() {
        List<SecurityScheme> lst = new ArrayList<>();
        // lst.add(new ApiKey("api_key", "X-API-KEY", "header"));

        LoginEndpoint login = new LoginEndpointBuilder().url("http://172.17.0.2:8080/auth/realms/master/protocol/openid-connect/auth").build();

        List<GrantType> gTypes = new ArrayList<>();
        gTypes.add(new ImplicitGrant(login, "acces_token"));

        lst.add(new OAuth("oauth2", scopes(), gTypes));
        return lst;
    }

    private List<AuthorizationScope> scopes() {
        List<AuthorizationScope> scopes = new ArrayList<>();
        for (String scopeItem : new String[]{"openid=openid", "profile=profile"}) {
            String scope[] = scopeItem.split("=");
            if (scope.length == 2) {
                scopes.add(new AuthorizationScopeBuilder().scope(scope[0]).description(scope[1]).build());
            } else {
                log.warn("Scope '{}' is not valid (format is scope=description)", scopeItem);
            }
        }

        return scopes;
    }
}

您可以在此代码中更新很多内容。这和以前大体相同:

  • nonce 应该是随机的东西(swagger-ui 还没用)
  • clientId 您需要根据您在 keycloak 中设置的客户端进行设置
  • basePackage: 你需要设置你所有控制器所在的包
  • 如果您需要 api-key,您可以启用它并将其添加到安全方案列表中
  • LoginEndpoint: 需要是你keycloak领域的授权端点
  • scopeItems:您希望用于此身份验证的范围。

它会产生和之前一样的东西:更新swagger添加securityDefinition并使swagger-UI接受clientId、nonce、...的参数。

【讨论】:

    【解决方案2】:

    在过去 2 天里一直在努力解决此设置问题。终于为那些无法解决的人找到了一个可行的解决方案。

    pom.xml

        ...
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-spring-security-adapter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-spring-boot-starter</artifactId>
        </dependency>
        ...
    

    在主类上启用 Swagger

    ...    
    import springfox.documentation.swagger2.annotations.EnableSwagger2;
    
    @SpringBootApplication
    @EnableSwagger2
    @EnableAsync
    @EnableCaching
    public class MainApplication {
      public static void main(String[] args) {
        SpringApplication app = new SpringApplication(MainApplication.class);
        app.run(args);
      }
    }
    

    SwaggerConfig.java

    package com.XXX.XXXXXXXX.app.config;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import springfox.documentation.builders.ApiInfoBuilder;
    import springfox.documentation.builders.AuthorizationCodeGrantBuilder;
    import springfox.documentation.builders.OAuthBuilder;
    import springfox.documentation.builders.PathSelectors;
    import springfox.documentation.service.*;
    import springfox.documentation.spi.DocumentationType;
    import springfox.documentation.spi.service.contexts.SecurityContext;
    import springfox.documentation.spring.web.plugins.Docket;
    import springfox.documentation.swagger.web.SecurityConfiguration;
    import springfox.documentation.swagger.web.SecurityConfigurationBuilder;
    import springfox.documentation.swagger2.annotations.EnableSwagger2;
    
    import java.util.Arrays;
    
    import static springfox.documentation.builders.PathSelectors.regex;
    
    /*
     * Setting up Swagger for spring boot
     * https://www.baeldung.com/swagger-2-documentation-for-spring-rest-api
     */
    @Configuration
    @EnableSwagger2
    public class SwaggerConfig {
    
     @Value("${keycloak.auth-server-url}")
     private String AUTH_SERVER;
    
     @Value("${keycloak.credentials.secret}")
     private String CLIENT_SECRET;
    
     @Value("${keycloak.resource}")
     private String CLIENT_ID;
    
     @Value("${keycloak.realm}")
     private String REALM;
    
     private static final String OAUTH_NAME = "spring_oauth";
     private static final String ALLOWED_PATHS = "/directory_to_controllers/.*";
     private static final String GROUP_NAME = "XXXXXXX-api";
     private static final String TITLE = "API Documentation for XXXXXXX Application";
     private static final String DESCRIPTION = "Description here";
     private static final String VERSION = "1.0";
    
     @Bean
     public Docket taskApi() {
       return new Docket(DocumentationType.SWAGGER_2)
        .groupName(GROUP_NAME)
        .useDefaultResponseMessages(true)
        .apiInfo(apiInfo())
        .select()
        .paths(regex(ALLOWED_PATHS))
        .build()
        .securitySchemes(Arrays.asList(securityScheme()))
        .securityContexts(Arrays.asList(securityContext()));
     }
    
     private ApiInfo apiInfo() {
       return new 
         ApiInfoBuilder().title(TITLE).description(DESCRIPTION).version(VERSION).build();
     }
    
     @Bean
     public SecurityConfiguration security() {
       return SecurityConfigurationBuilder.builder()
        .realm(REALM)
        .clientId(CLIENT_ID)
        .clientSecret(CLIENT_SECRET)
        .appName(GROUP_NAME)
        .scopeSeparator(" ")
        .build();
     }
    
     private SecurityScheme securityScheme() {
       GrantType grantType =
        new AuthorizationCodeGrantBuilder()
            .tokenEndpoint(new TokenEndpoint(AUTH_SERVER + "/realms/" + REALM + "/protocol/openid-connect/token", GROUP_NAME))
            .tokenRequestEndpoint(
                new TokenRequestEndpoint(AUTH_SERVER + "/realms/" + REALM + "/protocol/openid-connect/auth", CLIENT_ID, CLIENT_SECRET))
            .build();
    
    SecurityScheme oauth =
        new OAuthBuilder()
            .name(OAUTH_NAME)
            .grantTypes(Arrays.asList(grantType))
            .scopes(Arrays.asList(scopes()))
            .build();
    return oauth;
     }
    
     private AuthorizationScope[] scopes() {
    AuthorizationScope[] scopes = {
      new AuthorizationScope("user", "for CRUD operations"),
      new AuthorizationScope("read", "for read operations"),
      new AuthorizationScope("write", "for write operations")
    };
    return scopes;
    }
    
    private SecurityContext securityContext() {
    return SecurityContext.builder()
        .securityReferences(Arrays.asList(new SecurityReference(OAUTH_NAME, scopes())))
        .forPaths(PathSelectors.regex(ALLOWED_PATHS))
        .build();
     }
    }
    

    从终端运行“mvnw spring-boot:run”

    打开浏览器并点击http://localhost:[port]/[app_name]/swagger-ui.html

    点击授权按钮: Swagger Authorize Button

    这应该会显示一个模式来确认您的 keycloak 设置。

    再次点击授权按钮。您应该被重定向到登录屏幕。

    输入并确认凭据后,您将被重定向回完全经过身份验证的 Swagger-UI。

    【讨论】:

    • 范围从何而来?在您的密钥斗篷中,您在哪里定义了用户/读/写?
    【解决方案3】:

    使用隐式流程、OpenAPI 3.0 模板的 Swagger-ui + Keycloak(或任何其他 OAuth2 提供程序):

    components:
      ...
       securitySchemes:
        my_auth_whatever:
          type: oauth2
          flows:
            implicit:
              authorizationUrl: https://MY-KEYCLOAK-HOST/auth/realms/MY-REALM-ID/protocol/openid-connect/auth
              scopes: {}
      ...
    security:
      - my_auth_whatever: []
    

    确保在您使用的客户端的 Keycloak 设置中启用了隐式流。

    一个缺点是,当点击 Swagger UI 中的“授权”按钮时,仍会在模式中要求用户输入 client_id。 用户输入的值可能会通过将查询参数?client_id=YOUR-CLIENT-ID 添加到 authorizationUrl 来覆盖,但这有点肮脏,并且模态仍然显示给用户。 在 docker 中运行 swagger-ui 时 - 可以将 OAUTH_CLIENT_ID 环境变量提供给容器以设置模态的默认 client_id 值。 对于非 docker 部署,请参阅 @wargre 更改 index.html 的方法(不确定是否有更好的方法)。

    对于 SwaggerAPI (OpenAPI 2.0) 示例,请参阅@wargre 的答案中的第一个代码 sn-p 和此文档:https://swagger.io/docs/specification/2-0/authentication/

    【讨论】:

      猜你喜欢
      • 2019-03-28
      • 2016-11-07
      • 2019-12-02
      • 2021-07-20
      • 2021-11-15
      • 2022-07-07
      • 1970-01-01
      • 2018-06-08
      • 2014-02-23
      相关资源
      最近更新 更多