美文网首页
iOS Developer的全栈之路 - Keycloak(4)

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

作者: 西西的一天 | 来源:发表于2020-01-05 23:44 被阅读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

    相关文章

      网友评论

          本文标题:iOS Developer的全栈之路 - Keycloak(4)

          本文链接:https://www.haomeiwen.com/subject/yzgcactx.html