【Spring Security はじめました】#2 ユーザー認証

Web

今回の目標

前回はSpring Securityの導入方法と、簡単なアクセス制御について説明しました。

【Spring Security はじめました 】#1 導入
Spring Securityは、Spring Frameworkの1つで「認証」と「認可」を実装するための仕組みになります。まずは認証と認可の説明と、Spring Securityの導入について簡単に説明をします。

今回は主に次の3つについて説明をしていきます。

  • インメモリによるユーザー認証
  • ユーザー権限によるアクセス制御
  • ユーザー情報の参照

インメモリによる認証

前回の記事では、application.yml(application.properties)に設定したユーザー情報で認証を行いました。Apatch+Tomcatのように設定ファイルに認証情報を設定すようなものもありますが、多くのサービスではユーザーは複数存在し、それぞれが認証を行うと思います。

よく利用されるのはDB認証だと思いますが、今回はインメモリにユーザー情報を設定して認証する方法について説明していきます。

ユーザー情報の設定

ユーザー情報に関する設定は、前回作成したWebSecurityConfigurerAdapterを継承した設定クラスにconfigureメソッドを追加(オーバーライド)します。

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  auth.inMemoryAuthentication()
      .withUser("user").password("password").roles("USER")
      .and()
      .withUser("admin").password("adminpassword").roles("ADMIN");
}

前回もconfigureメソッドをオーバーライドしましたが、引数が異なるため別のメソッドになります(オーバーロード)。

上例では以下のユーザーをインメモリに保存しています。

UsernamePasswordRole(権限)
userpasswordUSER
adminadminpasswordADMIN

権限は文字列で、単純な文字列比較で権限が判断されます。そのためどのような文字を設定してもかまいません。また権限はカンマ区切りで繋げることで複数設定可能です。

パスワードのハッシュ化

では実際に「/login」にアクセスし、上記で登録したユーザー情報で認証します。が、おそらくエラーになるのではないかと思います。

なぜエラーになるのかというと、Spring Securityではパスワードをハッシュ化して認証を行わなければいけません。つまり、「登録されているユーザーのパスワードハッシュ化されてないぞ(#゚Д゚)ゴルァ!!」と怒られているわけです。

なのでパスワードをハッシュ化するための処理を追加します。

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
  auth.inMemoryAuthentication()
      .withUser("user").password(encoder.encode("password")).roles("USER")
      .and()
      .withUser("admin").password(encoder.encode("adminpassword")).roles("ADMIN");	
}

Spring Securityでは、「BCrypt」によるハッシュ化がデフォルトであり、推奨されています。その他の方法も使用できますが、特に理由がない限りはこのままで良いと思います。

これでインメモリに登録したユーザーで認証ができるようになったと思いますので、一度確認してみてください。

ユーザー権限によるアクセス制御

前回の記事では、単純な認証の有無によるページのアクセス制御を行いました。しかし、実際には管理者ユーザーのみアクセスできるページがあったりと、ユーザーの権限によってアクセス制限を設定するケースも多いと思います。

では実際に「/admin」というページに対し、ADMIN権限をもったユーザーのみアクセスできるように制限を設定したいと思います。アクセス制限は前回と同じく、HttpSecurityを引数に持つconfigureメソッドを書き換えてやります。

@Override 
protected void configure(HttpSecurity http) throws Exception { 
  http.authorizeRequests().antMatchers("/home").permitAll()
                          .antMatchers("/admin").hasRole("ADMIN")
                          .anyRequest().authenticated()
      .and().formLogin() 
      .and().httpBasic();

hasRoleメソッドを使うことで引数に指定した権限のみアクセスが可能になります。複数の権限を設定したい場合は、hasAnyRolesメソッドを使用します。

この設定により各ページは次のようなアクセス制限になります。

ページアクセス制限
/home制限なし
/admin認証したADMIN権限を持つユーザー
その他認証したすべてのユーザー

ユーザー情報の参照

では認証されたユーザー情報はいったいどのようにして参照することができるのでしょうか。

ユーザー情報?

そもそもの話、ユーザー情報ってどのような形で管理されているのでしょうか。

細かい話は割愛しますが、インメモリに登録した際に使用したinMemoryAuthenticationメソッドをたどっていくと、UserDetailsManagerConfigurerというクラスが見つかります。このクラスのソースを確認すると、

private final List<UserDetails> users = new ArrayList<>();

と明らかにユーザー情報を管理していそうなフィールドがあります。これを見るにどうやらユーザー情報はUserDetailsに設定されていそうです。ただし、UserDetailsはインターフェースなので、実際にはUserという実装クラスに設定されています。

package org.springframework.security.core.userdetails;

//・・・

public class User implements UserDetails, CredentialsContainer {

  //・・・

  private String password;
  private final String username;
  private final Set<GrantedAuthority> authorities;
  private final boolean accountNonExpired;
  private final boolean accountNonLocked;
  private final boolean credentialsNonExpired;
  private final boolean enabled;

  //・・・
}

認証が行われると、対象のUserインスタンスがセッションに登録されます。つまりセッションに登録されたUserインスタンスが認証されたユーザーの情報となるわけです。

コントローラーからの参照

先程の認証情報ですが、セッションに「SPRING_SECURITY_CONTEXT」という名前で登録されます。しかし、Userインスタンスがそのまま登録されているわけではありません。実際にはSecurityContextImplというクラスのインスタンスが登録されており、この中の情報の1つとしてUserインスタンスが存在しています。

わざわざSercurityContextImplインスタンスからUserインスタンスを取得するのかというと、やっぱりそれは面倒なのでSpringお得意のアノテーションを使って取得することできます。

@GetMapping("admin")
public String admin(@AuthenticationPrincipal User user) {
  System.out.println(user);
  return "admin";
}

@AuthenticationPrincipalを追加することで、セッションから認証されたユーザーの情報として、Userインスタンスを取得することができます。

コンソールに認証されたユーザー情報が表示されることを確認してみてください。

Thymeleafからの参照

認証されたユーザー情報をフロントサイドで使用したい場合、上記で取得したUserインスタンスをaddAttributeで設定することがまず最初に思いつきます。が、実際にはそんなことをせずとも直接参照することができます。

と、その前に少し準備が必要になります。Gradleのdependenciesに以下を追加し再ビルドしてください。ThymeleafでSpring Securityを扱うための拡張機能ぐらいに思ってもらえれば良いかと思います。

dependencies {
  implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5'
}

これによってThymeleafから次のようにしてユーザー情報を参照できるようになります。

<html xmlns:th="http://www.thymeleaf.org">
<body>
  <p th:text="${#authentication.principal.username}"></p>
</body>
</html>

あとがき

今回はインメモリにユーザー情報を設定し、認証する方法について説明しました。またユーザー権限によるページアクセス制御とユーザー情報の参照法についても説明しました。

実際にはDBからユーザー情報を取得して認証を行うのが一般的だと思います。のでまたいつかのタイミングでこちらも記事にできたらと思っています。

 

- Spring Bootのおすすめ書籍はコチラ -

コメント

タイトルとURLをコピーしました