Springboot中结合Keycloak和Spring security 开启细粒度权限控制

Springboot中结合Keycloak和Spring security 开启细粒度权限控制

背景说明

Keycloak作为开源轻量级的统一账号管理系统,可以帮助我们快速搭建一个完整,安全的支持单点登录,开放平台,鉴权及授权的集中式账号管理系统。

加上Keycloak的Authorization Services,提供给我们灵活强大的细粒度权限控制,而不再局限在角色访问控制。

这篇文章重点在于,如何在springboot中集成spring security,以及keycloak,以及配置相关访问权限配置。

而 Keycloak服务的搭建,领域的配置,Authorization Services的配置则不在这里涉及。

引入依赖:Spring Security Adapter

Keycloak官方文档,提供了 Keycloak Java适配器的相关配置说明,集成Springboot说明,以及 Spring security中的集成,这篇文章主要参考
https://www.keycloak.org/docs/latest/securing_apps/index.html#_spring_security_adapter
可以作为官方文档的一个实践。

<dependency>
    <groupId>org.keycloak</groupId>
    <artifactId>keycloak-spring-security-adapter</artifactId>
    <version>12.0.1</version>
</dependency>

<dependency>
     <groupId>org.keycloak</groupId>
     <artifactId>keycloak-authz-client</artifactId>
     <version>12.0.1</version>
 </dependency>

结合spring security则必须加上 spring security的依赖

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

keycloak.json配置

keycloak的配置可以放在 application.yaml中,也可以单独放在自己的keycloak.json中;

放在不同位置,只需要在下面的配置文件中添加对应的resolver即可;

在这里,我们放在单独的keycloak.json中

{
    "realm": "kulchao-mall",
    "auth-server-url": "http://127.0.0.1:8018/auth",
    "ssl-required": "none",
    "resource": "mall_system",
    "bearer-only": true,
    "credentials": {
        "secret": "a9e0d2ce-25a4-4e10-b8bf-577f28468a42"
    },
    "policy-enforcer": {
        "paths": [
            {
                "name": "commodity",
                "path": "/",
                "methods": [
                    {
                        "method": "GET",
                        "scopes": ["view"]
                    },
                    {
                        "method": "POST",
                        "scopes": ["edit"]
                    }
                ]
            }
        ]
    }
}

KeycloakSecurityConfig配置

完成上述步骤后,就需要参照Spring Security的使用,配置下SecurityConfig,形式如下:

@EnableWebSecurity(debug = true)
@KeycloakConfiguration
public class KeycloakSecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
...
}

下面一步步讲解其中需要注意的地方:

配置如何读取keycloak

我们这里把keycloak的配置放在了单独的keycloak.json文件,对应的就需要配置一下读取该文件信息:

    @Bean
    public KeycloakConfigResolver keycloakConfigResolver() {
       return new KeycloakConfigResolver() {

           private KeycloakDeployment keycloakDeployment;

           @Override
           public KeycloakDeployment resolve(HttpFacade.Request facade) {
               if (keycloakDeployment != null) {
                   return keycloakDeployment;
               }

               String path = "/keycloak.json";
               InputStream configInputStream = getClass().getResourceAsStream(path);

               if (configInputStream == null) {
                   throw new RuntimeException("Could not load Keycloak deployment info: " + path);
               } else {
                   keycloakDeployment = KeycloakDeploymentBuilder.build(configInputStream);
               }

               return keycloakDeployment;
           }
       };
   }

具体如何起作用,查看下源码都是很清楚,不再赘述;

配置会话鉴权策略

SessionAuthenticationStrategy 主要用于会话相关的防护,比如:固定会话攻击等.
这里需要我们配一下具体的策略,相应的说明如下:

  • public or confidential 应用设置为 RegisterSessionAuthenticationStrategy
  • bearer-only应用设置为 NullAuthenticatedSessionStrategy

对应的配置如下:

    @Override
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
//        return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
        return new NullAuthenticatedSessionStrategy();
    }

避免重复注册过滤器

由于KeycloakSpringBoot中会进行提前注册相关的过滤器,然后在Spring Security中也进行相关过滤器的添加,会导致重复注册;

我们只需要在 securityconfig中添加FilterRegistrationBean,则对应的@ConditionalOnMissingBean注解条件不成立,就可以避免上述情况的发生.

@Bean
    @SuppressWarnings("unchecked")
    public FilterRegistrationBean keycloakAuthenticationProcessingFilterRegistrationBean(KeycloakAuthenticationProcessingFilter filter) {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
        registrationBean.setEnabled(false);
        return registrationBean;
    }

    @Bean
    @SuppressWarnings("unchecked")
    public FilterRegistrationBean keycloakPreAuthActionsFilterRegistrationBean(KeycloakPreAuthActionsFilter filter) {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
        registrationBean.setEnabled(false);
        return registrationBean;
    }

    @Bean
    @SuppressWarnings("unchecked")
    public FilterRegistrationBean keycloakAuthenticatedActionsFilterBean(KeycloakAuthenticatedActionsFilter filter) {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
        registrationBean.setEnabled(false);
        return registrationBean;
    }

    @Bean
    @SuppressWarnings("unchecked")
    public FilterRegistrationBean keycloakSecurityContextRequestFilterBean(KeycloakSecurityContextRequestFilter filter) {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
        registrationBean.setEnabled(false);
        return registrationBean;
    }

    // avoid double registration
    @Bean
    @Override
    @ConditionalOnMissingBean(HttpSessionManager.class)
    protected HttpSessionManager httpSessionManager() {
        return new HttpSessionManager();
    }

完整的配置如下

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
import java.io.InputStream;
import org.keycloak.adapters.KeycloakConfigResolver;
import org.keycloak.adapters.KeycloakDeployment;
import org.keycloak.adapters.KeycloakDeploymentBuilder;
import org.keycloak.adapters.spi.HttpFacade;
import org.keycloak.adapters.springsecurity.KeycloakConfiguration;
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;
import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
import org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticatedActionsFilter;
import org.keycloak.adapters.springsecurity.filter.KeycloakAuthenticationProcessingFilter;
import org.keycloak.adapters.springsecurity.filter.KeycloakPreAuthActionsFilter;
import org.keycloak.adapters.springsecurity.filter.KeycloakSecurityContextRequestFilter;
import org.keycloak.adapters.springsecurity.management.HttpSessionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy;
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;

/**
 *
 * @author admin
 */
@EnableWebSecurity(debug = true)
@KeycloakConfiguration
public class KeycloakSecurityConfig extends KeycloakWebSecurityConfigurerAdapter {

    private final static Logger LOGGER = LoggerFactory.getLogger(KeycloakSecurityConfig.class);

    /**
     * Registers the KeycloakAuthenticationProvider with the authentication
     * manager.
     *
     * @param auth
     */
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) {
        KeycloakAuthenticationProvider keyCloakAuthProvider = keycloakAuthenticationProvider();
        keyCloakAuthProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
        auth.authenticationProvider(keyCloakAuthProvider);
    }

    @Override
    protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
//        return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
        return new NullAuthenticatedSessionStrategy();
    }

    @Override
    protected AuthenticationEntryPoint authenticationEntryPoint() {
        return (request, response, authException) -> {
            LOGGER.warn("401 Unauthorized while processing " + request.getRequestURI(), authException);
            response.sendError(401, "Unauthorized");
        };
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        http.cors().and()
                .csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .sessionAuthenticationStrategy(sessionAuthenticationStrategy())
                .and();
        http.authorizeRequests().anyRequest().authenticated().and();
    }

    /**
     * Overrides default keycloak config resolver behaviour
     * (/WEB-INF/keycloak.json) by a simple mechanism.
     * <p>
     * This example loads other-keycloak.json when the parameter use.other is
     * set to true, e.g.: {@code ./gradlew bootRun -Duse.other=true}
     *
     * @return keycloak config resolver
     */
    @Bean
    public KeycloakConfigResolver keycloakConfigResolver() {
        return new KeycloakConfigResolver() {

            private KeycloakDeployment keycloakDeployment;

            @Override
            public KeycloakDeployment resolve(HttpFacade.Request facade) {
                if (keycloakDeployment != null) {
                    return keycloakDeployment;
                }

                String path = "/keycloak.json";
                InputStream configInputStream = getClass().getResourceAsStream(path);

                if (configInputStream == null) {
                    throw new RuntimeException("Could not load Keycloak deployment info: " + path);
                } else {
                    keycloakDeployment = KeycloakDeploymentBuilder.build(configInputStream);
                }

                return keycloakDeployment;
            }
        };
    }

    // avoids double registration of keycloak filters
    @Bean
    @SuppressWarnings("unchecked")
    public FilterRegistrationBean keycloakAuthenticationProcessingFilterRegistrationBean(KeycloakAuthenticationProcessingFilter filter) {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
        registrationBean.setEnabled(false);
        return registrationBean;
    }

    @Bean
    @SuppressWarnings("unchecked")
    public FilterRegistrationBean keycloakPreAuthActionsFilterRegistrationBean(KeycloakPreAuthActionsFilter filter) {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
        registrationBean.setEnabled(false);
        return registrationBean;
    }

    @Bean
    @SuppressWarnings("unchecked")
    public FilterRegistrationBean keycloakAuthenticatedActionsFilterBean(KeycloakAuthenticatedActionsFilter filter) {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
        registrationBean.setEnabled(false);
        return registrationBean;
    }

    @Bean
    @SuppressWarnings("unchecked")
    public FilterRegistrationBean keycloakSecurityContextRequestFilterBean(KeycloakSecurityContextRequestFilter filter) {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
        registrationBean.setEnabled(false);
        return registrationBean;
    }

    // avoid double registration
    @Bean
    @Override
    @ConditionalOnMissingBean(HttpSessionManager.class)
    protected HttpSessionManager httpSessionManager() {
        return new HttpSessionManager();
    }

}

Policy Enforcers相关配置

参考官方文档

只要policy-enforcer有定义,就会开启 PEP流程

{
 "policy-enforcer": {}
}

paths主要配置如下:

  • name: 资源名称,需要跟资源服务上的资源名称对应起来.定义了该属性值,则uris匹配规则失效
  • path: 资源路径;在不配置name的情况下,根据path匹配对应的资源;
  • methods-method: 请求方法
  • methods-scopes: 需要的scopes数组
  • methods-scopes-enforcement-mode: 表明上面的scopes是只需要一个满足(ANY)还是都需要满足(ALL)
  • enforcement-mode: enforcement执行的模式是强制还是关闭
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值