【Spring Security はじめました】#3 DB認証

Web

今回の目標

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

【Spring Security はじめました】#2 ユーザー認証
Spring Securityによるユーザー認証の第一歩として、インメモリを用いた認証について説明をしていきます。またユーザーに与えられた権限を基にしたページアクセス制御や認証したユーザー情報の参照方法についても触れていきます。

今回はインメモリではなくDBからユーザー情報を取得して認証する方法について説明していきます。

ユーザー情報の検索

最初におさらいですが、インメモリにユーザー情報を設定するには、設定クラスの以下のcofigureメソッドをオーバーライドしました。

@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");  
}

インメモリに登録さたユーザー情報を検索し、該当のユーザーが存在すれば認証されたことになり、セッションにユーザー情報が登録されます。

では、ユーザー情報は何によって検索されているのでしょうか?

UserDetailsService

configureメソッドの引数であるAuthenticationManagerBuilderのソースを確認してみます。

public class AuthenticationManagerBuilder
    extends
    AbstractConfiguredSecurityBuilder<AuthenticationManager, AuthenticationManagerBuilder>
    implements ProviderManagerBuilder<AuthenticationManagerBuilder> {
  private final Log logger = LogFactory.getLog(getClass());

  private AuthenticationManager parentAuthenticationManager;
  private List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
  private UserDetailsService defaultUserDetailsService;
  private Boolean eraseCredentials;
  private AuthenticationEventPublisher eventPublisher;

  //以下省略
}

実はこのフィールドの中にユーザー情報を検索する役割を担っているものがあります。それがUserDetailsServiceです。UserDetailsServiceはインターフェースなので、実際には実装クラスのインスタンスが保持されます。

インメモリの場合はInMemoryUserDetailsManagerというクラスで実装されています。

UserDetailsServiceのソースを確認すると、宣言されているのはUserDetailsを返すloadUserByUsernameメソッドだけです。ここにユーザーの検索処理を実装します。

public interface UserDetailsService {
  UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

では実装クラスとしてUserDetailsServiceImplを作成します。DB認証の前に、オンコードで擬似的に検索処理を実装してみます。

public class UserDetailsServiceImpl implements UserDetailsService {

  @Override
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
    //adminだけ認証する
    if (username == null || !"admin".equals(username)) {
      throw new UsernameNotFoundException("Not found username : " + username);
    }
    
    //パスワードの設定
    PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
    String password = encoder.encode("adminpassword");
    
    //権限の設定
    Collection<GrantedAuthority> authorities = new ArrayList<>();
    authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
    
    //ユーザー情報を作成
    User user = new User(username, password, authorities);
    return user;
  }
}

ここではadminというユーザーのみ存在するものとします。ユーザーが存在しない場合は、UsernameNotFoundExceptionの例外をスローします。

ユーザー情報は前回の記事でも紹介した、Spring Securityで実装されているUserクラスを用います。いくつかフィールドがありますが、最低限ユーザー名、パスワード、権限があればインスタンスが作成できます。

これでUserDetailsServiceが実装できました。あとは以下のように設定ファイルでUserDetailsServiceを設定するだけです。

@Autowired
UserDetailsServiceImpl service;

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  auth.userDetailsService(service);
}

実際に認証できることを確認してみてください。

DB認証

この記事ではJava Data JPAを用いた方法を説明します。Java Data JPA自体の説明はこの記事ではしません。

以下のAccountテーブルにユーザー情報が登録されているとします。内容としては、先程オンコードで設定したユーザー情報がそのまま登録されているイメージです。つまりパスワードはハッシュ化された値が登録されています。

CREATE TABLE account (
  username varchar NOT NULL,
  "password" varchar NOT NULL,
  "role" varchar NOT NULL,
  CONSTRAINT account_pk PRIMARY KEY (username)
);

このテーブルに対応するEntityクラスを作成します。

@Entity
public class Account {

  @Id
  private String username;
  private String password;
  private String role;
  
  public Account() {}

  //Setter、Getterは省略
}

そしてRepositoryインターフェースを作成するのですが、今回はusernameがキーなので検索用のメソッドを宣言しておきます。

@Repository
public interface AccountRepository extends JpaRepository<Account, String> {
  Account findByUsername(String username);
}

あとはUserServiceにRepositoryを使ったユーザー検索を実装するだけです。

public class UserDetailsServiceImpl implements UserDetailsService {

  private AccountRepository repository;
  
  @Autowired
  public UserDetailsServiceImpl(AccountRepository repository) {
    this.repository = repository;
  }
  
  @Override
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
    if (StringUtils.isEmpty(username)) {
      throw new UsernameNotFoundException("username is empty");
    }
    
    Account account = repository.findByUsername(username);
    
    if (account == null) {
      throw new UsernameNotFoundException("Not found username : " + username);
    }
    
    Collection<GrantedAuthority> authorities = new ArrayList<>();
    authorities.add(new SimpleGrantedAuthority(account.getRole()));
    
    User user = new User(account.getUsername(), account.getPassword(), authorities);
    return user;
  }
}

あとがき

今回はDB認証について説明しました。UserDetailsServiceにユーザーの検索処理を実装することでDB認証が可能になります。

今回使用したテーブルはかなりシンプルなものでしたが、実際にはもう少し多くのフィールドを持っていると思います。このような場合にどうすればよいのかを次回の記事にしたいと思います。

 

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

コメント

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