首页 > 文章列表 > Spring Boot应用程序中如何使用Keycloak详解

Spring Boot应用程序中如何使用Keycloak详解

java
141 2023-05-12

正文

在这篇文章中,我将展示如何在 Spring Boot 应用程序中使用 Keycloak。在我们使用 Keycloak 之前,我们将介绍一些关于 Keycloak 是什么以及我们为什么使用它的基础知识。

要开始前,您需要具备以下条件:

  • 代码编辑器——IntelliJ
  • 数据库——MySQL
  • Keycloak
  • Java 8

什么是Keycloak?

Keycloak是一种用于现代应用程序和服务的开源身份和访问管理解决方案。Keycloak 同时提供 SAML 和 OpenID 协议解决方案。

我们为什么要使用Keycloak?

如前所述,Keycloak 提供身份和访问管理,它也是开源的。SAML 和 OpenID 协议是行业标准。构建与 Keycloak 集成的应用程序只会为您提供更安全和稳定的解决方案。当然还有其他可用的解决方案,如 Gluu、Shibboleth、WSO2。

对于这篇文章,我们将使用 Keycloak。

在Spring Boot 应用程序中使用keycloak

这个演示有两个部分。一个是关于Keycloak。第二个是关于使用 Keycloak 保护 Spring Boot 应用程序。

安装Keycloak

在您的机器上下载Keycloak 。解压缩下载的文件并使用命令提示符下 bin 目录中的以下命令运行服务器(注意 – 我在 Windows 机器上):

standalone.bat -Djboss.socket.binding.port-offset=100

这将在本地计算机上为您的 Keycloak 启动 Wildfly 服务器。我们可以通过执行 URL 来访问服务器http://localhost:8180。如果您只是使用 standalone.bat 执行而没有该参数,则服务器将在端口上运行8080

启动服务器后,您要做的第一件事就是创建一个管理员用户。我们将创建一个用户 admin 和密码 d#n3q2b 。

现在我们将访问管理控制台并输入我们的用户详细信息。一旦我们以管理员用户身份登录,我们将看到如下第一个屏幕:

添加应用程序

初始屏幕显示默认领域。出于演示目的,我们将创建一个新领域SpringBootKeycloakApp。在这个领域中,我们将添加我们的 Spring Boot 应用程序作为客户端。在“客户端”选项卡上创建一个新客户端。我们将我们的客户端应用程序命名为 SpringBootApp。

现在在设置中,我们将为我们的 Spring Boot 应用程序添加重定向 url。这是 Keycloak 将在身份验证后重定向到我们的应用程序的 URL。此外,我们使用 openid connect 作为协议作为此实现的一部分。

添加用户

现在我们将添加一个我们将用于身份验证的用户。我们将使用此用户登录到我们的示例 Spring Boot 应用程序。

在 Keycloak 的角色选项卡上为该用户添加您想要的角色ROLE_User。完成后,让我们转到“用户”选项卡并添加一个新用户。

在 Role Mappings 选项卡上,确保为该用户添加新创建的角色。

创建 Spring Boot 应用程序

现在,我们将创建一个简单的 Spring Boot 应用程序,它将使用 Keycloak 来确保安全。作为此应用程序的一部分,我们将为将通过应用程序进行身份验证的用户显示待办事项列表任务。

要构建此应用程序,我们需要以下依赖项:

dependencies {

    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

    implementation 'org.springframework.boot:spring-boot-starter-security'

    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'

    implementation 'org.springframework.boot:spring-boot-starter-web'

    implementation 'org.springframework.boot:spring-boot-starter-jdbc'

    implementation 'org.keycloak:keycloak-spring-boot-starter'

    runtimeOnly 'mysql:mysql-connector-java'

    testImplementation('org.springframework.boot:spring-boot-starter-test') {

        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'

    }

    testImplementation 'org.springframework.security:spring-security-test'

}

如您所见,我们正在使用spring-bootandspring-security以及keycloak-spring-boot-starter依赖项。

keycloak 依赖项包括 Keycloak 客户端适配器。我们将使用这些适配器进行身份验证。它们将取代我们的标准 Spring Security 适配器。为了确保此keycloak-spring-boot-starter依赖项正常工作,我们需要在我们的 gradle 文件中添加一个依赖项,如下所示:

dependencyManagement {

    imports {

        mavenBom "org.keycloak.bom:keycloak-adapter-bom:11.0.2"

    }

}

要了解更多相关信息,您可以访问keycloak的官方文档。

我们的 Controller 类将有两个重要的方法,一个是获取任何人都可以访问的主页,另一个是获取任务列表,只有具有 ROLE_User 角色的经过身份验证的用户才能访问这些任务。此 TaskController 的代码如下所示:

package com.betterjavacode.keycloakdemo.keycloakdemo.controllers;

import com.betterjavacode.keycloakdemo.keycloakdemo.dto.TaskDto;

import com.betterjavacode.keycloakdemo.keycloakdemo.managers.TaskManager;

import org.keycloak.KeycloakSecurityContext;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Controller;

import org.springframework.ui.Model;

import org.springframework.web.bind.annotation.GetMapping;

import javax.servlet.http.HttpServletRequest;

import java.util.List;

@Controller

public class TaskController

{

    private final HttpServletRequest request;

    @Autowired

    public TaskController(HttpServletRequest request)

    {

        this.request = request;

    }

    @Autowired

    private TaskManager taskManager;

    @GetMapping(value="/")

    public String home()

    {

        return "index";

    }

    @GetMapping(value="/tasks")

    public String getTasks(Model model)

    {

        List tasks = taskManager.getAllTasks();

        model.addAttribute("tasks", tasks);

        model.addAttribute("name", getKeycloakSecurityContext().getIdToken().getGivenName());

        return "tasks";

    }

    private KeycloakSecurityContext getKeycloakSecurityContext()

    {

        return (KeycloakSecurityContext) request.getAttribute(KeycloakSecurityContext.class.getName());

    }

}

在这个控制器类中,我们用来TaskManager获取所有任务。我会解释 KeyCloakSecurityContext我什么时候会展示SecurityConfig

有没有使用Spring-Security

我们可以利用此应用程序并使用 Keycloak 进行身份验证,无论是否使用Spring-Security. 作为本演示的一部分,我们使用Spring-Security. 要在没有 Spring-Security 的情况下使用相同的应用程序,您只需删除 Spring-Security 依赖并通过application.properties文件添加安全配置。

我们需要以下属性才能application.properties在此应用程序中使用 Keycloak 进行身份验证。

keycloak.auth-server-url=http://localhost:8180/auth

keycloak.realm=SpringBootKeycloakApp

keycloak.resource=SpringBootApp

keycloak.public-client=true

keycloak.principal-attribute=preferred_username

如果我们想在没有 Spring-Security 的情况下使用这个应用程序,我们还需要以下两个属性:

keycloak.security-constraints[0].authRoles[0]=ROLE_User

keycloak.security-constraints[0].securityCollections[0].patterns[0]=/tasks

由于我们正在使用Spring-Security,所以我们将通过一个 Java 类来配置安全配置SecurityConfig

这个 SecurityConfig 类将扩展KeyCloakWebSecurityConfigurerAdapter.

我们的配置方法如下所示:

    @Override

    protected void configure(HttpSecurity httpSecurity) throws Exception

    {

        super.configure(httpSecurity);

        httpSecurity.authorizeRequests()

                .antMatchers("/tasks").hasRole("User")

                .anyRequest().permitAll();

    }

基本上任何到达 /tasks 端点的请求都应该具有 ROLE_User 用户角色。此处假定 ROLE_ 的前缀。除任何其他请求外,未经任何授权将被允许。在这种情况下,我们将调用我们的索引页面。

我们将使用@KeyCloakConfiguration基本上是封面@Configuration@EnableWebSecurity注释的注释。

由于我们的SecurityConfigextends KeycloakWebSecurityConfigurerAdapter,我们必须实施 sessionAuthenticationStrategy 和 httpSessionManager 。我们还必须使用 Spring Security Authentication Manager 注册我们的 idp Keycloak。

所以我们的 SecurityConfig 将如下所示:

package com.betterjavacode.keycloakdemo.keycloakdemo.config;

import org.keycloak.adapters.springsecurity.KeycloakConfiguration;

import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;

import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;

import org.keycloak.adapters.springsecurity.management.HttpSessionManager;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;

import org.springframework.context.annotation.Bean;

import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;

import org.springframework.security.config.annotation.web.builders.HttpSecurity;

import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;

import org.springframework.security.core.session.SessionRegistryImpl;

import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;

import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;

@KeycloakConfiguration

public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter

{

    @Autowired

    public void configureGlobal(AuthenticationManagerBuilder authenticationManagerBuilder)

    {

        SimpleAuthorityMapper simpleAuthorityMapper = new SimpleAuthorityMapper();

        simpleAuthorityMapper.setPrefix("ROLE_");

        KeycloakAuthenticationProvider keycloakAuthenticationProvider =

                keycloakAuthenticationProvider();

        keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(simpleAuthorityMapper);

        authenticationManagerBuilder.authenticationProvider(keycloakAuthenticationProvider);

    }

    @Bean

    @Override

    protected SessionAuthenticationStrategy sessionAuthenticationStrategy ()

    {

        return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());

    }

    @Bean

    @Override

    @ConditionalOnMissingBean(HttpSessionManager.class)

    protected HttpSessionManager httpSessionManager()

    {

        return new HttpSessionManager();

    }

    @Override

    protected void configure(HttpSecurity httpSecurity) throws Exception

    {

        super.configure(httpSecurity);

        httpSecurity.authorizeRequests()

                .antMatchers("/tasks").hasRole("User")

                .anyRequest().permitAll();

    }

}

所以 Spring Security 使用像 ROLE_USER 这样大写的角色,并且总是使用 ROLE_ 前缀。为了处理这个问题,我在 Keycloak 中添加了一个角色为 ROLE_User 的用户,但我们只会验证一个前缀,因为我们的 http 配置无论如何都会验证该角色。

由于我们将使用 Keycloak 进行身份验证,因此我们需要一个用于用户状态的会话。我们在这里使用RegisterSessionAuthenticationStrategyHttpSessionManager是一个条件 bean,因为 Keycloak 已经实现了那个 bean。

要实现 Keycloak Spring Boot 适配器,我们将添加一个KeyCloakSpringBootConfigResolverbean,如下所示:

package com.betterjavacode.keycloakdemo.keycloakdemo.config;

import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

@Configuration

public class KeycloakConfig

{

    @Bean

    public KeycloakSpringBootConfigResolver keycloakSpringBootConfigResolver()

    {

        return new KeycloakSpringBootConfigResolver();

    }

}

应用程序演示

运行我们的 keycloak 应用程序,它将在http://localhost:8180上运行。我们的 Spring Boot 应用程序将在http://localhost:8080运行。

我们的 Spring Boot 应用程序的第一个屏幕如下所示:

现在,如果用户点击获取所有任务,他将被重定向到 Keycloak 登录屏幕,如下所示:

现在,我将输入我的用户 betterjavacode 用户名和密码,它将向我们显示我们的任务列表,如下所示:

认证流程

当用户单击“获取所有任务”时,用户将被重定向到 Spring Security 的 sso/login 端点,KeycloakSpringBootConfigResolver 处理该端点并向 Keycloak 发送授权代码流请求

http://localhost:8180/auth/realms/SpringBootKeycloakApp/protocol/openid-connect/auth?response_type=code&client_id=SpringBootApp&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fsso%2Flogin&state=70bd4e28-89e6-43b8-8bea-94c6d057a5cf&login=true&scope=openid

Keycloak 将处理请求以响应会话代码并显示登录屏幕。

一旦用户输入凭据并且 keycloak 验证了这些凭据,它将使用授权代码进行响应,并将此代码交换为令牌,然后用户登录。

结论

在这篇文章中,展示了如何使用 Keycloak 作为身份提供者来保护您的 Spring Boot 应用程序。这样 一个简单的程序就完成了!