如何将Spring Security RemoteTokenService与Keycloak一起使用

时间:2017-11-02 06:58:33

标签: spring-security keycloak

我设置了一个Keycloak服务器。配置领域和客户端等。 我成功地编写了一个带有“org.keycloak:keycloak-spring-boot-starter”的Spring Boot服务并保护了我的RestController。像魅力一样。

但是当我尝试使用Spring Security(没有keycloak特定的依赖项)时,我被卡住了。

这是我的傻瓜:

dependencies {
compile('org.springframework.boot:spring-boot-starter-security')
compile('org.springframework.security.oauth:spring-security-oauth2')

compile('org.springframework.boot:spring-boot-starter-web')
compileOnly('org.projectlombok:lombok')
testCompile('org.springframework.boot:spring-boot-starter-test')
testCompile('org.springframework.security:spring-security-test')

}

这是我的SecurityConfig:

@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends 
ResourceServerConfigurerAdapter {

@Override
public void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
        .antMatchers("/person/**").hasRole("DEMO_SPRING_SECURITY")
        .anyRequest().authenticated()
        .and().formLogin().disable();
}

@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {

    resources.resourceId("demo-client");
    RemoteTokenServices tokenServices = new RemoteTokenServices();
    tokenServices.setCheckTokenEndpointUrl(
        "http://localhost:8280/auth/realms/demo-realm/protocol/openid-connect/token/introspect");
    tokenServices.setClientId("demo-client");
    tokenServices.setClientSecret("80e19056-7770-4a4a-a3c4-06d8ac8792ef");
    resources.tokenServices(tokenServices);
}
}

现在我尝试访问服务器:

  1. 获取访问令牌(通过REST客户端) 解码后的JWT看起来像这样:
  2. {
    "jti": "78c00562-d80a-4f5a-ab08-61ed10cb575c",
    "exp": 1509603570,
    "nbf": 0,
    "iat": 1509603270,
    "iss": "http://localhost:8280/auth/realms/demo-realm",
    "aud": "demo-client",
    "sub": "6ee90ba4-2854-49c1-9776-9aa95b6ae598",
    "typ": "Bearer",
    "azp": "demo-client",
    "auth_time": 0,
    "session_state": "68ce12fb-3b3f-429d-9390-0662f0503bbb",
    "acr": "1",
    "client_session": "ec0113e1-022a-482a-a26b-e5701e5edec1",
    "allowed-origins": [],
    "realm_access": {
      "roles": [
        "demo_user_role",
        "uma_authorization"
      ]
    },
    "resource_access": {
      "account": {
        "roles": [
          "manage-account",
          "manage-account-links",
          "view-profile"
        ]
      }
    },
    "name": "Jim Panse",
    "preferred_username": "demo-user",
    "given_name": "Jim",
    "family_name": "Panse",
    "email": "user@dmoain.com"
    }
    

    但是我得到了一个AccessDeniedException。

    2017-11-02 07:18:05.344 DEBUG 17637 --- [nio-8080-exec-1] o.s.s.w.a.i.FilterSecurityInterceptor    : Previously Authenticated:
    
         

    org.springframework.security.oauth2.provider.OAuth2Authentication@1f3ee7e1:   校长:演示客户;证书:[保护];认证:真实;   详细信息:remoteAddress = 127.0.0.1,tokenType = BearertokenValue =;   没有授予任何权力机构2017-11-02 07:18:05.348 DEBUG 17637 ---   [nio-8080-exec-1] o.s.s.access.vote.AffirmativeBased:Voter:   org.springframework.security.web.access.expression.WebExpressionVoter@14032696,   返回:-1 2017-11-02 07:18:05.353 DEBUG 17637 --- [nio-8080-exec-1]   o.s.s.w.a.ExceptionTranslationFilter:访问被拒绝(用户是   不是匿名的);委托给AccessDeniedHandler

         

    org.springframework.security.access.AccessDeniedException:访问是   拒绝

    我对RemoteTokenService进行了调试,发现Keycloak以完全相同的accessstoken响应。哪个好。 但是DefaultAccessTokenConverter尝试从字段authorities中读取不存在的用户角色。并且OAuth2WebSecurityExpressionHandler评估用户doenst是否具有任何角色。 - >访问被拒绝

    所以我的问题:

    使用Keycloak访问令牌使Spring Security工作有什么必要?

2 个答案:

答案 0 :(得分:4)

通过keycloak管理控制台,您可以为客户端“demo-client”创建类型为用户领域角色的标记映射器,其声明名称为“authority”。 然后访问令牌包含此属性中的角色名称,并且不需要自定义 DefaultAccessTokenConverter

答案 1 :(得分:3)

我在这里提出这个问题之后就找到了解决方案。有时尝试表达问题会有所帮助。

解决方案是覆盖DefaultAccessTokenConverter以教他如何阅读“realm_access”字段。它的丑陋却很有效:

@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {

    resources.resourceId("demo-client");
    RemoteTokenServices tokenServices = new RemoteTokenServices();
    tokenServices.setCheckTokenEndpointUrl(
        "http://localhost:8280/auth/realms/demo-realm/protocol/openid-connect/token/introspect");
    tokenServices.setClientId("demo-client");
    tokenServices.setClientSecret("80e19056-7770-4a4a-a3c4-06d8ac8792ef");
    tokenServices.setAccessTokenConverter(new KeycloakAccessTokenConverter());
    resources.tokenServices(tokenServices);

}
private class KeycloakAccessTokenConverter extends DefaultAccessTokenConverter {

    @Override
    public OAuth2Authentication extractAuthentication(Map<String, ?> map) {
        OAuth2Authentication oAuth2Authentication = super.extractAuthentication(map);
        Collection<GrantedAuthority> authorities = (Collection<GrantedAuthority>) oAuth2Authentication.getOAuth2Request().getAuthorities();
        if (map.containsKey("realm_access")) {
            Map<String, Object> realm_access = (Map<String, Object>) map.get("realm_access");
            if(realm_access.containsKey("roles")) {
                ((Collection<String>) realm_access.get("roles")).forEach(r -> authorities.add(new SimpleGrantedAuthority(r)));
            }
        }
        return new OAuth2Authentication(oAuth2Authentication.getOAuth2Request(),oAuth2Authentication.getUserAuthentication());
    }
}