当前位置: 首页 > 知识库问答 >
问题:

在Spring Boot中扩展KeyCloak令牌

楚硕
2023-03-14

我正在使用KeyClope来保护我的Spring boot后端。

依赖项:

<dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-spring-boot-2-adapter</artifactId>
            <version>12.0.3</version>
        </dependency>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-tomcat7-adapter-dist</artifactId>
            <version>12.0.3</version>
            <type>pom</type>
        </dependency>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-spring-security-adapter</artifactId>
            <version>12.0.3</version>
        </dependency>

安全配置:

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        super.configure(http);
        ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry expressionInterceptUrlRegistry = http.cors()
                .and()
                .csrf().disable()                
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) 
                .and() 
                .authorizeRequests();

        expressionInterceptUrlRegistry = expressionInterceptUrlRegistry.antMatchers("/iam/accounts/promoters*").hasRole("PROMOTER");
        expressionInterceptUrlRegistry.anyRequest().permitAll();
    }

一切工作正常!

但是现在我在keydove令牌“roles”中添加了一个新的部分,我需要在我的Spring Boot中以某种方式扩展keydove jwt类,并编写一些代码来解析角色信息并将其存储到SecurityContext。你能告诉我如何存档这个目标吗?

共有2个答案

上官霄
2023-03-14

首先,扩展keyCloak AccessToken:

@Data
static class CustomKeycloakAccessToken extends AccessToken {

    @JsonProperty("roles")
    protected Set<String> roles;

}

然后:

@KeycloakConfiguration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class KeycloakSecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
    @Override
    protected KeycloakAuthenticationProvider keycloakAuthenticationProvider() {
        return new KeycloakAuthenticationProvider() {

            @Override
            public Authentication authenticate(Authentication authentication) throws AuthenticationException {
                KeycloakAuthenticationToken token = (KeycloakAuthenticationToken) authentication;
                List<GrantedAuthority> grantedAuthorities = new ArrayList<>();

                for (String role : ((CustomKeycloakAccessToken)((KeycloakPrincipal)token.getPrincipal()).getKeycloakSecurityContext().getToken()).getRoles()) {
                    grantedAuthorities.add(new KeycloakRole(role));
                }

                return new KeycloakAuthenticationToken(token.getAccount(), token.isInteractive(), new SimpleAuthorityMapper().mapAuthorities(grantedAuthorities));
            }

        };
    }

    /**
     * Use NullAuthenticatedSessionStrategy for bearer-only tokens. Otherwise, use
     * RegisterSessionAuthenticationStrategy.
     */
    @Bean
    @Override
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new NullAuthenticatedSessionStrategy();
    }

    @Override
    protected KeycloakAuthenticationProcessingFilter keycloakAuthenticationProcessingFilter() throws Exception {
        KeycloakAuthenticationProcessingFilter filter = new KeycloakAuthenticationProcessingFilter(authenticationManagerBean());
        filter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy());
        filter.setRequestAuthenticatorFactory(new SpringSecurityRequestAuthenticatorFactory() {

            @Override
            public RequestAuthenticator createRequestAuthenticator(HttpFacade facade,
                                                                   HttpServletRequest request, KeycloakDeployment deployment, AdapterTokenStore tokenStore, int sslRedirectPort) {
                return new SpringSecurityRequestAuthenticator(facade, request, deployment, tokenStore, sslRedirectPort) {

                    @Override
                    protected BearerTokenRequestAuthenticator createBearerTokenAuthenticator() {
                        return new BearerTokenRequestAuthenticator(deployment) {

                            @Override
                            protected AuthOutcome authenticateToken(HttpFacade exchange, String tokenString) {
                                log.debug("Verifying access_token");
                                if (log.isTraceEnabled()) {
                                    try {
                                        JWSInput jwsInput = new JWSInput(tokenString);
                                        String wireString = jwsInput.getWireString();
                                        log.tracef("\taccess_token: %s", wireString.substring(0, wireString.lastIndexOf(".")) + ".signature");
                                    } catch (JWSInputException e) {
                                        log.errorf(e, "Failed to parse access_token: %s", tokenString);
                                    }
                                }
                                try {
                                    TokenVerifier<CustomKeycloakAccessToken> tokenVerifier = AdapterTokenVerifier.createVerifier(tokenString, deployment, true, CustomKeycloakAccessToken.class);

                                    // Verify audience of bearer-token
                                    if (deployment.isVerifyTokenAudience()) {
                                        tokenVerifier.audience(deployment.getResourceName());
                                    }
                                    token = tokenVerifier.verify().getToken();
                                } catch (VerificationException e) {
                                    log.debug("Failed to verify token");
                                    challenge = challengeResponse(exchange, OIDCAuthenticationError.Reason.INVALID_TOKEN, "invalid_token", e.getMessage());
                                    return AuthOutcome.FAILED;
                                }
                                if (token.getIssuedAt() < deployment.getNotBefore()) {
                                    log.debug("Stale token");
                                    challenge = challengeResponse(exchange,  OIDCAuthenticationError.Reason.STALE_TOKEN, "invalid_token", "Stale token");
                                    return AuthOutcome.FAILED;
                                }
                                boolean verifyCaller;
                                if (deployment.isUseResourceRoleMappings()) {
                                    verifyCaller = token.isVerifyCaller(deployment.getResourceName());
                                } else {
                                    verifyCaller = token.isVerifyCaller();
                                }
                                surrogate = null;
                                if (verifyCaller) {
                                    if (token.getTrustedCertificates() == null || token.getTrustedCertificates().isEmpty()) {
                                        log.warn("No trusted certificates in token");
                                        challenge = clientCertChallenge();
                                        return AuthOutcome.FAILED;
                                    }

                                    // for now, we just make sure Undertow did two-way SSL
                                    // assume JBoss Web verifies the client cert
                                    X509Certificate[] chain = new X509Certificate[0];
                                    try {
                                        chain = exchange.getCertificateChain();
                                    } catch (Exception ignore) {

                                    }
                                    if (chain == null || chain.length == 0) {
                                        log.warn("No certificates provided by undertow to verify the caller");
                                        challenge = clientCertChallenge();
                                        return AuthOutcome.FAILED;
                                    }
                                    surrogate = chain[0].getSubjectDN().getName();
                                }
                                log.debug("successful authorized");
                                return AuthOutcome.AUTHENTICATED;
                            }

                        };
                    }
                };
            }
        });
        return filter;
    }

}
翟修明
2023-03-14

我不明白为什么你需要扩展KeyCloak令牌。已经存在的角色在KeyCloak Token中。我将尝试解释如何访问它,关键斗篷有两个级别的角色,1)领域级别和2)应用程序(客户端)级别,默认情况下,您的关键斗篷适配器使用领域级别,要使用应用程序级别,您需要设置属性keycloak.use-资源-角色映射与true在您的application.yml

如何在领域中创建角色,请在此处输入图像描述

如何在客户端中创建角色在此处输入图像描述

具有ADMIN(领域)和ADD_USER(应用程序)角色的用户在此输入图像描述

要拥有访问角色,您可以在KeyClope适配器中使用KeyClope AuthenticationToken类,您可以尝试调用以下方法:

...
public ResponseEntity<Object> getUsers(final KeycloakAuthenticationToken authenticationToken)  {
    final AccessToken token = authenticationToken.getAccount().getKeycloakSecurityContext().getToken();
    final Set<String> roles = token.getRealmAccess().getRoles();
    final Map<String, AccessToken.Access> resourceAccess = token.getResourceAccess();
...
}
...

要保护任何使用Spring Security的路由器,您可以使用此注释,

@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/users")
public ResponseEntity<Object> getUsers(final KeycloakAuthenticationToken token)  {
   return ResponseEntity.ok(service.getUsers());
}

奥林匹克广播公司

如果您想在代币中添加任何自定义声明,请让我知道我可以更好地解释。

我在这里介绍了如何设置KeyClope适配器以及

安全Config.java

...
@KeycloakConfiguration
@EnableGlobalMethodSecurity(prePostEnabled = true)
class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {

    @Value("${project.cors.allowed-origins}")
    private String origins = "";

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) {
        KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
        keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
        auth.authenticationProvider(keycloakAuthenticationProvider);
    }

    @Bean
    public KeycloakSpringBootConfigResolver keycloakConfigResolver() {
        return new KeycloakSpringBootConfigResolver();
    }

    @Bean
    @Override
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
        return new NullAuthenticatedSessionStrategy();
    }

    @Override
    protected KeycloakAuthenticationProcessingFilter keycloakAuthenticationProcessingFilter() throws Exception {
        KeycloakAuthenticationProcessingFilter filter = new KeycloakAuthenticationProcessingFilter(this.authenticationManagerBean());
        filter.setSessionAuthenticationStrategy(this.sessionAuthenticationStrategy());
        filter.setAuthenticationFailureHandler((request, response, exception) -> {
            response.addHeader("Access-Control-Allow-Origin", origins);
            if (!response.isCommitted()) {
                response.sendError(401, "Unable to authenticate using the Authorization header");
            } else if (200 <= response.getStatus() && response.getStatus() < 300) {
                throw new RuntimeException("Success response was committed while authentication failed!", exception);
            }
        });
        return filter;
    }

    @Override
    protected void configure(final HttpSecurity http) throws Exception {
        super.configure(http);
        http.csrf()
                .disable()
                .authorizeRequests()
                .antMatchers(HttpMethod.OPTIONS, "**").permitAll()
                .antMatchers("/s/**").authenticated()
                .anyRequest().permitAll();

    }
}

应用yml

..
keycloak: 
    enabled: true 
    auth-server-url: http://localhost:8080/auth 
    resource: myclient 
    realm: myrealm 
    bearer-only: true 
    principal-attribute: preferred_username 
    use-resource-role-mappings: true
..
 类似资料:
  • 我开始在一个现有的纯JS自制chrome扩展上实现keycloak SSO身份验证。Keycloak服务器配置良好,我已经检查过了。我可以在background.js上导入keycloak.json。我可以初始化keycloak对象:在这里输入图像描述 但我对登录有点迷茫。单击登录按钮应该调用keycloak.login函数。简单的${url_extension_bckg}?action=logi

  • 主要内容:org.springframework.context.ApplicationContextInitializer,org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor,org.springframework.beans.factory.config.BeanFactoryPostProcessor,,,,,,,,,,,,1.可扩展的接口启动调用顺序图 ApplicationConte

  • 主要内容:org.springframework.context.ApplicationContextInitializer,org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor,org.springframework.beans.factory.config.BeanFactoryPostProcessor,,,,,,,,,,,,1.可扩展的接口启动调用顺序图 ApplicationConte

  • 扩展说明 所有服务器均支持 telnet 访问,用于人工干预。 扩展接口 org.apache.dubbo.remoting.telnet.TelnetHandler 扩展配置 <dubbo:protocol telnet="xxx,yyy" /> <!-- 缺省值设置,当<dubbo:protocol>没有配置telnet属性时,使用此配置 --> <dubbo:provider telnet=

  • Szenario:我有两个扩展,它们用一些特定字段扩展了。在TYPO3 9之前,我必须使用以下打字脚本配置对新闻扩展的依赖关系进行配置: 模型扩展了基本扩展的模型: 在TYPO3 10中,在(中断:#87623): 只要您只有一个扩展新闻扩展名的扩展名,它就可以工作多久。如果您有第二个扩展并启用TYPO3缓存,您将得到一个错误,即在第一个扩展中添加的字段在新闻扩展的模板中不可用。奇怪的是,这个问题

  • 问题内容: 我想对第3方指令(特别是Angular UI Bootstrap)进行较小的修改。我只是想添加指令的范围: 但是我也想让Bower与Angular-Bootstrap保持同步。运行后,我将覆盖所做的更改。 那么,如何与该Bower组件分开扩展该指令? 问题答案: 解决此问题的最简单方法可能是在您的应用上创建与第三方指令同名的指令。这两个指令都将运行,并且您可以使用属性指定运行顺序(优先