首页 文章

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

提问于
浏览
4

我设置了一个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);
}
}

现在我尝试访问服务器:

  • 获取访问令牌(通过REST客户端)解码的JWT如下所示:

{
“jti”:“78c00562-d80a-4f5a-ab08-61ed10cb575c”,
“exp”:1509603570,
“nbf”:0,
“iat”:1509603270,
“iss”:“http:// localhost:8280 / auth / realms / demo-realm”,
“aud”:“演示客户”,
“sub”:“6ee90ba4-2854-49c1-9776-9aa95b6ae598”,
“典型”:“持票人”,
“azp”:“演示客户端”,
“auth_time”:0,
“session_state”:“68ce12fb-3b3f-429d-9390-0662f0503bbb”,
“acr”:“1”,
“client_session”:“ec0113e1-022a-482a-a26b-e5701e5edec1”,
“允许起源”:[],
“realm_access”:{
“角色”:[
“demo_user_role”
“uma_authorization”
]
},
“resource_access”:{
“帐户”:{
“角色”:[
“管理帐户”,
“管理帐户链接”,
“查看资料”
]
}
},
“名字”:“Jim Panse”,
“preferred_username”:“演示用户”,
“given_name”:“吉姆”,
“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:以前经过身份验证:
org.springframework.security.oauth2.provider.OAuth2Authentication@1f3ee7e1:校长:demo-client;证书:[保护];认证:真实;详细信息:remoteAddress = 127.0.0.1,tokenType = BearertokenValue =;未授予任何权限2017-11-02 07:18:05.348 DEBUG 17637 --- [nio-8080-exec-1] ossaccess.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] osswaExceptionTranslationFilter:访问被拒绝(用户不是匿名的);委托给AccessDeniedHandler org.springframework.security.access.AccessDeniedException:访问被拒绝

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

所以我的问题:

什么是使Spring Security与Keycloak访问令牌一起工作的必要条件?

2 回答

  • 4

    通过keycloak管理控制台,您可以为客户端"demo-client"创建类型为 User Realm Role 的令牌映射器,其声明名称为"authorities" . 然后访问令牌包含此属性中的角色名称,并且不需要自定义DefaultAccessTokenConverter .

  • 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());
        }
    }
    

相关问题