Works by

Ren's blog

@rennnosuke_rk 技術ブログです

【Spring】@AuthenticationPrincipal経由でセッションユーザ情報を取得する際のチェック事項

f:id:rennnosukesann:20181220183020p:plain:w300

@AuthenticationPrincipal

Spring Securityでは、セッションユーザ情報をシンプルに取得できるアノテーション @AuthenticationPrincipal が存在します。

Javadocより

メソッドのパラメーターまたはメソッドの戻り値に Authentication.getPrincipal()をバインドするアノテーションです。

型が UserDetails 実装型であるメソッドの引数、あるいは戻り値に付与することで、Authentication.getPrincipal()の呼び出し結果を埋め込んでくれるアノテーションです。

UserDetails はSpring Securityにおいてユーザ被認証主体の情報を保持する役割を持ち、Spring Security上では UserDetails の持つ情報を使って認証・認可の仕組みを提供しますが、その UserDetails オブジェクトを簡単に取得できます。

よくあるのが、Controllerメソッドの引数に埋め込んでログイン済みユーザ情報を使用するシーンです。

@RestController
@RequestMapping("/users")
class UserController {
    /**
     * ユーザ取得 - 【GET】/users/me
     */
    @GetMapping("/me")
    fun get(@AuthenticationPrincipal user: User): UserResponse = UserResponse.create(user) // Userからユーザ情報レスポンスを作成

}

上記Controllerクラスではログインユーザ情報を取得するAPIを実装していますが、
その際に @AuthenticationPrincipal で取得したログインユーザ情報を使用しています。

@AuthenticationPrincipal を使用するための準備

このように便利なアノテーションですが、利用のために幾つか下準備が必要です。
よく失念するので、手順の再確認も兼ねてメモしました。

UserDetails 実装クラスの定義

何はともあれインスタンスのクラスがないことには始まりません。
Spring Securityでのユーザ認証・認可を利用する場合、既に作成していることと思います。

data class User(
    val email: String,
    val pass: String,
    val roleType: RoleType,
) : UserDetails {

    override fun getAuthorities(): MutableCollection<out GrantedAuthority> {
        return AuthorityUtils.createAuthorityList(roleType.name)
    }

    override fun isEnabled(): Boolean {
        return true
    }

    override fun getUsername(): String {
        return email
    }

    override fun isCredentialsNonExpired(): Boolean {
        return true
    }

    override fun getPassword(): String {
        return pass
    }

    override fun isAccountNonExpired(): Boolean {
        return true
    }

    override fun isAccountNonLocked(): Boolean {
        return true
    }

}

UserDetailsService 実装の定義

UserDetails オブジェクトをハンドルするサービスクラスも定義します。
既にSpring Securityが提供している UserDetailsServiceを実装したServiceクラスを定義します。
loadUserByUsername をオーバーライドし、先程作成した UserDetails 実装クラスオブジェクトを提供するロジックを記述します。

@Service
class UserDetailsServiceImpl : UserDetailsService {

    @Throws(UsernameNotFoundException::class)
    override fun loadUserByUsername(email: String?): UserDetails {
        // Userクラスオブジェクトを返す
    }

}

WebSecurityConfigurerAdapter 派生クラスで UserService を登録

WebSecurityConfigurerAdapter 派生クラスを定義し、 configure メソッド内で先程の UserService を登録します。
派生クラスはBeanとして登録し、@EnableWebSecurity も忘れずに。

@Configuration
@EnableWebSecurity
class SecurityConfig(
    @Autowired private val userDetailsService: UserDetailsService,
) : WebSecurityConfigurerAdapter() {

    override fun configure(auth: AuthenticationManagerBuilder?) {
        auth?.userDetailsService(userDetailsService)
    }
...
}

呼び出し

あとは実際に呼び出してみます。null でなければOK。

@RestController
@RequestMapping("/users")
class UserController {
    /**
     * ユーザ取得 - 【GET】/users/me
     */
    @GetMapping("/me")
    fun get(@AuthenticationPrincipal user: User): UserResponse = UserResponse.create(user) // Userからユーザ情報レスポンスを作成

}

参考文献

docs.spring.io