Webエンジニアのメモ帳

技術的な話を中心に書いています。

【Spring Security】ログイン機能の実装方法

Springを用いた開発では、Spring Securityを使えばログイン機能が簡単に実装できます。

今回は最低限のコード量で、フォームでのログインを実装します。

build.gradleの修正

まずは、Spring Securityを使うため、build.gradleに依存ライブラリを追加します。

以下のように、spring-boot-starter-securityを追加すればOKです。gradleでなくmavenを使っている場合も同様です。

dependencies {
  implementation 'org.springframework.boot:spring-boot-starter-security'
}

ユーザークラスを実装

ユーザーを識別するため、ユーザークラスを実装します。

まずは、データベースにuserテーブルを作成します。 テーブルを作成するSQL文は以下の通りです。

CREATE TABLE `user` (
  `id` int PRIMARY KEY AUTO_INCREMENT,
  `username` varchar(20) NOT NULL UNIQUE,
  `password` varchar(100) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8

usernameにはユーザー名を、passwordにはハッシュ化されたパスワードを格納します。

なお、レコード名は「username」と「password」にしてください。(違う名前でもいいですが、記述するコード量が少し増えます。)

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

ポイントは、UserDetailsインターフェースを継承する点です。そのため、いくつかのメソッドをオーバーライドしないといけません。

import java.util.Collection;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import lombok.Getter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

@Getter
@Entity
@Table(name = "user")
public class User implements UserDetails {
  @Id
  private String id;

  private String username;

  private String password;

  @Override
  public Collection<? extends GrantedAuthority> getAuthorities() {
    return null;
  }

  @Override
  public boolean isAccountNonExpired() {
    return true;
  }

  @Override
  public boolean isAccountNonLocked() {
    return true;
  }

  @Override
  public boolean isCredentialsNonExpired() {
    return true;
  }

  @Override
  public boolean isEnabled() {
    return true;
  }
}

ユーザークラスを扱うレポジトリクラス・サービスクラスの作成

つぎに、上で作成したユーザークラスを扱うレポジトリクラスを作成します。

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

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

↑で作成したレポジトリクラスを使用するサービスクラスも作成します。

ポイントは、UserDetailsServiceインターフェースを継承し、loadUserByUsername()メソッドをオーバーライドする点です。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserDetailsService {
  @Autowired
  private final UserRepository userRepository;

  @Override
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    return userRepository.findByUsername(username);
  }
}

WebSecurityConfigurerAdapterクラスを継承するクラスの作成

最後に、WebSecurityConfigurerAdapterという抽象クラスを継承するクラスを実装します。

今回は、トップページには誰でもアクセスでき、それ以外はログインが必要で、ログイン成功後は/memberというパスに遷移する、という内容とします。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
  @Autowired
  private final UserDetailsService userDetailsService;

  @Override
  protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
    authenticationManagerBuilder.userDetailsService(this.userDetailsService);
  }

  // cssや画像などを読み込む場合は記述
  @Override
  public void configure(WebSecurity web) throws Exception {
    //静的リソースをセキュリティ対象外に設定
    web.ignoring().antMatchers("/css/**", "/js/**", "/img/**");
  }

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
      .antMatchers("/").permitAll()
      .anyRequest().authenticated()
      .and()
      // ログインにはデフォルトのフォームを使用
      .formLogin()
      // ログイン後に遷移するパスを記述
      .defaultSuccessUrl("/member", true)
      .and()
      .logout()
      // ログアウト後に遷移するパスを記述
      .logoutSuccessUrl("/");
    }

  // これを記述することで、フォームから入力されたパスワードをハッシュ化したものが、DBに格納されたパスワードと照合されます。
  @Bean
  PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
  }
}

独自のログインフォームを実装するなど、様々な応用が可能できますが、最低限のログイン機能は上記のコードのコピペで使えるはずです。