SpringBootでログイン中のユーザーのUserDetailsクラスを継承したクラスの情報を取得したい

###2021/07/25に追記しました
最後に簡単な方法を追記しました。

###環境
Spring Boot 2.4.0
java 1.8
thymeleaf 3.0.11
Spring Security 2.4.0


###実現したいこと
ログイン中のユーザーのUSERDETAILSを継承したクラスの情報を取得したい。

###いろいろ試してなんとか実現
ほぼ丸一日かかりました。というかちゃんとSpringBootを理解していないのだとは思いますが、いろんなサイトを見て試してみてなんとかうまく行ったので、記録として残しておきます。

###USERDETAILSを継承しているクラス

```java
Tenantsクラス
Staffクラス
```

###ログイン認証
MyUserDetailsServiceクラスのloadUserByUsernameメソッドで、返すクラスを分けています。

```java:MyUserDetailsServiceクラス
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		// 引数がnullなら例外を投げる
    if (Objects.isNull(username)) {
      throw new UsernameNotFoundException("username is empty");
	  }
    Tenants tenants = tenantRepository.findByAdminId(username);
    Staff staff = staffRepository.findByEmail(username);
    // nullが返ってきたら例外を投げる
    if(tenants != null) {
    	return tenants;
    } else if(staff != null) {
    	return staff;
    } else {
    	throw new UsernameNotFoundException("Not found username: " + username);
    }

	}

```

###コントローラー
ログイン後のトップ画面を表示するコントローラーで、今どのクラスでログイン認証されているか表示したい

いろいろ試した結果、下の実装で、どのクラスで認証されているか取得できるようになりました。

```java
@GetMapping("/top")
public ModelAndView userTop(eModelAndView mv, Principal principal) {
	
        // ログイン中のユーザーのUSERDETAILSを継承したクラスを取得する
	Authentication auth = (Authentication) principal;
	String authClass = auth.getPrincipal().getClass().getSimpleName();
	if(authClass.equals("Tenants")) {
		System.out.println("テナントクラスだぜ！！");
		String tenantId = ((Tenants)auth.getPrincipal()).getId().toString();
		httpServletResponse.addCookie(new Cookie("tenantId", tenantId));
	}
	if(authClass.equals("Staff")) {
		System.out.println("スタッフクラスやっちゅうねん！！");
		String tenantId = ((Staff)auth.getPrincipal()).getTenantId().toString();
		httpServletResponse.addCookie(new Cookie("tenantId", tenantId));
	}
}
```

###ハマったところ
PrincipalやAuthenticationなどの使いかたがよくわからなくて、
・引数の渡し方
・@AuthenticationPrincipalアノテーションの使い方
・キャストが必要？
・PrincipalにTenantsクラス入ってるけど、フィールドが取り出せないよ？
・getClass()がコード・アシスタントで出てくるのにエラーが出て実行できないよ？
などなど。。。

どうやらPrincipalにはUserDetailsを継承したクラスの、認証がうまく行ったときのレコードが全部入っているようでした。

しかし、username以外は取り出せないということに。。。

Principalを使えば良いとばかり思っていたけど、どうやら違った。

Authenticationクラスの中にPrincipalクラスが入っていて、Principalの中にUserDetailsクラスが入っているイメージでした。（合っているかわかりません）

ただそのイメージで

```java
// コントローラーの引数に渡しているPrincipalからまずAuthenticationを取得する
Authentication auth = (Authentication) principal;
String authClass = auth.getPrincipal() // Principalを取得
   .getClass() // クラスを取得
   .getSimpleName(); // パッケージ名を除外
```
この流れでうまくいきました。

最初の
`Principal → Authentication`へのキャストが必要なの？？って感じでしたが、これをやらないとうまく行かなかった。


###thymeleafでビューで表示する方法

```html
<div sec:authentication="principal"></div>
<div th:text="${#authentication.principal}"></div>
```
いずれも同じ内容が表示されます。コントローラーで取得するようりも遥かに簡単に取得できます。

そしてちゃんとprincipalにはUserDetailsが入っているのが確認できます。


###まとめ

* コントローラー引数に Principal principalを渡す
* Authenticationから.getPrincipal()でPrincipalを取得
* thymeleafでは`<div sec:authentication="principal"></div>`
* または`<div th:text="${#authentication.principal}"></div>`


```java
// コントローラー
// 引数に Principal principalを渡す
public ModelAndView userTop(ModelAndView mv, Principal principal) {
// コントローラー
// 引数に Principal principalを渡す
Authentication auth = (Authentication) principal;
// AuthenticationからPrincipalを取得
String authClass = auth.getPrincipal()
   .getClass()
   .getSimpleName();
```


2021/07/25追記
###もっと簡単な方法がありました

####前提
User.javaクラスはUserDetailsを継承している。

###コントローラーで認証したUserを取得する

```java
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.annotation.AuthenticationPrincipal;

public String userTop(
  HttpServletResponse httpServletResponse,
  @AuthenticationPrincipal User user) {
  
  //  ユーザIDをクッキーに保存
  httpServletResponse.addCookie(new Cookie("userId", user.getId().toString()));

  return "top";
}

```

つまり、コントローラーのメソッドの引数の
` @AuthenticationPrincipal User user`
の時点ですでにUserクラスにキャストされているので、自由に取り出せる！！