美文网首页
SpringBoot Security集成Keycloak

SpringBoot Security集成Keycloak

作者: 乱七八糟谈技术 | 来源:发表于2021-05-06 15:17 被阅读0次

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

    环境准备

    • Keycloak 9.0.2版本
    • SpringBoot Security 2.3.4.RELEASE

    注意:在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角色。

    • employee1拥有app-user realm 角色
    • employee2拥有app-admin realm角色
    • employee3拥有app-admin和app-user两个角色
      第一步,从菜单中,单击"user"以打开用户列表页面。
      第二步,在空用户列表的右侧,单击"add user"以打开添加的用户页面。
      第三步,在用户名字段中输入名称employee1,然后单击"保存"以保存数据并为新用户打开管理页面。其他字段可以输入也可以不输,邮件也可以作为登录的唯一标志使用。因此,如果输入邮件,必须保证唯一性。


      image.png

    第四步,点击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();
        }
    }
    
    • configureGlobal:在authentication manager中注册KeycloakAuthenticationProvider。
    • SessionAuthenticationStrategy:定义会话身份验证策略。
    • KeycloakConfigResolver:默认情况下,Spring Security Adapter 寻找 keycloak.json配置文件。您可以通过此bean来确保使用SpringBoot adapter提供的的配置
    • @EnableGlobalMethodSecurity:jsr250Enabled 属性允许我们使用@RoleAllowed注释。
    使用@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
    

    相关文章

      网友评论

          本文标题:SpringBoot Security集成Keycloak

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