SpringBoot Security集成Keycloak

2021-05-06  本文已影响0人  乱七八糟谈技术

Keycloak 是一种开源身份和访问管理解决方案,它使使用很少或根本没有代码的现代应用程序和服务变得容易。之前我也有很多文章介绍过如何使用和配置keycloak以及如何在Nginx里来集成Keycloak,这篇文章主要介绍如何在SpringBoot Security集成Keycloak来对微服务的API进行认证和授权。

环境准备

注意:在Keycloak 8.0.2版本以及使用keycloak-spring-boot-starter 8.0.2版本上运行文中的实例有问题,之上的版本也没有测试。如何安装Keycloak可以到官网上去下载,或者使用Docker方式安装。

Keycloak配置

首先,让我们在 Keycloak中进行一下配置,详细的方法可以参考我之前的博文。

创建领域

Keycloak里的领域是管理一组用户、凭据、角色和组和客户端。用户属于并登录到一个领域。领域彼此隔离,只能管理和验证他们控制的用户。
转到 http://localhost:8080/auth/admin/ 并使用管理员登录到管理控制台,管理员身份可以在启动时设置。
从主下拉菜单中,单击"添加领域"。当您登录到主领域时,此下拉菜单列出了所有的领域。
在名称字段中键入Demo-Realm并单击"创建"。

image.png

创建该领域时,master领域的管理员控制台页面将打开。切换到新创建的Demo-Realm领域。项目中尽量避免使用Master领域,应该自己创建一个领域。

创建客户端

客户端是可以请求Keycloak对用户进行身份验证的实体。大多数情况下,客户端是希望使用 Keycloak 来保护自己并提供单个登录解决方案的应用程序和服务,比如一个微服务程序,Nginx客户端等都可以作为一个客户端。客户端也可以是只想要请求身份信息或访问令牌的实体,以便他们能够安全地调用Keycloak保护的网络上的其他服务。
第一步,单击左窗格中的"client"菜单。所选领域下的所有可用客户端都会呈现在列表中。如下图,


image.png

第二步,要创建新客户端,请单击"Create"按钮。您将被提示为客户ID、客户协议和ROOT URL。客户ID的Y一般是是你的应用程序的名称(比如myMicroservice),客户端协议应设置为openid-connect,root URL应设置为应用程序URL。


image.png
第三步,保存后,您将返回到客户端配置页面,如果需要,您可以填写客户端名字和描述。
将Access Type设置为confidential、Authorization Enabled设置为on、启用Service Account,并单击"保存"。
Credentials选项卡将显示客户端密钥,这个在后面的微服务程序里需要配置。
image.png

第四步,转到Client Roles选项卡来创建客户端角色。想象一下,您正在微服务具有不同类型的用户,具有不同的用户权限。比如,user和admin,一些读取API 仅可供user访问。管理员可能会访问更多的API。根据示例,让我们创建两个角色:Uer和Admin,单击"添加角色"按钮。


image.png

创建领域角色

Keycloak分为client role 和realm role,realm role可以组合多个client role。应用程序通常将访问权限和权限分配给特定角色,而不是单个用户,因为与用户打交道可能过于精细,难以管理。
第一步,我们创建两个realm角色app-user和app-admin,并赋予相应的client role(user和admin)单击左窗格中的"Role"菜单。所选领域的所有可用角色都会显示在列表中。


image.png

第二步,要创建realm角色app-user,请单击"Add Role"。您将被提示为角色名称和描述。提供以下详细信息并保存。
保存后,启用Composite Roles,并在"Client roles"字段下搜索刚创建的client。选择user角色,然后单击"Add Selected"


image.png

此配置将赋予app-user领域角色到客户端的user角色。也可以添加多个客户端角色到此realm角色。
第三步,按照上面的步骤,创建app-admin用户,并赋予客户端admin角色。

创建用户

Keycloak中的用户是能够登录到你的系统的实体,user是整个领域生效,不是在客户端中。他们可以拥有与自己相关的属性,如电子邮件、用户名、地址、电话号码和出生日。他们可以被分配到group,并分配给他们特定的角色。
让我们创建以下用户,并授予他们用于app-user和app-admin角色。

第四步,点击Credentials标签页,为这个用户设置临时密码。


image.png

第五步,输入一个新密码并确认新密码,点击"Reset Password"设置用户的新密码。
第六步,单击"Role mapping"选项卡,向用户分配领域角色。领域角色列表将在可用角色列表中提供。选择一个所需的角色,然后单击"已添加已选>将其分配给用户。
角色分配后,分配的角色将在分配的角色列表下可用。员工1、员工2和员工的角色分配如下。


image.png

完成上面的realm,client,user和role的创建和配置后,Keycloak的基本配置功能都已经完成,更高级的配置可以去官网学习。

产生tokens

Keycloak提供了一系列的endpoint来供用户访问,包括如何为用户产生access token。
第一步,选择Realm settings,点击"OpenID Endpoint Configuration"能看到OpenID Endpoint的详细信息。如下图,


image.png

第二步,从OpenID Endpoint Configuration拷贝token_endpoint,URL大致结构如下:

<KEYCLOAK_SERVER_URL>/auth/realms/<REALM_NAME>/protocol/openid-connect/token
例如:http://localhost:8080/auth/realms/Demo-Realm/protocol/openid-connect/token

第三步,使用postman或者curl来访问token endpoint来获取access token。如下:

curl -X POST '<KEYCLOAK_SERVER_URL>/auth/realms/<REALM_NAME>/protocol/openid-connect/token' \
 --header 'Content-Type: application/x-www-form-urlencoded' \
 --data-urlencode 'grant_type=password' \
 --data-urlencode 'client_id=<CLIENT_ID>' \
 --data-urlencode 'client_secret=<CLIENT_SECRET>' \
 --data-urlencode 'username=<USERNAME>' \
 --data-urlencode 'password=<PASSWORD>'
例如:
curl -X POST 'http://localhost:8080/auth/realms/Demo-Realm/protocol/openid-connect/token' \
 --header 'Content-Type: application/x-www-form-urlencoded' \
 --data-urlencode 'grant_type=password' \
 --data-urlencode 'client_id=springboot-microservice' \
 --data-urlencode 'client_secret=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx' \
 --data-urlencode 'username=employee1' \
 --data-urlencode 'password=xxxxxx'

使用之前创建的client的client_secret和密码替换里面相应的信息。获取到的结果如下图:


image.png

access token是一个JWT token,因此可以使用jwt工具来查看里面的信息。可以访问https://jwt.io,将access token拷贝到里面,看到信息如下:

image.png

SpingBoot里集成Keycloak

创建一个SpingBoot模板创建一个SpringBoot security项目。
Step1,添加keycloak-spring-boot-starter依赖。

<properties>
   <java.version>11</java.version>
   <keycloak.version>9.0.2</keycloak.version>
</properties>
<dependencyManagement>
   <dependencies>
      <dependency>
         <groupId>org.keycloak.bom</groupId>
         <artifactId>keycloak-adapter-bom</artifactId>
         <version>${keycloak.version}</version>
         <type>pom</type>
         <scope>import</scope>
      </dependency>
   </dependencies>
</dependencyManagement>
<dependencies>
   <dependency>
      <groupId>org.keycloak</groupId>
      <artifactId>keycloak-spring-boot-starter</artifactId>
      <version>${keycloak.version}</version>
   </dependency>
</dependencies>

注意版本,在8.0.2上不能使用下面的代码,需要配置keycloak.json文件。
第二步,增加Keycloak配置
在application.properties中增加如下配置,

keycloak.realm                      = Demo-Realm
keycloak.auth-server-url            = http://localhost:8080/auth
keycloak.ssl-required               = external
keycloak.resource                   = springboot-microservice
keycloak.credentials.secret         = XXXXXXXXXXXXXXXXXXXXXXXXX
keycloak.use-resource-role-mappings = true
keycloak.bearer-only                = true

使用你的client secret替换keycloak.credentials.secret 属性和client id替换keycloak.resource。

第三步,增加KeycloakSecurityConfig.java配置属性类。

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(jsr250Enabled = true)
public class KeycloakSecurityConfig extends KeycloakWebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        http.authorizeRequests()
            .anyRequest()
            .permitAll();
        http.csrf().disable();
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
        keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
        auth.authenticationProvider(keycloakAuthenticationProvider);
    }

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

    @Bean
    public KeycloakConfigResolver KeycloakConfigResolver() {
        return new KeycloakSpringBootConfigResolver();
    }
}
使用@RolesAllowed注解来实现Role-base的访问控制

在一个API方法上增加RolesAllowed注解就可以轻松的实现role based的API访问控制。如果不加此注解,则默认所有用户都可以访问。如下实例:

@RestController
@RequestMapping("/test")
public class TestController {

    @RequestMapping(value = "/anonymous", method = RequestMethod.GET)
    public ResponseEntity<String> getAnonymous() {
        return ResponseEntity.ok("Hello Anonymous");
    }

    @RolesAllowed("user")
    @RequestMapping(value = "/user", method = RequestMethod.GET)
    public ResponseEntity<String> getUser(@RequestHeader String Authorization) {
        return ResponseEntity.ok("Hello User");
    }

    @RolesAllowed("admin")
    @RequestMapping(value = "/admin", method = RequestMethod.GET)
    public ResponseEntity<String> getAdmin(@RequestHeader String Authorization) {
        return ResponseEntity.ok("Hello Admin");
    }

    @RolesAllowed({ "admin", "user" })
    @RequestMapping(value = "/all-user", method = RequestMethod.GET)
    public ResponseEntity<String> getAllUser(@RequestHeader String Authorization) {
        return ResponseEntity.ok("Hello All User");
    }

}

访问这些API前,需要使用token endpoint来获取access token,再访问这些API.访问的结果应该如下:

curl -X GET 'http://localhost:8000/test/user' \
--header 'Authorization: bearer <ACCESS_TOKEN>'
Outputs:
anonymous: 403 Forbidden
employee1: Hello User
employee2: 403 Forbidden
employee3: Hello User
curl -X GET 'http://localhost:8000/test/admin' \
--header 'Authorization: bearer <ACCESS_TOKEN>'
Outputs:
anonymous: 403 Forbidden
employee1: 403 Forbidden
employee2: Hello Admin
employee3: Hello Admin
curl -X GET 'http://localhost:8000/test/all-user' \
--header 'Authorization: bearer <ACCESS_TOKEN>'
Outputs:
anonymous: 403 Forbidden
employee1: Hello All User
employee2: Hello All User
employee3: Hello All User

如果token设置为5分钟的过期时间,则会出现401 unauthorized 错误

Security开启和关闭

有时为了开发和调试方便,需要关闭security的相关功能,网上有很多的方法因为SpringBoot的版本不同不太好用,下面是我经过测试和验证的方法,使用@ConditionalOnProperty注解来注入不同的bean类。在上面的代码中

@ConditionalOnProperty(prefix = "security", name = "enabled", havingValue = "true")
public class KeycloakSecurityConfig extends KeycloakWebSecurityConfigurerAdapter {

增加了该条件注解,当security.enableld=true时才会注入KeycloakSecurityConfig这个Bean类。此外,需要增加下面的注解类来disable web security。

@Configuration
@ConditionalOnProperty(prefix = "security", name = "enabled", havingValue = "false", matchIfMissing = true)
public class KeycloakSecurityDisable extends WebSecurityConfigurerAdapter {
    @Override
    public void configure(WebSecurity web){
        web.ignoring().antMatchers("/**");
    }
}

在application.properties中增加如下的配置来关闭security。

security.enabled = false
security.ignored=/**
security.basic.enabled=false
management.security.enabled=false

在application.properties中增加如下的配置来开启security。

security.enabled = true
security.ignored=/**
security.basic.enabled=false
management.security.enabled=false
上一篇下一篇

猜你喜欢

热点阅读