如何使用Keyclaok和Swagger实现Sping Boot Security OAuth2

4zcjmb1e  于 2023-10-20  发布在  Spring
关注(0)|答案(2)|浏览(152)

我希望能够通过使用Keycloak和Sping Boot Security的Swagger UI登录。到目前为止,我可以通过 Postman 使用bearer生成的token登录,但如何配置用户凭据登录?
安全配置:

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@RequiredArgsConstructor
public class SecurityConfig {
    private static final String[] AUTH_WHITELIST = {"/swagger-resources", "/swagger-resources/**", "/configuration/ui",
            "/configuration/security", "/swagger-ui.html", "/webjars/**", "/v3/api-docs/**", "v3/api-docs",
            "/api/public/**", "/api/public/authenticate", "/actuator/*", "/swagger-ui/**", "/api-docs/**"};

    private final JwtAuthConverter jwtAuthConverter;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.csrf(csrf -> csrf.disable());
        http.authorizeHttpRequests(auth -> auth.requestMatchers(AUTH_WHITELIST).permitAll().anyRequest().authenticated());
        http.oauth2ResourceServer(o2 -> o2.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthConverter)));
        http.sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
        return http.build();
    }
}

转换器:

@Component
public class JwtAuthConverter implements Converter<Jwt, AbstractAuthenticationToken> {

    private final JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();

    @Value("${jwt.auth.converter.principle_attribute}")
    private String principleAttribute;
    @Value("${jwt.auth.converter.resource-id}")
    private String resourceId;

    @Override
    public AbstractAuthenticationToken convert(@NonNull Jwt jwt) {
        Collection<GrantedAuthority> authorities = Stream.concat(jwtGrantedAuthoritiesConverter.convert(jwt).stream(), extractResourceRoles(jwt).stream()).collect(Collectors.toSet());
        return new JwtAuthenticationToken(jwt, authorities, getPrincipleClaimName(jwt));
    }

    private String getPrincipleClaimName(Jwt jwt) {
        String claimName = JwtClaimNames.SUB;
        if (principleAttribute != null) {
            claimName = principleAttribute;
        }
        return jwt.getClaim(claimName);
    }

    private Collection<? extends GrantedAuthority> extractResourceRoles(Jwt jwt) {
        Map<String, Object> resourceAccess;
        Map<String, Object> resource;
        Collection<String> resourceRoles;
        if (jwt.getClaim("resource_access") == null) {
            return Set.of();
        }
        resourceAccess = jwt.getClaim("resource_access");

        if (resourceAccess.get(resourceId) == null) {
            return Set.of();
        }
        resource = (Map<String, Object>) resourceAccess.get(resourceId);

        resourceRoles = (Collection<String>) resource.get("roles");
        return resourceRoles.stream().map(role -> new SimpleGrantedAuthority("ROLE_" + role)).collect(Collectors.toSet());
    }
}

Swagger配置:

@Configuration
public class SwaggerConfig {
    private static final String OAUTH_SCHEME_BEARER = "bearerAuth";

    @Bean
    public OpenAPI customizeOpenAPI() {
        return new OpenAPI()
                .addSecurityItem(new SecurityRequirement()
                        .addList(OAUTH_SCHEME_BEARER))
                .components(new Components()
                        .addSecuritySchemes(OAUTH_SCHEME_BEARER, new SecurityScheme()
                                .name(OAUTH_SCHEME_BEARER)
                                .type(SecurityScheme.Type.HTTP)
                                .scheme("bearer")
                                .bearerFormat("JWT")));
    }

}

任何建议或教程链接,将受到欢迎,我尝试了我能找到的,但没有运气。

tmb3ates

tmb3ates1#

根据我们在评论中的讨论,我认为你不能通过swagger访问应用程序的端点,并且根据你提供的代码,我可以说你应该改变你的swagger配置:
大概是这样的:

OpenApi配置类

@Configuration
public class SwaggerConfig {
  
  @Bean
  public OpenAPI myAPI() {
    return new OpenAPI()
        .components(new Components()
            .addSecuritySchemes("bearer-key",
                new SecurityScheme()
                    .type(SecurityScheme.Type.HTTP)
                    .scheme("bearer")
                    .bearerFormat("JWT")
            )
        )
        .info(getApiInfo())
        .addSecurityItem(
            new SecurityRequirement()
                .addList("bearer-jwt", Arrays.asList("read", "write"))
                .addList("bearer-key", Collections.emptyList())
        );
  }
  

  private Info getApiInfo() {
    return new Info()
        .title("Application Backend API")
        .description("APIS")
        .version("version")
        .contact(getContacts())
        .license(new License().name("Apache 2.0").url("http://springdoc.org"));
  }

  private Contact getContacts() {
    return new Contact()
        .name("app name")
        .url("url")
        .email("mail");
  }
}

在它之后,如果你将打开打开API URL(...swagger-ui/index.html),并点击Authorize按钮后,它插入您的JWT令牌在这里就像你在 Postman 所有将工作得很好。

更新:

OAuth 2.0中的Authorization Code Flow是一个客户端(在您的案例中为postman)从授权服务器获取授权码,然后使用该代码从令牌服务器获取访问令牌的过程,之后它可以使用令牌(客户端)从资源服务器(您在8081上运行的应用程序)访问安全端点。这意味着swagger-ui不能被配置为获取token。
关于这个主题的好文章oauth-authorization-code-grant-type

jljoyd4f

jljoyd4f2#

我不得不重写我的swagger配置,它看起来像这样:

private static final String OAUTH_SCHEME = "auth";

@Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
String authURL;

@Bean
public OpenAPI customizeOpenAPI() {
    return new OpenAPI()
            .addSecurityItem(new SecurityRequirement()
                    .addList(OAUTH_SCHEME))
            .components(new Components()
                    .addSecuritySchemes(OAUTH_SCHEME, createOAuthScheme()))
            .addSecurityItem(new SecurityRequirement().addList(OAUTH_SCHEME));
}

private SecurityScheme createOAuthScheme() {
    return new SecurityScheme().type(SecurityScheme.Type.OAUTH2).flows(createOAuthFlows());
}

private OAuthFlows createOAuthFlows() {
    final var oauthFlow = new OAuthFlow()
            .authorizationUrl(authURL + "/protocol/openid-connect" + "/auth")
            .refreshUrl(authURL + "/protocol/openid-connect" + "/token")
            .tokenUrl(authURL + "/protocol/openid-connect" + "/token")
            .scopes(new Scopes());
    return new OAuthFlows().authorizationCode(oauthFlow);
}

我还必须在我的application.yml中添加一些配置

server:
  port: 8081

keycloak:
  username: test-client
  password: *****
  realm: test-realm
  uri: http://localhost:8080
  auth-server-url: ${keycloak.uri}/realms/${keycloak.realm}/protocol/openid-connect

springdoc:
  swagger-ui:
    oauth:
      clientId: ${keycloak.username}
      clientSecret: ${keycloak.password}

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: ${keycloak.uri}/realms/${keycloak.realm}
          jwk-set-uri: ${spring.security.oauth2.resourceserver.jwt.issuer-uri}/protocol/openid-connect/certs
      client:
        registration:
          keycloak:
            authorization-grant-type: client_credentials
            scope: openid
        provider:
          keycloak:
            issuer-uri: ${keycloak.uri}/realms/${keycloak.realm}

jwt:
  auth:
    converter:
      resource-id: ${keycloak.username}
      principle_attribute: preferred_username

它现在工作,通过重定向到keycloak日志页面,我也预填充客户端ID和属性中的秘密,使其更容易。

相关问题