我一开始看的是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
自动跳转到
登录后
接下来因为session的存在,访问8081端口不会跳转的登录界面
可以在浏览器中删除session,重现登录过程