簡體   English   中英

Spring 安全性:將 OAuth2 聲明與角色映射到保護資源服務器端點

[英]Spring Security: mapping OAuth2 claims with roles to secure Resource Server endpoints

我正在使用 Spring 啟動設置資源服務器,並保護我使用由 Spring Security 提供的 OAuth2 的端點。 所以我使用 Spring Boot 2.1.8.RELEASE例如使用 Spring Security 5.1.6.RELEASE

作為授權服務器,我正在使用 Keycloak。 資源服務器中身份驗證、頒發訪問令牌和驗證令牌之間的所有過程都正常工作。 下面是一個發行和解碼的令牌的例子(有些部分被剪掉了):

{
  "jti": "5df54cac-8b06-4d36-b642-186bbd647fbf",
  "exp": 1570048999,
  "aud": [
    "myservice",
    "account"
  ],
  "azp": "myservice",
  "realm_access": {
    "roles": [
      "offline_access",
      "uma_authorization"
    ]
  },
  "resource_access": {
    "myservice": {
      "roles": [
        "ROLE_user",
        "ROLE_admin"
      ]
    },
    "account": {
      "roles": [
        "manage-account",
        "manage-account-links",
        "view-profile"
      ]
    }
  },
  "scope": "openid email offline_access microprofile-jwt profile address phone",
}

如何配置 Spring 安全性以使用訪問令牌中的信息為不同端點提供條件授權?

最終我想像這樣寫一個 controller :

@RestController
public class Controller {

    @Secured("ROLE_user")
    @GetMapping("userinfo")
    public String userinfo() {
        return "not too sensitive action";
    }

    @Secured("ROLE_admin")
    @GetMapping("administration")
    public String administration() {
        return "TOOOO sensitive action";
    }
}

在搞砸了一點之后,我找到了一個實現自定義jwtAuthenticationConverter的解決方案,它能夠將 append 資源特定角色分配給權限集合。

    http.oauth2ResourceServer()
                .jwt()
                .jwtAuthenticationConverter(new JwtAuthenticationConverter()
                {
                    @Override
                    protected Collection<GrantedAuthority> extractAuthorities(final Jwt jwt)
                    {
                        Collection<GrantedAuthority> authorities = super.extractAuthorities(jwt);
                        Map<String, Object> resourceAccess = jwt.getClaim("resource_access");
                        Map<String, Object> resource = null;
                        Collection<String> resourceRoles = null;
                        if (resourceAccess != null &&
                            (resource = (Map<String, Object>) resourceAccess.get("my-resource-id")) !=
                            null && (resourceRoles = (Collection<String>) resource.get("roles")) != null)
                            authorities.addAll(resourceRoles.stream()
                                                            .map(x -> new SimpleGrantedAuthority("ROLE_" + x))
                                                            .collect(Collectors.toSet()));
                        return authorities;
                    }
                });

其中my-resource-id既是顯示在resource_access聲明中的資源標識符,也是與ResourceServerSecurityConfigurer中的 API 關聯的值。

請注意, extractAuthorities實際上已被棄用,因此更面向未來的解決方案應該是實現一個成熟的轉換器

    import org.springframework.core.convert.converter.Converter;
    import org.springframework.security.authentication.AbstractAuthenticationToken;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    import org.springframework.security.oauth2.jwt.Jwt;
    import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
    import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;

    import java.util.Collection;
    import java.util.Collections;
    import java.util.Map;
    import java.util.stream.Collectors;
    import java.util.stream.Stream;

    public class CustomJwtAuthenticationConverter implements Converter<Jwt, AbstractAuthenticationToken>
    {
        private static Collection<? extends GrantedAuthority> extractResourceRoles(final Jwt jwt, final String resourceId)
        {
            Map<String, Object> resourceAccess = jwt.getClaim("resource_access");
            Map<String, Object> resource;
            Collection<String> resourceRoles;
            if (resourceAccess != null && (resource = (Map<String, Object>) resourceAccess.get(resourceId)) != null &&
                (resourceRoles = (Collection<String>) resource.get("roles")) != null)
                return resourceRoles.stream()
                                    .map(x -> new SimpleGrantedAuthority("ROLE_" + x))
                                    .collect(Collectors.toSet());
            return Collections.emptySet();
        }

        private final JwtGrantedAuthoritiesConverter defaultGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();

        private final String resourceId;

        public CustomJwtAuthenticationConverter(String resourceId)
        {
            this.resourceId = resourceId;
        }

        @Override
        public AbstractAuthenticationToken convert(final Jwt source)
        {
            Collection<GrantedAuthority> authorities = Stream.concat(defaultGrantedAuthoritiesConverter.convert(source)
                                                                                                       .stream(),
                                                                     extractResourceRoles(source, resourceId).stream())
                                                             .collect(Collectors.toSet());
            return new JwtAuthenticationToken(source, authorities);
        }
    }

我已經使用 Spring Boot 2.1.9.RELEASE、Spring Security 5.2.0.RELEASE 和官方 Keycloak 7.0.0 ZC5FD214CDD0D2B3B3B4272E73B022 圖像測試了這兩種解決方案。

一般來說,我認為無論實際的授權服務器(即 IdentityServer4、Keycloak ...)如何,這似乎都是將聲明轉換為 Spring 安全授權的合適位置。

這是另一個解決方案

    private JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
        jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName("roles");
        jwtGrantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
        return jwtAuthenticationConverter;
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .oauth2ResourceServer().jwt()
                .jwtAuthenticationConverter(jwtAuthenticationConverter());
    }

您遇到的困難部分是由於您的角色位於 resource_server->client_id 下的 JWT 中。 然后,這需要一個自定義令牌轉換器來提取它們。

您可以將 keycloak 配置為使用客戶端映射器,該映射器將在頂級聲明名稱下顯示角色,例如“角色”。 這使得 Spring 安全配置更簡單,因為您只需要設置了 authorityClaimName 的 JwtGrantedAuthoritiesConverter,如@hillel_guy 采用的方法所示。

keycloak 客戶端映射器將配置如下:

在此處輸入圖像描述

正如@hillel_guy 的回答已經提到的那樣,使用AbstractHttpConfigurer應該是 go 的方法。 這對我來說與 spring-boot 2.3.4 和 spring-security 5.3.4 無縫協作。 請參閱 spring-security API 文檔以供參考: OAuth2ResourceServerConfigurer

更新

完整示例,如評論中所述:

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private static final String JWT_ROLE_NAME = "roles";
    private static final String ROLE_PREFIX = "ROLES_";

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests().anyRequest().authenticated()
                .and().csrf().disable()
                .cors()
                .and().oauth2ResourceServer().jwt()
                .jwtAuthenticationConverter(jwtAuthenticationConverter());
    }

    private JwtAuthenticationConverter jwtAuthenticationConverter() {
        // create a custom JWT converter to map the roles from the token as granted authorities
        JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
        jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName(JWT_ROLE_NAME); // default is: scope, scp
        jwtGrantedAuthoritiesConverter.setAuthorityPrefix(ROLE_PREFIX ); // default is: SCOPE_

        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
        return jwtAuthenticationConverter;
    }
}

就我而言,我想從 JWT 而不是scope中的 map roles
希望這可以幫助。

如果您使用的是 Azure AD Oath,現在有一個更簡單的方法:

        http 
        .cors()
        .and()
        .authorizeRequests()
        .anyRequest()
        .authenticated() 
        .and()
        .oauth2ResourceServer()
        .jwt()
        .jwtAuthenticationConverter(new AADJwtBearerTokenAuthenticationConverter("roles", "ROLE_")); 

ADDJwtBearerTokenAuthenticationConverter 允許您將聲明名稱添加為第一個參數,並將您希望角色前綴的內容添加為第二個參數。

我的導入,因此您可以找到該庫:

import com.azure.spring.aad.webapi.AADJwtBearerTokenAuthenticationConverter;

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM