Help us understand the problem. What is going on with this article?

Spring BootでBasic認証を使用する

More than 1 year has passed since last update.

はじめに

何となく、SIer向けの文体で書いてみました。環境も、あえてJava1.7にしてます。

概要

Spring BootでBasic認証を方法をサンプルコードを用いて説明する。
WebAPIとしての利用シーンを想定し、ステートレスな通信に対応した設定とする。

前提とする環境

バージョン

Version
Java 1.7
Spring Boot 1.5.9.RELEASE

依存関係

  • Spring Security

サンプルコード

設定

JavaConfigで以下の設定をする。具体的な設定内容についてはソースコード中のコメントを参照すること。

  • Spring Securityの設定
    • Basic認証
    • ステートレス通信のための設定
  • Bean定義
    • userDetailsService
    • passwordEncoder
MyConfigure.java
@Configuration
public class MyConfigure extends WebSecurityConfigurerAdapter {

  // ----------------------------------------
  // Spring Securityの設定
  // ----------------------------------------

  // <<< ※WebSecurityConfigurerAdapterには、configureのオーバーロードメソッドが複数あるので要注意 >>>
  @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { super.configure(auth); }
  @Override public    void configure(WebSecurity                  web)  throws Exception { super.configure(web); }
  //---

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    // Basic認証の設定
    http.httpBasic().realmName("My sample realm");
    // 認証が必要となるリクエストの設定
    http.authorizeRequests().anyRequest().authenticated();
    // CSRF対策が有効だとTokenなしのPOSTがエラーとなるため、無効化する
    http.csrf().disable();
    // 認証情報は常にAuthorizationヘッダから取得するため、Cookieによるセッション管理は不要
    http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
  }

  // ----------------------------------------
  // Bean定義
  // ----------------------------------------

  @Bean
  public UserDetailsService userDetailsService() {
    return new LoginPrincipal.LoginPrincipalService();
  }

  @Bean
  public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
  }

}

認証情報

認証情報は、UserDetails1を実装したクラスとして定義する(LoginPrincipal)。
このクラスは、以下の点に注意して設計する。

  • 認証に必要な情報のみを保持するようにする
    • 業務的に必要となる「利用者」情報は、このクラスとは別に定義する
  • UserDetailsにおけるusernameの意味を明確にする

上記を踏まえて本サンプルでは、以下の設計とする。

  • usernameには、「ログインID」を保持する
  • UserDetailsの情報に加え「従業員番号」を保持する

UserDetailsService2の実装クラスをBean定義することで、Spring Securityが認証情報を検索する際の実装を定義できる(LoginPrincipal.LoginPrincipalService)。
このクラスは、コード簡略化のためLoginPrincipalの内部クラスとして定義している。

なお、LoginPrincipal.DBクラスには、サンプルコード単体で動作可能とするためのダミー処理が定義されている。

LoginPrincipal.java
// ======================================================================
// ※UserDetailsの実装クラス
// ======================================================================
public class LoginPrincipal extends org.springframework.security.core.userdetails.User {
  // ※loginId(ログインID)はsuper.usernameに保持する
  private final String employeeNumber; // 従業員番号

  public LoginPrincipal(String loginId, String employeeNumber, String encodedPassword, String[] roles) {
    super(loginId, encodedPassword, true, true, true, true, AuthorityUtils.createAuthorityList(roles));
    this.employeeNumber = employeeNumber;
  }

  public String getLoginId() {
    return super.getUsername();
  }

  // <<< Getter >>>
  public String getEmployeeNumber() { return this.employeeNumber; }
  //---

  // ======================================================================
  // ※UserDetailsServiceの実装クラス
  // ======================================================================
  public static class LoginPrincipalService implements org.springframework.security.core.userdetails.UserDetailsService {

    /**
     * 指定されたログインIDで認証情報を検索します。
     * 認証情報が見つからなかった場合、結果は{@code null}です。
     */
    public LoginPrincipal findByLoginId(String loginId) {
      // ※実際には、ここで認証情報をDBから取得する
      return DB.AUTH_TABLE.get(loginId);
    }

    /** {@inheritDoc} */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
      // ※usernameには、Spring Securityの認証機能により基本認証の「ユーザ名」が設定されている
      // findByLoginIdに委譲
      LoginPrincipal found = this.findByLoginId(username);
      if (found == null) {
        throw new UsernameNotFoundException("username not found: " + username);
      }
      return found;
    }
  }


  // ======================================================================
  // (サンプル用) ユーザ認証マスタの代替として、Mapにユーザ認証情報を保持する
  // ======================================================================
  private static class DB {
    public static final Map<String, LoginPrincipal> AUTH_TABLE = new HashMap<>();

    static {
      BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
      LoginPrincipal[] data = { //
          new LoginPrincipal("U001", "S000001", passwordEncoder.encode("pass1"), new String[] { "USER" }), //
          new LoginPrincipal("U002", "S000002", passwordEncoder.encode("pass2"), new String[] { "USER" }), //
      };
      for (LoginPrincipal d : data) {
        AUTH_TABLE.put(d.getLoginId(), d);
      }
    }
  }

}

以上でBasic認証が有効になる。
Controllerから認証情報を参照するサンプルを以下に挙げる。

MyController.java
@RestController
public class MyController {

  @GetMapping
  public String index() {
    SecurityContext securityContext = SecurityContextHolder.getContext();
    LoginPrincipal loginPrincipal = (LoginPrincipal) securityContext.getAuthentication().getPrincipal();

    return "Hello " + loginPrincipal.getEmployeeNumber() + "!";
  }

}

おわりに

初めてのQiita投稿です。続けられるといいなと思います。
ご指導ご鞭撻のほどお願いいたします。


  1. org.springframework.security.core.userdetails.UserDetails 

  2. org.springframework.security.core.userdetails.UserDetailsService 

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした