iOS Developer的全栈之路 - Keycloak(4)

2020-01-05  本文已影响0人  西西的一天

本节将介绍如何基于SpringSe使用Keycloak来保护rest api,网上相关的文章很少,几乎没有直接可参考的,这里附上示例代码。其中使用的Keycloak版本为8.0.1。

配置Keycloak

根据系列文章的前两篇已经对Keycloak有了大致的认识,基础配置就不再赘述,这里需要创建一个realm:springboot-integration,并在此realm中创建两个client,一个是spring-security,用于获取token;一个是springboot-rest-api,也就是我们要保护的api。
分别看一下它们的配置:


spring-security.png rest-api.png

为什么是两个client呢?若要保护的是rest api,需要在访问时带上token,这种行为需要Access Type设置为bearer-only,而设置为bearer-only的client是无法通过请求获取token的。

添加用户

在配置好client之后,在这个realm中添加两个角色(user, admin),并为这两个角色分别添加一个用户,添加的方式可以参考第二篇文章。

Tips: 一个角色可以包含另一个角色的权限,在添加角色时开启Composite Roles,再在下方选择其他角色即可。

获取token

获取token使用的是spring-security这个client,我们在Access Type中选择了credential,此时发送请求获取token时需要带一个client_secret字段,这个字段在此获取:


spring-security-credentials.png

此时便可以通过Postman来获取token了:


request token.png

Spring Boot Application

接下来就需要准备一个简单的springboot项目,源码可参考此连接,在pom文件中添加对Keycloak的依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>keycloak-integration</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>keycloak-integration</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.keycloak</groupId>
            <artifactId>keycloak-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.keycloak.bom</groupId>
                <artifactId>keycloak-adapter-bom</artifactId>
                <version>8.0.1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

和上篇中的pom相比,添加了keycloak-spring-boot-starter和keycloak-adapter-bom。在此添加了两个endpoint用于演示:

@RestController
public class HelloController {
    @GetMapping(value = "/admin")
    public String admin() {
        return "Admin";
    }
    @GetMapping("/user")
    public String user() {
        return "User";
    }
}

添加了Keycloak的依赖后,剩下的工作就是配置了,默认情况下,Keycloak会从keycloak.json文件中获取配置信息,但在springboot中通常是使用application.properties来配置,所以需要声明一个resolver来覆盖原有的配置文件,添加后,启动后便会从application.properties来获取Keycloak的配置:

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

配置如下所示:

server.port=8099
keycloak.realm=springboot-integration
keycloak.bearer-only=true
keycloak.auth-server-url=http://localhost:8080/auth
keycloak.ssl-required=external
keycloak.resource=springboot-rest-api
keycloak.use-resource-role-mappings=false
keycloak.principal-attribute=preferred_username
keycloak.credentials.secret=8e4c5bac-8dcb-4f68-a529-c998e19f0c4d
keycloak.confidential-port=0

其中的credentials.secret是从springboot-rest-api这个client中获得的:


rest-api-credentials.png

剩下的配置就是SpringSecurity的配置了:

@KeycloakConfiguration
public class KeycloakSecurityConfigurer extends KeycloakWebSecurityConfigurerAdapter {

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

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

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

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

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

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

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        SimpleAuthorityMapper grantedAuthorityMapper = new SimpleAuthorityMapper();
        grantedAuthorityMapper.setConvertToUpperCase(true);

        KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
        keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(grantedAuthorityMapper);

        auth.authenticationProvider(keycloakAuthenticationProvider);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        http.cors().and().csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .antMatchers("/user").hasRole("USER")
                .antMatchers("/admin").hasRole("ADMIN")
                .anyRequest().permitAll();
    }
}

此处的父类KeycloakWebSecurityConfigurerAdapter,它继承了WebSecurityConfigurerAdapter,也就是我们之前配置SpringSecurity的配置类,而@KeycloakConfiguration注解也是对之前注解的一个复合:

@Configuration
@ComponentScan(basePackageClasses = KeycloakSecurityComponents.class)
@EnableWebSecurity
public @interface KeycloakConfiguration {
}
  1. NullAuthenticatedSessionStrategy:因为保护的是rest api,而不是基于session的所以此时需要override sessionAuthenticationStrategy;
  2. configureGlobal:这段配置来自于Keycloak的官方文档,并添加了SimpleAuthorityMapper,它的作用是一个匹配Keycloak和SpringSecurity中声明的角色大小写不同

测试

配置完成后,便可以开始测试了,使用postman发送请求,其中token来自于之前的请求:


test.png
上一篇下一篇

猜你喜欢

热点阅读