基于角色的 Spring Boot Keycloak 不起作用,这些角色在 keycloak 中分配给用户

sky*_*yho 7 spring-boot keycloak

我已经配置了Keycloak的使用,而不使用弹簧适配器。因为它已被弃用。我在Keycloak的控制台中创建了:一个REALM,一个用户,并为用户添加角色。

在此输入图像描述

在此输入图像描述

  • user 然后我创建了一个用户并向他添加了我之前在此处创建的角色。

在此输入图像描述

在此输入图像描述

  • 泊坞窗
....
  keycloak:
    container_name: blog
    depends_on:
      keycloakdb:
        condition: service_healthy
    environment:
      DB_DATABASE: ${POSTGRESQL_DB}
      DB_USER: ${POSTGRESQL_USER}
      DB_PASSWORD: ${POSTGRESQL_PASS}
      KEYCLOAK_USER: ${KEYCLOAK_USER}
      KEYCLOAK_PASSWORD: ${KEYCLOAK_PASSWORD}
      DB_VENDOR: ${DB_VENDOR}
      DB_ADDR: ${DB_ADDR}
      DEBUG_PORT: ${DEBUG_PORT}
      DB_PORT: ${DB_PORT}
      TZ: ${TZ}
      DEBUG: ${DEBUG}
    image: jboss/keycloak:latest
.....
Run Code Online (Sandbox Code Playgroud)

这是应用程序配置

  • pom.xml
  <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.0.0</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.guide</groupId>
    <artifactId>keycloak-postgres-quick-guide</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>keycloak-postgres-quick-guide</name>
    <description>keycloak-postgres-quick-guide</description>
    <properties>
        <java.version>17</java.version>
        <testcontainers.version>1.17.6</testcontainers.version>
        <snakeyaml.version>1.33</snakeyaml.version>
        <keycloak.version>20.0.2</keycloak.version>
    </properties>
    <dependencies>
....
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
...
  
    </dependencies>
    <dependencyManagement>
        <dependencies>

            <dependency>
                <groupId>org.keycloak</groupId>
                <artifactId>keycloak-spring-boot-starter</artifactId>
                <version>${keycloak.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

        </dependencies>
    </dependencyManagement>
Run Code Online (Sandbox Code Playgroud)
  • 安全
@Configuration
@EnableWebSecurity(debug = true)
@EnableMethodSecurity
@RequiredArgsConstructor
public class SecurityConfig {

    private final KeycloakLogoutHandler keycloakLogoutHandler;

    @Bean
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests()
                .requestMatchers("/customers*", "/users*")
                .hasRole("READ")
                .anyRequest()
                .permitAll();
        http.oauth2Login()
                .and()
                .logout()
                .addLogoutHandler(keycloakLogoutHandler)
                .logoutSuccessUrl("/");
        return http.build();
    }
}
Run Code Online (Sandbox Code Playgroud)
  • 控制器
    @GetMapping(path = "/customers")
    public String customers(Principal principal, Model model) {

        addCustomers();

        Iterable<Customer> customers = customerRepository.findAll();
        model.addAttribute("customers", customers);
        model.addAttribute("username", principal.getName());

        return "customers";
    }
Run Code Online (Sandbox Code Playgroud)
  • 应用程序.yml
spring:

  security:
    oauth2:
      client:
        provider:
          keycloak:
            issuer-uri: http://localhost:28080/auth/realms/SpringBootKeycloak
            user-name-attribute: preferred_username
        registration:
          keycloak:
            authorization-grant-type: authorization_code
            client-id: loggin-app
            scope: openid
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述 在此输入图像描述

但是,当尝试访问受保护的资源时,我收到错误:

发送OAuth2AuthenticationToken [主体=名称:[user-spring-app],授予权限:[[OIDC_USER,SCOPE_email,SCOPE_openid,SCOPE_profile]],用户属性:[{at_hash=yGjLEXdKSgOC3J8_QfLyrw,sub=26e4c1c7-cb02-4628-bae9-7a370b53c06 7 ,email_verified=false,iss=http://localhost:28080/auth/realms/SpringBootKeycloak,typ=ID,preferred_username=user-spring-app,nonce=yzqik3W-aQtCjP6nQGt-N2CbnyI7O7smrt6mLOZNXY8,sid=07dbaa6a-826c-4139-a956 -62a477f6eefe,aud=[loggin-app],acr=1,azp=loggin-app,auth_time=2022-12-21T08:22:10Z,exp=2022-12-21T08:27:10Z,session_state=07dbaa6a-826c -4139-a956-62a477f6eefe,iat = 2022-12-21T08:22:10Z,jti = 93f19918-bc4a-4435-bbcd-578f7ca2ed36}],凭据= [受保护],经过身份验证= true,详细信息= WebAuthenticationDetails [RemoteIpAddress = 127.0 .0.1,SessionId = A0B52BA34017AD4C0183CA1DD48420B8],授予权限= [OIDC_USER,SCOPE_email,SCOPE_openid,SCOPE_profile]]访问被拒绝的处理程序,因为访问被拒绝org.springframework.security.access.AccessDeniedException:访问被拒绝

我没有观察分配给用户的角色

主体=名称:[user-spring-app],授予权限:[[OIDC_USER,SCOPE_email,SCOPE_openid,SCOPE_profile]],

我试图了解keycloak的工作原理,但到目前为止只有零碎的信息。我发现的示例已过时(spring-boot- Keycloak-适配器现已弃用)。此外,我看到了不同的方法,有人使用hasAuthority(),有人使用hasRole(),但是,我不明白它与Keycloak 的工作原理。

在此输入图像描述 在此输入图像描述

在此输入图像描述 有谁知道如何解决这个问题并解释此类工作的原理?

sn1*_*87a 8

我认为你必须:

  1. 配置 keycloak 将角色放入 id 令牌中。
  2. 定义一个映射器来将 id 令牌内的角色映射到 spring security 角色。

我们开始做吧:

  1. 配置keycloak:我的版本是20.x,我的客户端是acme

转到您的 keycloak 领域 -> 客户端 -> 打开您的客户端 -> 选项卡“客户端范围” -> 在表中单击“acme-dedicated”

在此输入图像描述

添加映射器 -> 领域角色

在此输入图像描述

取消选中并选中“添加到 ID 令牌”:该复选框可能默认处于选中状态,但并未激活,我们必须手动取消选中,然后再次选中。完成后,单击“保存”

在此输入图像描述

您可以转到“客户端范围” -> “评估”并验证该角色是否存在于 ID 令牌中。

  1. 好的,现在让我们配置 spring boot 将 keycloak 角色映射到 spring security 角色。

为此,请将以下代码添加到您的 SecurityConfig 类中:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    private static final String REALM_ACCESS_CLAIM = "realm_access";
    private static final String ROLES_CLAIM = "roles";

    @Bean
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
    }

    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.oauth2Login(Customizer.withDefaults());
        ...
        return http.build();
    }

    @Bean
    @SuppressWarnings("unchecked")
    public GrantedAuthoritiesMapper userAuthoritiesMapperForKeycloak() {
        return authorities -> {
            Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
            var authority = authorities.iterator().next();
            boolean isOidc = authority instanceof OidcUserAuthority;

            if (isOidc) {
                var oidcUserAuthority = (OidcUserAuthority) authority;
                var userInfo = oidcUserAuthority.getUserInfo();

                if (userInfo.hasClaim(REALM_ACCESS_CLAIM)) {
                    var realmAccess = userInfo.getClaimAsMap(REALM_ACCESS_CLAIM);
                    var roles = (Collection<String>) realmAccess.get(ROLES_CLAIM);
                mappedAuthorities.addAll(generateAuthoritiesFromClaim(roles));
                }
            } else {
                var oauth2UserAuthority = (OAuth2UserAuthority) authority;
                Map<String, Object> userAttributes = oauth2UserAuthority.getAttributes();

                if (userAttributes.containsKey(REALM_ACCESS_CLAIM)) {
                    var realmAccess = (Map<String, Object>) userAttributes.get(REALM_ACCESS_CLAIM);
                    var roles = (Collection<String>) realmAccess.get(ROLES_CLAIM);
                    mappedAuthorities.addAll(generateAuthoritiesFromClaim(roles));
                }
            }
            return mappedAuthorities;
        };
    }

    Collection<GrantedAuthority> generateAuthoritiesFromClaim(Collection<String> roles) {
        return roles.stream().map(role -> new SimpleGrantedAuthority("ROLE_" + role)).collect(Collectors.toList());
        }
    }
Run Code Online (Sandbox Code Playgroud)

仅供参考:我不使用 keycloak java 适配器,因为它很快就会被弃用。我使用带有 spring security 的 oauth2-client 。这是我的 pom.xml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
Run Code Online (Sandbox Code Playgroud)

祝你好运!