Spring Security中使用Keycloak作为认证授权服务器

服务器
Keycloak对流行的Java应用提供了适配器。在系列文章的上一篇我们演示了针对Spring Boot的安全保护,用的就是适配器的一种。Keycloak同样提供Spring Security的适配器,后续的几篇文章我们就来共同学习Spring Security适配器的使用。

 [[415177]]

Keycloak对流行的Java应用提供了适配器。在系列文章的上一篇我们演示了针对Spring Boot的安全保护,用的就是适配器的一种。Keycloak同样提供Spring Security的适配器,后续的几篇文章我们就来共同学习Spring Security适配器的使用。

Keycloak的安装可参考前面的系列教程。

适配器集成

在Spring 应用中我们集成keycloak-spring-security-adapter:

  1. <dependency> 
  2.     <groupId>org.keycloak</groupId> 
  3.     <artifactId>keycloak-spring-security-adapter</artifactId> 
  4.     <version>15.0.0</version> 
  5. </dependency> 

在Spring Boot中可以这样集成:

  1. <dependency> 
  2.     <groupId>org.springframework.boot</groupId> 
  3.     <artifactId>spring-boot-starter-security</artifactId> 
  4. </dependency> 
  5. <dependency> 
  6.     <groupId>org.keycloak</groupId> 
  7.     <artifactId>keycloak-spring-boot-starter</artifactId> 
  8.     <version>15.0.0</version> 
  9. </dependency>        

然后就能利用Spring Security的特性来集成Keycloak。Keycloak 提供了一个 KeycloakWebSecurityConfigurerAdapter 作为创建WebSecurityConfigurer 实例的方便基类。我们可以编写了一个配置类来定制我们的安全策略,就像这样:

  1. @KeycloakConfiguration 
  2. public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter 
  3.     /** 
  4.      *  注册了一个Keycloak的AuthenticationProvider 
  5.      */ 
  6.     @Autowired 
  7.     public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { 
  8.         auth.authenticationProvider(keycloakAuthenticationProvider()); 
  9.     } 
  10.  
  11.     /** 
  12.      * 定义会话策略 
  13.      */ 
  14.     @Bean 
  15.     @Override 
  16.     protected SessionAuthenticationStrategy sessionAuthenticationStrategy() { 
  17.         return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl()); 
  18.     } 
  19.  
  20.     /** 
  21.      * 常见的Spring Security安全策略 
  22.      */  
  23.     @Override 
  24.     protected void configure(HttpSecurity http) throws Exception 
  25.     { 
  26.         super.configure(http); 
  27.         http 
  28.                 .authorizeRequests() 
  29.                 .antMatchers("/customers*").hasRole("USER"
  30.                 .antMatchers("/admin/**").hasRole("base_user"
  31.                 .anyRequest().permitAll(); 
  32.     } 

注意:上面的配置并不能成功。

配置完上面的然后我们直接启动应用,结果并不像期望的那样:

  1. java.io.FileNotFoundException: Unable to locate Keycloak configuration file: keycloak.json 

抛出找不到 keycloak.json文件的异常。Keycloak支持的每个Java适配器都可以通过一个简单的JSON文件进行配置,我们缺失的就是这个文件。

  1.   "realm" : "demo"
  2.   "resource" : "customer-portal"
  3.   "realm-public-key" : "MIGfMA0GCSqGSIb3D...31LwIDAQAB"
  4.   "auth-server-url" : "https://localhost:8443/auth"
  5.   "ssl-required" : "external"
  6.   "use-resource-role-mappings" : false
  7.   "enable-cors" : true
  8.   "cors-max-age" : 1000, 
  9.   "cors-allowed-methods" : "POST, PUT, DELETE, GET"
  10.   "cors-exposed-headers" : "WWW-Authenticate, My-custom-exposed-Header"
  11.   "bearer-only" : false
  12.   "enable-basic-auth" : false
  13.   "expose-token" : true
  14.   "verify-token-audience" : true
  15.    "credentials" : { 
  16.       "secret" : "234234-234234-234234" 
  17.    }, 
  18.  
  19.    "connection-pool-size" : 20, 
  20.    "socket-timeout-millis": 5000, 
  21.    "connection-timeout-millis": 6000, 
  22.    "connection-ttl-millis": 500, 
  23.    "disable-trust-manager"false
  24.    "allow-any-hostname" : false
  25.    "truststore" : "path/to/truststore.jks"
  26.    "truststore-password" : "geheim"
  27.    "client-keystore" : "path/to/client-keystore.jks"
  28.    "client-keystore-password" : "geheim"
  29.    "client-key-password" : "geheim"
  30.    "token-minimum-time-to-live" : 10, 
  31.    "min-time-between-jwks-requests" : 10, 
  32.    "public-key-cache-ttl": 86400, 
  33.    "redirect-rewrite-rules" : { 
  34.    "^/wsmaster/api/(.*)$" : "/api/$1" 
  35.    } 

上面包含的客户端配置属性都可以在Keycloak控制台进行配置,见下图:

配置Keycloak客户端属性

也就是说我们需要的json文件和图中的配置项是对应的。比较人性化的是我们不需要自行编写这个json文件,Keycloak提供了下载客户端配置的方法,这里我只使用了必要的配置项:

你可以下载客户端json配置

引入客户端配置

虽然顺利拿到json文件,但是加载这个json配置却不太顺利,经过我的摸索需要实现一个KeycloakConfigResolver并注入Spring IoC,有下面两种实现方式。

复用Spring Boot Adapter配置

直接复用Spring Boot的配置形式,先声明Spring Boot的KeycloakConfigResolver实现:

  1. /** 
  2.    * 复用spring boot 的方法 
  3.    * 
  4.    * @return the keycloak config resolver 
  5.    */ 
  6.   @Bean 
  7.   public KeycloakConfigResolver keycloakConfigResolver() { 
  8.       return new KeycloakSpringBootConfigResolver(); 
  9.   } 

然后复用Spring Boot的application.yaml的配置项:

复用Spring Boot配置项

原来的角色资源映射约束失效。

自定义实现

你也可以自定义写解析,这个时候json形式已经不重要了,你可以将json文件的内容存储到任何你擅长的地方。

  1. /** 
  2.  * 自己写解析 
  3.  * 
  4.  * @return the keycloak config resolver 
  5.  */ 
  6. @Bean 
  7. public KeycloakConfigResolver fileKeycloakConfigResolver() { 
  8.     return  new KeycloakConfigResolver() { 
  9.         @SneakyThrows 
  10.         @Override 
  11.         public KeycloakDeployment resolve(HttpFacade.Request request) { 
  12.             // json 文件放到resources 文件夹下 
  13.             ClassPathResource classPathResource = new ClassPathResource("./keycloak.json"); 
  14.             AdapterConfig adapterConfig = new ObjectMapper().readValue(classPathResource.getFile(), AdapterConfig.class); 
  15.  
  16.             return KeycloakDeploymentBuilder.build(adapterConfig); 
  17.         } 
  18.     }; 

角色命名策略

Spring Security会为每个角色添加ROLE_前缀,这需要我们声明GrantedAuthoritiesMapper的实现SimpleAuthorityMapper来完成这一功能。Keycloak在KeycloakAuthenticationProvider中配置该功能:

  1. KeycloakAuthenticationProvider authenticationProvider = keycloakAuthenticationProvider(); 
  2.  authenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper()); 

完整的配置

applicaiton.yaml:

  1. keycloak: 
  2. # 声明客户端所在的realm 
  3.   realm: felord.cn 
  4. # keycloak授权服务器的地址 
  5.   auth-server-url: http://localhost:8011/auth 
  6. # 客户端名称 
  7.   resource: springboot-client 
  8. # 声明这是一个公开的客户端,否则不能在keycloak外部环境使用,会403 
  9.   public-client: true 

这里要结合Keycloak导出的json文件配置。

Spring Security配置:

  1. @KeycloakConfiguration 
  2. public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter { 
  3.      
  4.     /** 
  5.      * 复用spring boot 的方法 
  6.      * 
  7.      * @return the keycloak config resolver 
  8.      */ 
  9.     @Bean 
  10.     public KeycloakConfigResolver keycloakConfigResolver() { 
  11.         return new KeycloakSpringBootConfigResolver(); 
  12.     } 
  13.     /** 
  14.      * 自己写解析 
  15.      * 
  16.      * @return the keycloak config resolver 
  17.      */ 
  18. //    @Bean 
  19.     public KeycloakConfigResolver fileKeycloakConfigResolver() { 
  20.         return request -> { 
  21.             // json 文件放到resources 文件夹下 
  22.             ClassPathResource classPathResource = new ClassPathResource("./keycloak.json"); 
  23.             AdapterConfig adapterConfig = null
  24.             try { 
  25.                 adapterConfig = new ObjectMapper().readValue(classPathResource.getFile(),  
  26.                         AdapterConfig.class); 
  27.             } catch (IOException e) { 
  28.                 e.printStackTrace(); 
  29.             } 
  30.  
  31.             return KeycloakDeploymentBuilder.build(adapterConfig); 
  32.         }; 
  33.     } 
  34.     /** 
  35.      *  配置{@link AuthenticationManager} 
  36.      *  这里会引入Keycloak的{@link AuthenticationProvider}实现 
  37.      * 
  38.      * @param auth the auth 
  39.      */ 
  40.     @Autowired 
  41.     public void configureGlobal(AuthenticationManagerBuilder auth) { 
  42.         KeycloakAuthenticationProvider authenticationProvider = keycloakAuthenticationProvider(); 
  43.         authenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper()); 
  44.         auth.authenticationProvider(authenticationProvider); 
  45.     } 
  46.     /** 
  47.      * 会话身份验证策略 
  48.      */ 
  49.     @Bean 
  50.     @Override 
  51.     protected SessionAuthenticationStrategy sessionAuthenticationStrategy() { 
  52.         return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl()); 
  53.     } 
  54.     /** 
  55.      * 配置 session 监听器 保证单点退出生效 
  56.      * 
  57.      * @return the servlet listener registration bean 
  58.      */ 
  59.     @Bean 
  60.     public ServletListenerRegistrationBean<HttpSessionEventPublisher> httpSessionEventPublisher() { 
  61.         return new ServletListenerRegistrationBean<>(new HttpSessionEventPublisher()); 
  62.     } 
  63.     @Override 
  64.     protected void configure(HttpSecurity http) throws Exception { 
  65.         super.configure(http); 
  66.         http 
  67.                 .authorizeRequests() 
  68.                 .antMatchers("/customers*").hasRole("USER"
  69.                 .antMatchers("/admin/**").hasRole("base_user"
  70.                 .anyRequest().permitAll(); 
  71.     } 

调用流程

资源客户端springboot-client有一个接口/admin/foo,当未登录调用该接口时会转发到:

  1. http://localhost:8011/auth/realms/felord.cn/protocol/openid-connect/auth?response_type=code&client_id=springboot-client&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fsso%2Flogin&state=ec00d608-5ce7-47a0-acc8-8a20a2bfadfd&login=true&scope=openid 

输入正确的用户密码后才能得到期望的结果。

典型的authorazation code flow。

总结

Keycloak整合Spring Security的要点这里需要再梳理一下。在原生情况下,客户端的配置、用户的信息、角色信息都由Keycloak负责;客户端只负责角色和资源的映射关系。后续会深入并定制Keycloak和Spring Security以满足实际场景需要。

本文转载自微信公众号「码农小胖哥」,可以通过以下二维码关注。转载本文请联系码农小胖哥公众号。

责任编辑:武晓燕 来源: 码农小胖哥
相关推荐

2022-02-15 07:35:12

服务器KeycloakOAuth2

2021-10-19 14:02:12

服务器SpringSecurity

2021-07-07 07:33:49

开源Keycloak平台

2022-01-12 08:49:33

CaddyWeb服务器Go语言

2021-11-10 05:00:30

服务器Spring授权

2021-08-06 06:51:16

适配器配置Spring

2009-09-17 18:05:51

Nis服务器

2021-11-15 13:58:00

服务器配置授权

2021-08-29 23:33:44

OAuth2服务器Keycloak

2019-07-09 14:57:09

代理服务器NginxHTTPS

2010-08-25 21:25:41

DHCP服务器

2009-09-17 18:06:44

Nis服务器

2022-06-27 14:21:09

Spring语言

2021-11-11 07:38:15

服务器过滤器框架

2010-05-20 18:19:45

2017-09-06 09:21:04

服务器C&CDropBox

2009-06-30 09:39:20

Win Server 打印服务器

2022-05-12 07:37:51

单点登录微服务开源

2009-07-06 10:26:32

2021-12-28 11:13:05

安全认证 Spring Boot
点赞
收藏

51CTO技术栈公众号