Keycloak 和 Spring Boot:实现单点登录的终极指南

2025-06-04

Keycloak 和 Spring Boot:实现单点登录的终极指南

介绍:

单点登录 (SSO)已成为现代 Web 应用程序的必备功能,它不仅提升了用户体验,还增强了安全性。本指南将指导您使用 Keycloak 和 Spring Boot 实现 SSO,为您的应用程序提供强大的身份验证和授权解决方案。

Keycloak 单点登录的重要性

单点登录 (SSO) 对于简化身份验证流程、增强安全性和提升用户体验至关重要。以下是一些主要优势:

  1. 集中式身份验证:SSO 允许用户只需进行一次身份验证即可访问多个应用程序。Keycloak 提供用户身份的集中管理,这在具有众多应用程序的环境中非常有用。

  2. 安全性提升:通过集中式身份管理,可以统一执行安全策略(例如密码强度、双因素身份验证和帐户锁定策略)。Keycloak 支持 OpenID Connect 和 OAuth 2.0 等协议,确保其符合强大的现代安全标准。

  3. 减少密码疲劳并增强用户体验:只需登录一次,用户即可避免密码疲劳和多重凭证,从而实现跨应用程序的更顺畅、更快速的交互。

  4. 可扩展性和灵活性:Keycloak 的配置可以支持大量用户和多个身份提供者,包括社交登录(Google、Facebook 等)和企业目录(LDAP、Active Directory)。

  5. 定制化和可扩展性:Keycloak 支持自定义主题、登录流程和扩展,能够适应各种需求。此外,它还是一个开源平台,为企业提供了根据需要修改或扩展平台的灵活性。

单点登录(SSO)的替代方案:

  1. 多重登录/传统身份验证:

    • 用户对每个应用程序或服务都有单独的凭据
    • 需要单独登录每个系统
    • 每个应用程序管理自己的身份验证
  2. 联合身份:

    • 与 SSO 类似,但允许跨不同组织进行身份验证
    • 使用 SAML 或 OpenID Connect 等标准
    • 用户身份已由其所在组织验证
  3. 多重身份验证 (MFA):

    • 除了用户名和密码之外,还增加了额外的安全层
    • 可与 SSO 或传统身份验证一起使用
    • 通常涉及你所了解、拥有和正在

SSO 流程说明:

在深入实现之前,让我们先了解一下 SSO 流程:

SSO 流程

先决条件:

步骤 1:项目设置

使用Spring Initializr或 intelliJ 创建具有以下结构的新 Spring Boot 项目:

keycloak-demo/
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/
│   │   │       └── bansikah/
│   │   │           └── keycloakdemo/
│   │   │               ├── config/
│   │   │               │   └── SecurityConfig.java
│   │   │               ├── controller/
│   │   │               │   └── FoodOrderingController.java
│   │   │               └── KeycloakDemoApplication.java
│   │   └── resources/
│   │       ├── templates/
│   │       │   ├── home.html
│   │       │   └── menu.html
│   │       └── application.yml
├── docker-compose.yml
└── pom.xml
Enter fullscreen mode Exit fullscreen mode

笔记:

bansikah是我的名字😂所以你可以放上你的名字或者举任何你想要的例子……

第2步:配置pom.xml

将以下依赖项添加到您的依赖项中pom.xml,或者您可以只替换依赖项部分以避免冲突:

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras-springsecurity3 -->
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity3</artifactId>
            <version>3.0.5.RELEASE</version>
        </dependency>
    </dependencies>
Enter fullscreen mode Exit fullscreen mode

步骤 3:使用 Docker 设置 Keycloak

docker-compose.yml在根目录中创建文件:

version: '3'

services:
  keycloak:
    image: quay.io/keycloak/keycloak:latest
    environment:
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: admin
    ports:
      - "8088:8080"
    command:
      - start-dev

  app:
    build: .
    ports:
      - "8082:8082"
    depends_on:
      - keycloak
Enter fullscreen mode Exit fullscreen mode

使用以下命令运行 Keycloak 服务器:

docker-compose up -d
Enter fullscreen mode Exit fullscreen mode

步骤4:配置Keycloak

  1. 访问 Keycloak 管理控制台:

  2. 创建新领域:

    • 前往左上角的“Master”
    • 选择“添加领域”
    • 命名food-ordering-realm
    • 点击“创建”
  3. 创建新客户:
    在第一个屏幕上:

    • 将“客户端 ID”设置为“food-ordering-client”
    • 客户端类型:选择“OpenID Connect”
    • 点击“下一步”

在下一个屏幕(功能配置)上:

  • 客户端身份验证:启用此功能(取代旧的“机密”设置)
  • 授权:除非您需要细粒度的授权,否则可以将其关闭
  • 点击“下一步”
  1. 客户端配置:
    • 将根 URL 设置为http://localhost:8082/
    • 将访问类型设置为机密
    • 添加有效的重定向 URI(每个 URI 占一行):
 http://localhost:8082/
 http://localhost:8082/menu
 http://localhost:8082/login/oauth2/code/keycloak
Enter fullscreen mode Exit fullscreen mode

图片一

  1. 检索客户端机密:
    • 转到Credentials标签页
    • 复制Secret字段的值以用于应用程序配置

图片二

  1. 创建用户:
    • 前往Users并点击“添加用户”
    • 设置用户名(例如,testuser)
    • Credentials标签中:
      • 设置密码
      • 禁用“临时”

图三
并设置密码:

图片四

步骤5:配置Spring Boot应用程序

创建application.ymlsrc/main/resources

server:
  port: 8082

spring:
  application:
    name: keycloak-demo
  security:
    oauth2:
      client:
        registration:
          keycloak:
            client-id: food-ordering-client
            client-secret: your-client-secret
            scope: openid,profile,email
            redirect-uri: http://localhost:8082/login/oauth2/code/keycloak
        provider:
          keycloak:
            issuer-uri: http://localhost:8088/realms/food-ordering-realm

logging:
  level:
    org.springframework.security: DEBUG
    org.springframework.security.oauth2: DEBUG

Enter fullscreen mode Exit fullscreen mode

<your-client-secret>用从 Keycloak 复制的密钥替换,通常是一些随机文本。
注意:

在生产中或作为一种良好的做法,最好将此类敏感信息保存在.env项目根目录的文件中,并将其用作配置中的变量,它将类似于 ${CLIENT_SECRET},当您启动应用程序时,它会从 .env 文件中选择它,这也适用于重定向、issuer-uri 和其余部分......

步骤6:创建安全配置

创建SecurityConfig.javasrc/main/java/com/bansikah/keycloakdemo/config

package com.bansikah.keycloakdemo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;

/**
 * SecurityConfig class configures security settings for the application,
 * enabling security filters and setting up OAuth2 login and logout behavior.
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    /**
     * Configures the security filter chain for handling HTTP requests, OAuth2 login, and logout.
     *
     * @param http HttpSecurity object to define web-based security at the HTTP level
     * @return SecurityFilterChain for filtering and securing HTTP requests
     * @throws Exception in case of an error during configuration
     */
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                // Configures authorization rules for different endpoints
                .authorizeHttpRequests(authorize -> authorize
                        .requestMatchers("/").permitAll() // Allows public access to the root URL
                        .requestMatchers("/menu").authenticated() // Requires authentication to access "/menu"
                        .anyRequest().authenticated() // Requires authentication for any other request
                )
                // Configures OAuth2 login settings
                .oauth2Login(oauth2 -> oauth2
                        .loginPage("/oauth2/authorization/keycloak") // Sets custom login page for OAuth2 with Keycloak
                        .defaultSuccessUrl("/menu", true) // Redirects to "/menu" after successful login
                )
                // Configures logout settings
                .logout(logout -> logout
                        .logoutSuccessUrl("/") // Redirects to the root URL on successful logout
                        .invalidateHttpSession(true) // Invalidates session to clear session data
                        .clearAuthentication(true) // Clears authentication details
                        .deleteCookies("JSESSIONID") // Deletes the session cookie
                );

        return http.build();
    }
}


Enter fullscreen mode Exit fullscreen mode

步骤 7:创建控制器

创建FoodOrderingController.javasrc/main/java/com/bansikah/keycloakdemo/controller

package com.bansikah.keycloakdemo.controller;

import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

/**
 * FoodOrderingController handles web requests related to the home and menu pages of the food ordering application.
 */
@Controller
public class FoodOrderingController {

    /**
     * Maps the root URL ("/") to the home page.
     *
     * @return the name of the view to render for the home page
     */
    @GetMapping("/")
    public String home() {
        return "home";
    }

    /**
     * Maps the "/menu" URL to the menu page and sets the authenticated user's username in the model.
     *
     * @param user  the authenticated OIDC (OpenID Connect) user
     * @param model Model object for passing data to the view
     * @return the name of the view to render for the menu page, or redirects to home if user is not authenticated
     */
    @GetMapping("/menu")
    public String menu(@AuthenticationPrincipal OidcUser user, Model model) {
        if (user != null) {
            model.addAttribute("username", user.getPreferredUsername());
        } else {
            return "redirect:/";  // Redirect to home if not authenticated
        }
        return "menu";
    }
}

Enter fullscreen mode Exit fullscreen mode

步骤8:创建HTML模板

创建home.htmlsrc/main/resources/templates

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Welcome to Food Ordering</title>
</head>
<body>
<h1>Welcome to Food Ordering</h1>
<p>Click <a th:href="@{/menu}">here</a> to view the menu (requires login).</p>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

创建menu.htmlsrc/main/resources/templates

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Food Ordering Menu</title>
</head>
<body>
<h1>Welcome to the Menu, <span th:text="${username}"></span>!</h1>
<p>Here's our menu (placeholder):</p>
<ul>
    <li>Pizza - $10</li>
    <li>Burger - $8</li>
    <li>Salad - $6</li>
</ul>
<form th:action="@{/logout}" method="post">
    <input type="submit" value="Logout"/>
</form>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

步骤9:运行应用程序

您应该能够通过此 URL http://localhost:8082访问该应用程序,并且您应该看到以下内容

图片五
当你点击here链接时,它会带你到 keycloak 表单,用户必须使用用户名和密码进行身份验证foodorder realm

图片六

验证后,您将看到menu页面

图片七

现在有了 SSO 的重要性,即使我注销,我也不必像下面这样再次登录

图片八
然后我可以再次点击该链接,并且不会像下面那样提示我再次输入密码和用户名

图片九

工作原理:
Keycloak 在用户登录时发放访问令牌和刷新令牌。对于单点登录 (SSO),它会在域内已授权的应用程序之间共享这些令牌,使用户无需重新身份验证即可访问多个应用程序。访问令牌过期后,刷新令牌将用于续订,从而保持会话在各个应用程序之间保持活动状态。

结论

恭喜😊,感谢您一直以来的关注。
此实现演示了使用 Keycloak 和 Spring Boot 的强大 SSO 解决方案。它在保持安全性的同时,提供了无缝的身份验证体验。该配置允许轻松定制和扩展,以满足特定的应用程序需求。
如果您遇到任何问题或对此实现有任何疑问,请随时在下方留言。请记住查看 Spring Security 和 Keycloak 文档,了解更多高级配置和功能。
代码链接:GitHub 上。
您也可以访问此代码库,使用 Keycloak SSO 进行 ping 服务器应用程序身份验证,以查看全栈 Spring Boot 和 React 应用程序中的实现。

参考:

文章来源:https://dev.to/bansikah/keycloak-and-spring-boot-the-ultimate-guide-to-implementing-single-sign-on-1af7
PREV
别再用 ChatGPT 帮你写博客文章了!它根本没用……
NEXT
介绍 React-Redux 使用 Hooks (useSelector && useDispatch)