はじめに
Spring Boot ベースのWebアプリケーションで、Keycloakを標準のまま(ユーザーストレージなどのカスタマイズなし)利用していると、大文字のユーザー名でも、小文字で保存されてしまう問題に直面しました。他の方の記事にもあるように、これが標準の動きっぽい。
システムのユーザー名は大文字英数字で構成されており、トークンの「preferred_username」クレームの値を画面表示などに利用しているため、回避策を調べました。
実行環境
- keycloak 12.0.4
- Java11
- Spring Security 5.4.5
- Spring Boot 2.4.3
回避策
Spring Security のカスタマイズ箇所を利用し、ユーザ名を大文字に変換するOidcUserServiceのサブクラスを用意しました。
OidcUserServiceのサブクラス
package demo.keycloak;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.stereotype.Component;
@Component
public class UpperCaseOidcUserService extends OidcUserService {
@Override
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
OidcUser originalUser = super.loadUser(userRequest);
System.out.println("[UpperCaseOidcUserService] originalUser=" + originalUser.getPreferredUsername());
OidcUser upperCaselUser = new UpperCaseOidcUser(originalUser);
System.out.println("[UpperCaseOidcUserService] upperCaselUser=" + upperCaselUser.getPreferredUsername());
return upperCaselUser;
}
/**
* preferred_usernameを大文字にするUserクラス
*/
public static class UpperCaseOidcUser extends DefaultOidcUser {
public UpperCaseOidcUser(OidcUser originalUser) {
super(originalUser.getAuthorities(), originalUser.getIdToken(), originalUser.getUserInfo());
}
@Override
public String getPreferredUsername() {
return super.getPreferredUsername().toUpperCase();
}
}
}
ユーザ名を表示するController
package demo.keycloak;
import org.springframework.security.core.context.SecurityContextHolder;
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;
import org.springframework.web.bind.annotation.RequestMapping;
import lombok.RequiredArgsConstructor;
@Controller
@RequestMapping("top")
@RequiredArgsConstructor
public class TopController {
@GetMapping
public String top(Model model) {
var principal = (OidcUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
System.out.println("[TopController] user=" + principal.getPreferredUsername());
return "top";
}
}
検証結果
コンソール
[UpperCaseOidcUserService] originalUser=user1
[UpperCaseOidcUserService] upperCaselUser=USER1
[TopController] user=USER1
Keycloakが内部で保存する値は小文字のままですが、アプリーケーションに返却する値はこれで大文字になりました。他にも、Keycloak側の設定で回避する術もあるみたいですが、そこは検証できたら追記したい。