Java Spring Security
2021-08-03-22-58-16-790863.jpeg
Keycloak对流行的Java应用提供了适配器。Keycloak同样提供Spring Security的适配器。

适配器集成

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

注意:上面的配置并不能成功。
配置完上面的然后直接启动应用,结果并不像期望的那样:

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

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

  1. {
  2. "realm" : "demo",
  3. "resource" : "customer-portal",
  4. "realm-public-key" : "MIGfMA0GCSqGSIb3D...31LwIDAQAB",
  5. "auth-server-url" : "https://localhost:8443/auth",
  6. "ssl-required" : "external",
  7. "use-resource-role-mappings" : false,
  8. "enable-cors" : true,
  9. "cors-max-age" : 1000,
  10. "cors-allowed-methods" : "POST, PUT, DELETE, GET",
  11. "cors-exposed-headers" : "WWW-Authenticate, My-custom-exposed-Header",
  12. "bearer-only" : false,
  13. "enable-basic-auth" : false,
  14. "expose-token" : true,
  15. "verify-token-audience" : true,
  16. "credentials" : {
  17. "secret" : "234234-234234-234234"
  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. }
  36. }

上面包含的客户端配置属性都可以在Keycloak控制台进行配置,见下图:
2021-08-03-22-58-16-988856.png
配置Keycloak客户端属性
也就是说需要的json文件和图中的配置项是对应的。比较人性化的是不需要自行编写这个json文件,Keycloak提供了下载客户端配置的方法,这里只使用了必要的配置项:
Spring Security 中使用Keycloak作为认证授权服务器 - 图3
可以下载客户端json配置

Keycloak适配器的常用属性

在Spring Security集成Keycloak 适配器时需要引入一些额外的配置属性。一般会把它配置到Spring Boot的配置文件中。

realm

Keycloak领域名称,这是一个必须项。

resource

应用的client_id,Keycloak服务器上注册的每个客户端都有一个独一无二的标识。这是一个必须项。

realm-public-key

PEM格式的realm公钥,不建议客户端配置。每次Keycloak Adapter会自动拉取它。

auth-server-url

Keycloak服务器的基本地址,格式通常是https://host:port/auth,这是一个必须项。

ssl-required

Keycloak 服务器的通信使用HTTPS的范围,是可选的,有三个选项:

  • external,默认值,表示外部的请求都必须使用HTTPS。
  • all,顾名思义,所有的都使用HTTPS。
  • none,禁用HTTPS。

    confidential-port

    Keycloak服务器的安全端口,默认 8443。

    use-resource-role-mappings

    如果设置为true,Keycloak Adapter将检查令牌携带的用户角色是否跟资源一致;否则会去查询realm中用户的角色。默认false

    public-client

    设置为true则不需要为客户端配置密码,否则需要配置keycloak.credentials.secret。生成secret的方法是在Keycloak控制台上修改对应客户端设置选项的访问类型为confidential,然后在安装中查看对应配置项。当访问类型不是confidential时该值为false

    enable-cors

    开启跨域(cors)支持。可选项,默认false。如果设置为true就激活了cors-开头的配置项,都是常见的跨域配置项。

    bearer-only

    对于服务,这应该设置为true。如果启用,适配器将不会尝试对用户进行身份验证,而只会验证不记名令牌。如果用户请求资源时没有携带Bearer Token将会401。这是可选的。默认值为false。

    autodetect-bearer-only

    如果应用不仅仅是Web应用而且还提供API服务(现在通常是Restful Service),开启了这一配置后Keycloak服务器会通过请求标头相对“智能”地引导未认证的用户到登录页面还是返回401状态。比bearer-only更加智能一些。

    enable-basic-auth

    为适配器开启Basic Authentication认证,如果开启就必须提供secret。默认false

    expose-token

    JavaScript CORS 请求通过根路径下/k_query_bearer_token用来从服务器获取令牌的。

    credentials

    当客户端的访问类型(access type)为Confidential时,需要配置客户端令牌,目前支持secret和jwt类型。参考public-client中的描述。

    引入客户端配置

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

    调用流程

    资源客户端springboot-client有一个接口/admin/foo,当未登录调用该接口时会转发到:
    http://localhost:8011/auth/realms/fcant.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负责;客户端只负责角色和资源的映射关系。