spring boot 3.2 整合 keycloak

背景

项目中用到 keycloak,因此其他所有管理页面要集成 keycloak 做统一登录认证。

Keycloak 侧配置

容器方式启动 keycloak 服务端

docker run -d --name mykeycloak -p 8080:8080         -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin         keycloak         start-dev

注意如果不能联网的话需要要提前下载好镜像。

创建域

启动后,浏览器登录 http://ip:8080,默认用户名密码:admin/admin
创建域:client
spring boot 3.2 整合 keycloak_第1张图片
spring boot 3.2 整合 keycloak_第2张图片

创建客户端

spring boot 3.2 整合 keycloak_第3张图片
打开客户端认证
spring boot 3.2 整合 keycloak_第4张图片
设置登录成功后的重定向地址(这里可以添加多个)
spring boot 3.2 整合 keycloak_第5张图片
获取密钥,这个在配置 spring boot 时需要
spring boot 3.2 整合 keycloak_第6张图片

创建用户

这里的用户用于统一认证。
spring boot 3.2 整合 keycloak_第7张图片
设置登录密码
spring boot 3.2 整合 keycloak_第8张图片

Spring boot 侧实现

keycloak 有好几种方式集成 spring boot,例如(https://www.keycloak.org/docs/latest/securing_apps/index.html):
spring boot 3.2 整合 keycloak_第9张图片

之前使用的是 Adapter 方式,但是官方已经废弃了,现在统一用 oauth2。

引入依赖

<dependency>
  <groupId>org.springframework.bootgroupId>
  <artifactId>spring-boot-starter-oauth2-clientartifactId>
dependency>
<dependency>
	<groupId>org.springframework.bootgroupId>
	<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
	<groupId>org.springframework.bootgroupId>
	<artifactId>spring-boot-starter-securityartifactId>
dependency>

如果没有特殊要求,则直接使用 spring boot 中配置的版本即可,不用单独设置版本。

添加 Security Filter

package casuallc.github.io;

import static casuallc.github.io.global.Constants.IGNORE_AUTHENTICATION_RESOURCES;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;

@Configuration
@EnableWebSecurity
@Slf4j
public class CustomSecurityConfiguration {

    @Value("${security.enabled}")
    private boolean securityEnabled;
    @Value("${security.same-site.enabled}")
    private boolean securitySameSiteEnabled;
    @Value("${security.keycloak.enabled:false}")
    private boolean keyCloakEnabled;
    @Value("${security.oauth2.enabled:false}")
    private boolean oauth2Enabled;
    @Value("${security.oauth2.logout.url:}")
    private String oauth2LogoutUrl;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        if (!securitySameSiteEnabled) {
            http.headers(AbstractHttpConfigurer::disable);
        } else {
            http.headers(header -> header.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin));
        }

        if (securityEnabled) {
            CookieCsrfTokenRepository csrfTokenRepository = new CookieCsrfTokenRepository();
            csrfTokenRepository.setCookieCustomizer(cookie -> cookie.secure(true)
                    .maxAge(86400));
            http.csrf(csrf -> csrf.ignoringRequestMatchers(IGNORE_AUTHENTICATION_RESOURCES)
                    .csrfTokenRepository(csrfTokenRepository));
            return http.build();
        }

        // 使用其他方式认证时关闭 csrf
        http.csrf(AbstractHttpConfigurer::disable);

        if (keyCloakEnabled || oauth2Enabled) {
            http.authorizeHttpRequests(a -> a
                            .requestMatchers(IGNORE_AUTHENTICATION_RESOURCES)
                            .permitAll()
                            .anyRequest()
                            .authenticated())
                    .oauth2Login(Customizer.withDefaults());
            if (StringUtils.isNotBlank(oauth2LogoutUrl)) {
                http.logout(r -> r.logoutSuccessUrl(oauth2LogoutUrl));
            }
            log.info("{} enabled ...", keyCloakEnabled ? "Keycloak" : "Oauth2");
            return http.build();
        }

        log.info("Security CSRF disabled ...");
        return http.build();
    }
}

IGNORE_AUTHENTICATION_RESOURCES 配置了哪些请求不需要认证,比如登录地址。

添加认证配置

### --- 认证配置(需要关闭security.same-site.enabled和security.enabled) --- ###
security.keycloak.enabled=true
spring.security.oauth2.client.provider.external.issuer-uri=http://keycloak:8080/realms/test

spring.security.oauth2.client.registration.external.provider=external
# 客户端名称
spring.security.oauth2.client.registration.external.client-name=client
# 客户端名称
spring.security.oauth2.client.registration.external.client-id=client
spring.security.oauth2.client.registration.external.client-secret=配置在 keycloak client 页面获取的密钥
spring.security.oauth2.client.registration.external.scope=openid,offline_access,profile
spring.security.oauth2.client.registration.external.authorization-grant-type=authorization_code

获取登录用户

在使用 keycloak 登录后,在 request 中可以获取到用户信息

public static String getUserFromOauth2(HttpServletRequest request) {
	OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) request.getUserPrincipal();
	if (token == null) {
		log.warn("Can not get keycloak user from request.");
		return null;
	}
	OAuth2User user = token.getPrincipal();
	if (user instanceof DefaultOidcUser oidcUser) {
		return oidcUser.getPreferredUsername();
	}
	if (user instanceof DefaultOAuth2User oAuth2User) {
		return oAuth2User.getName();
	}
	return user.getName();
}

最后

以上操作完成后,可以访问 spring boot 项目,会自动跳转到 keycloak 登录页面,登录成功后重定向到 spring boot 项目页面。

你可能感兴趣的:(spring,boot,后端,java)