はじめに
何となく、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
@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();
}
}
認証情報
認証情報は、UserDetails
1を実装したクラスとして定義する(LoginPrincipal
)。
このクラスは、以下の点に注意して設計する。
- 認証に必要な情報のみを保持するようにする
- 業務的に必要となる「利用者」情報は、このクラスとは別に定義する
-
UserDetails
におけるusernameの意味を明確にする
上記を踏まえて本サンプルでは、以下の設計とする。
- usernameには、「ログインID」を保持する
-
UserDetails
の情報に加え「従業員番号」を保持する
UserDetailsService
2の実装クラスをBean定義することで、Spring Securityが認証情報を検索する際の実装を定義できる(LoginPrincipal.LoginPrincipalService
)。
このクラスは、コード簡略化のためLoginPrincipal
の内部クラスとして定義している。
なお、LoginPrincipal.DB
クラスには、サンプルコード単体で動作可能とするためのダミー処理が定義されている。
// ======================================================================
// ※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から認証情報を参照するサンプルを以下に挙げる。
@RestController
public class MyController {
@GetMapping
public String index() {
SecurityContext securityContext = SecurityContextHolder.getContext();
LoginPrincipal loginPrincipal = (LoginPrincipal) securityContext.getAuthentication().getPrincipal();
return "Hello " + loginPrincipal.getEmployeeNumber() + "!";
}
}
おわりに
初めてのQiita投稿です。続けられるといいなと思います。
ご指導ご鞭撻のほどお願いいたします。