spring boot整合keycloak

我一开始看的是springboot整合keycloak_keycloak-spring-boot-starter-CSDN博客这个,但是发现adapter被弃用了,现在是使用 Spring Security 与 Keycloak 建立 OpenID 和 OAuth2 连接,而不是依赖 Keycloak 适配器。

要求:spring boot是3.3.1以上的版本(如果你希望用spring boot的更低的版本,只需要修改spring boot项目里面的api,主要是修改Security Config,可以让chatgpt帮你修改)

java版本是21

keycloak是18.0.2

开始keycloak的配置

我用的是docker(docker运行和把keycloak下载到本地运行都可以,如果发现有问题,一般不会出现在这里)

docker run -d -p 8080:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin quay.io/keycloak/keycloak:18.0.2 start-dev

直接把命令输入到终端中,

然后访问
http://localhost:8080

用户名,密码都是admin

1.登录之后,创建一个新的“realm”

我已经创建好了,我的realm叫tom

2.接下来创建client

客户端编号:tomlzkClient

  • 已启用:开启
  • 客户端协议: openid-connect
  • 访问类型:confidential
  • 已启用标准流程:开启
  • 启用直接访问授权:开启
  • 有效的重定向 URI:http://localhost:8081/*

调为client id and secret

复制secret,之后对spring boot的配置有用

3.创建user

这里只输个名字就行

在这里设置密码,把temporary关掉,不然首次登录就要修改密码

这里就keycloak就配置完成了

你也可以验证一下配置的对不对
新建一个client,假设叫myclient,配置如下

access type改为public

然后访问Test application - Keycloak

接下来配置spring boot

pom.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.3.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.moonight</groupId>
    <artifactId>keycloak</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo1</name>
    <description>demo1</description>
    <url/>
    <licenses>
        <license/>
    </licenses>
    <developers>
        <developer/>
    </developers>
    <scm>
        <connection/>
        <developerConnection/>
        <tag/>
        <url/>
    </scm>
    <properties>
        <java.version>21</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</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-security</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

SecurityConfig

package com.example.demo.config;

import com.example.demo.service.CustomOAuth2UserService;
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.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
public class SecurityConfiguration {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .oauth2Login(oauth2Login ->
                        oauth2Login
                                .tokenEndpoint(tokenEndpoint ->
                                        tokenEndpoint.accessTokenResponseClient(accessTokenResponseClient())
                                )
                                .userInfoEndpoint(userInfoEndpoint ->
                                        userInfoEndpoint.userService(customOAuth2UserService())
                                )
                )
                .oauth2Client(oauth2Client ->
                        oauth2Client
                                .authorizationCodeGrant(codeGrant ->
                                        codeGrant.accessTokenResponseClient(accessTokenResponseClient())
                                )
                )
                .sessionManagement(sessionManagement ->
                        sessionManagement.sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
                )
                .authorizeHttpRequests(authorizeRequests ->
                        authorizeRequests
                                .requestMatchers("/unauthenticated", "/oauth2/**", "/login/**").permitAll()
                                .anyRequest().fullyAuthenticated()
                )
                .logout(logout ->
                        logout.logoutSuccessUrl("http://localhost:8080/realms/tom/protocol/openid-connect/logout?redirect_uri=http://localhost:8081/")
                );

        return http.build();
    }

    @Bean
    public OAuth2UserService<OAuth2UserRequest, OAuth2User> customOAuth2UserService() {
        return new CustomOAuth2UserService();
    }

    @Bean
    public OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient() {
        return new DefaultAuthorizationCodeTokenResponseClient(); // 使用默认的令牌响应客户端
    }
}

IndexConfig

package com.example.demo.controller;

import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;

@RestController
public class IndexController {

    @GetMapping(path = "/")
    public HashMap index() {
        // get a successful user login
        OAuth2User user = ((OAuth2User)SecurityContextHolder.getContext().getAuthentication().getPrincipal());
        return new HashMap(){{
            put("hello", user.getAttribute("name"));
            put("your email is", user.getAttribute("email"));
        }};
    }


    @GetMapping(path = "/unauthenticated")
    public HashMap unauthenticatedRequests() {
        return new HashMap(){{
            put("this is ", "unauthenticated endpoint");
        }};
    }
}

customOAuth2UserService

package com.example.demo.service;

import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.oauth2.core.OAuth2Error;


import java.util.Map;

public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {

    private final OidcUserService oidcUserService = new OidcUserService();
    private final DefaultOAuth2UserService defaultOAuth2UserService = new DefaultOAuth2UserService();

    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        if (isOidcProvider(userRequest.getClientRegistration())) {
            // 尝试获取 OIDC ID Token
            OidcIdToken idToken = extractOidcIdToken(userRequest);
            OidcUserRequest oidcUserRequest = new OidcUserRequest(
                    userRequest.getClientRegistration(),
                    userRequest.getAccessToken(),
                    idToken);
            return oidcUserService.loadUser(oidcUserRequest);
        } else {
            return defaultOAuth2UserService.loadUser(userRequest);
        }
    }

    private boolean isOidcProvider(ClientRegistration clientRegistration) {
        return clientRegistration.getProviderDetails()
                .getConfigurationMetadata()
                .containsKey("userinfo_endpoint");
    }

    private OidcIdToken extractOidcIdToken(OAuth2UserRequest userRequest) {
        // 从 userRequest 中获取 OIDC ID Token,这里假设它已经包含在 access token response 的附加参数中
        // 如果不存在,则需要处理这个情况,可能是返回 null 或抛出异常
        Map<String, Object> additionalParameters = userRequest.getAdditionalParameters();
        Object idTokenObj = additionalParameters.get("id_token");

        if (idTokenObj instanceof Map) {
            @SuppressWarnings("unchecked")
            Map<String, Object> idTokenClaims = (Map<String, Object>) idTokenObj;
            return new OidcIdToken(userRequest.getAccessToken().getTokenValue(),
                    userRequest.getAccessToken().getIssuedAt(),
                    userRequest.getAccessToken().getExpiresAt(),
                    idTokenClaims);
        }

        throw new OAuth2AuthenticationException(new OAuth2Error("invalid_id_token"), "Invalid or missing ID token");
    }
}

配置文件application.properties

spring.application.name=demo1
### server port
server.port=8081

## logging
logging.level.org.springframework.security=INFO
logging.pattern.console=%d{dd-MM-yyyy HH:mm:ss} %magenta([%thread]) %highlight(%-5level) %logger.%M - %msg%n

## keycloak(把tom换成自己的keycloak的realm)
spring.security.oauth2.client.provider.external.issuer-uri=http://localhost:8080/realms/tom

# external是标识符
spring.security.oauth2.client.registration.external.provider=external
spring.security.oauth2.client.registration.external.client-name=tomlzkClient
spring.security.oauth2.client.registration.external.client-id=tomlzkClient
spring.security.oauth2.client.registration.external.client-secret=Ah0p8n8R4ZnIHJbWhJ2g24fi3eID7Rio
spring.security.oauth2.client.registration.external.scope=openid,offline_access,profile
spring.security.oauth2.client.registration.external.authorization-grant-type=authorization_code

访问http://localhost:8081/

自动跳转到

登录后

接下来因为session的存在,访问8081端口不会跳转的登录界面

可以在浏览器中删除session,重现登录过程

  • 17
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值