KeyCloak是用于集中身份和访问管理的开源应用程序。提供了许多身份验证方法,可以根据自己的喜好进行自定义。我们如何将我们的春季靴应用程序与百里叶前端连接到KeyCloak?
this separate article在当前版本21.1.1
中的KeyCloak设置。此后,我们的KeyCloak服务器已经在port 8085
上运行,包含一个领域和客户端,并准备连接到我们的Spring Boot应用程序。 oauth/oidc自然是我们的首选 - 用户被重定向到外部登录页面,并在成功身份验证后返回我们的Web应用程序。
以前,有一个单独的弹簧启动适配器用于KeyCloak,但这是弃用的。取而代之的是,我们可以直接使用Spring Security 的Oauth板工具。使用库spring-boot-starter-oauth2-client
,我们可以将应用程序作为KeyCloak的OAuth / OIDC客户端设置。这种方法也可以应用于Okta或Onelogin等其他身份经理。
准备我们的应用程序
我们在Bootify Builder中快速创建一个带有百叶窗的简单春季启动应用程序。 Open your project一键单击并选择首选前端。此外,我们使用两个字符串字段externalId
和email
创建一个实体User
,在那里我们将存储登录的用户 - 稍后将详细介绍。我们的初始项目现在可以直接下载和执行。
现在我们可以开始为KeyCloak增添添加。除了依赖关系org.springframework.boot:spring-boot-starter-oauth2-client
(通过BOM自动提供版本),我们的应用程序还需要许多设置,我们将其添加到我们的application.yml
/ application.properties
。
spring:
security:
oauth2:
client:
provider:
keycloak-bootify:
issuer-uri: http://localhost:8085/realms/bootify
registration:
keycloak-bootify:
client-id: testapp
client-secret: ${KEYCLOAK_CLIENT_SECRET:<<YOUR_CLIENT_SECRET>>}
client-name: Testapp
authorization-grant-type: authorization_code
redirect-uri: http://localhost:8080/login/oauth2/code/testapp
scope: openid,profile,email,offline_access
在provider
后面的区域,我们将keycloak-bootify
添加到我们的应用程序中。通过OIDC issuer-uri
春季启动将检索OAuth Integration 的所有必需信息。您可以在浏览器中打开它以查看提供的内容。
在registration
后面,我们向提供商添加了一个客户。在那里,我们使用客户端ID以及配置KeyCloak服务器时收到的秘密。如果我们使用带有DevServer的前端,则为重定向URL指定端口8081
。准备此后,我们已经可以为弹簧安全性定义中央配置。
@Configuration
public class OAuthSecurityConfig {
private OidcClientInitiatedLogoutSuccessHandler oidcLogoutSuccessHandler() {
final OidcClientInitiatedLogoutSuccessHandler oidcLogoutSuccessHandler =
new OidcClientInitiatedLogoutSuccessHandler(this.clientRegistrationRepository);
oidcLogoutSuccessHandler.setPostLogoutRedirectUri("{baseUrl}?logoutsuccess=true");
return oidcLogoutSuccessHandler;
}
@Bean
public SecurityFilterChain configure(final HttpSecurity http) throws Exception {
return http.cors(withDefaults())
.csrf((csrf) -> csrfwithDefaults())
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/", "/css/**", "/js/**", "/images/**", "/webjars/**").permitAll()
.anyRequest().hasAuthority(UserRoles.ROLE_USER))
.oauth2Login(withDefaults())
.logout((logout) -> logout
.logoutSuccessHandler(oidcLogoutSuccessHandler())
.deleteCookies("JSESSIONID"))
.build();
}
}
- 我们的OAuth/OIDC
的中央设置自Spring Boot 3.0以来,必须以HttpSecurity
类配置为SecurityFilterChain
。在启用CORS保护后,我们定义了一些例外(例如http://localhost:8080
的"/"
),期望其他所有内容的角色"ROLE_USER"
。身份验证是使用OAuth 2登录完成的。此外,我们已经定义了自己的注销处理程序,该处理程序在成功的注销后将我们重定向回主页。
角色映射
与KeyCloak的连接现在应该有效,但是在登录后仍无法访问保护区。根据我们的KeyCloak设置,角色已经作为ID令牌的一部分提供了,但尚未读出。因此,我们必须使用角色映射。
扩展我们的配置
public class OAuthSecurityConfig {
// ...
/**
* Custom mapper to use OIDC claims as Spring Security roles.
*/
@Bean
public GrantedAuthoritiesMapper userAuthoritiesMapper() {
return (authorities) -> {
final Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
authorities.forEach((authority) -> {
if (authority instanceof OidcUserAuthority oidcAuth) {
mappedAuthorities.addAll(mapAuthorities(oidcAuth.getIdToken().getClaims()));
} else if (authority instanceof OAuth2UserAuthority oauth2Auth) {
mappedAuthorities.addAll(mapAuthorities(oauth2Auth.getAttributes()));
}
});
return mappedAuthorities;
};
}
/**
* Read claims from attribute realm_access.roles as SimpleGrantedAuthority.
*/
private List<SimpleGrantedAuthority> mapAuthorities(final Map<String, Object> attributes) {
final Map<String, Object> realmAccess = ((Map<String, Object>)attributes.getOrDefault("realm_access", Collections.emptyMap()));
final Collection<String> roles = ((Collection<String>)realmAccess.getOrDefault("roles", Collections.emptyList()));
return roles.stream()
.map((role) -> new SimpleGrantedAuthority(role))
.toList();
}
}
提供GrantedAuthoritiesMapper
这通过我们的配置提供了GrantedAuthoritiesMapper
bean,该配置将自动通过春季安全拾取。这读了realm_access.roles
字段的角色,并将其转换为SimpleGrantedAuthority
。如果我们立即启动应用程序并转到保护区,则应自动重定向到KeyCloak。注册/登录后,我们将其发送回我们的应用程序,我们已成功身份验证并具有所需的角色。
OAuth用户与数据库的同步
身份验证的用户主要连接其他业务逻辑,例如存储地址或个人数据。因此,每次登录后,将用户与数据库同步是有意义的。为此,我们将UserSynchronizationService
添加到我们的Spring Boot应用程序中。
@Service
public class UserSynchronizationService {
// ...
private void syncWithDatabase(final OidcUserInfo userInfo) {
User user = userRepository.findByExternalId(userInfo.getSubject());
if (user == null) {
log.info("adding new user after successful login: {}", userInfo.getSubject());
user = new User();
user.setExternalId(userInfo.getSubject());
} else {
log.info("updating existing user after successful login: {}", userInfo.getSubject());
}
user.setEmail(userInfo.getEmail());
userRepository.save(user);
}
@EventListener(AuthenticationSuccessEvent.class)
public void onAuthenticationSuccessEvent(final AuthenticationSuccessEvent event) {
final OidcUser oidcUser = ((OidcUser)event.getAuthentication().getPrincipal());
syncWithDatabase(oidcUser.getUserInfo());
}
}
创建或更新数据库中的用户
此服务对的AuthenticationSuccessEvent
响应,该AuthenticationSuccessEvent
会在用户通过OAuth登录后自动触发。即使其他用户数据已更改,每个用户都会由"subject"
字段唯一识别。然后,新的或现有的User
对象被当前的电子邮件填充并持续。
在Bootify的免费计划中,可以使用自己的数据库模式和CRUD功能初始化当前版本3.1.0
中的Spring Boot应用程序。在专业计划中,还可以配置弹簧安全性 - 在这里包括KeyCloak作为选项。这将提供此处描述的设置,该设置已定制为所选设置。