Keycloakで認証したユーザー名を大文字にする

# はじめに
Spring Boot ベースのWebアプリケーションで、Keycloakを標準のまま（ユーザーストレージなどのカスタマイズなし）利用していると、大文字のユーザー名でも、小文字で保存されてしまう問題に直面しました。[他の方の記事](https://qiita.com/rururu_kenken/items/7b334275560e4520863f)にもあるように、これが標準の動きっぽい。
システムのユーザー名は大文字英数字で構成されており、トークンの「preferred_username」クレームの値を画面表示などに利用しているため、回避策を調べました。

## 実行環境
- keycloak 12.0.4
- Java11
- Spring Security 5.4.5
- Spring Boot 2.4.3

# 回避策
Spring Security のカスタマイズ箇所を利用し、ユーザ名を大文字に変換する[OidcUserService](https://spring.pleiades.io/spring-security/site/docs/current/api/org/springframework/security/oauth2/client/oidc/userinfo/OidcUserService.html)のサブクラスを用意しました。

```java: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();
		}
	}
}
```

```java:ユーザ名を表示する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";
	}
}
```

# 検証結果
ログイン画面（ユーザー名＝user1）
![login.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2602859/7b5ec4b6-576b-01a1-0a2c-1063c3685073.png)

```:コンソール
[UpperCaseOidcUserService] originalUser=user1
[UpperCaseOidcUserService] upperCaselUser=USER1
[TopController] user=USER1
```

Keycloakが内部で保存する値は小文字のままですが、アプリーケーションに返却する値はこれで大文字になりました。他にも、Keycloak側の設定で回避する術もあるみたいですが、そこは検証できたら追記したい。

# 参考記事
- https://qiita.com/kazuki43zoo/items/53804e18337933a77ad0
- https://stackoverflow.com/questions/69553369/how-to-access-the-original-case-sensitive-username-input-in-custom-user-storage