Spring BootのRest APIのDigest認証のサンプル。
Rest API用のDigest認証も、ウェブアプリケーションのDigest認証と同じ。
ウェブアプリケーションをForm認証、Rest APIをDigest認証にする例。(Spring Boot 1.5.6、Spring Security 4.2.3)
「/api/」で始まるURI(Rest API)をDigest認証とし、それ以外(ウェブ)をForm認証にする。
これは、Basic認証とForm認証を混在させる方法に対し、Basic認証を(ウェブと同様の)Digest認証にするだけ。[2017-09-20]
ただし、Form認証(Basic認証)とDigest認証では、パスワードをDBに保存しておく方法が違う。
Form認証はPasswordEncoderを使ってエンコードしてDBに保存するのに対し、
Digest認証の場合はMD5ハッシュ化してDBに保存する。
したがって、DBからユーザー情報を取得するUserDetailsServiceを共有する事が出来ない。
Digest認証用のUserDetailsServiceを別途作成する必要がある。
package com.example.demo.auth.both; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.concurrent.ConcurrentMapCache; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; 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.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.cache.SpringCacheBasedUserCache; import org.springframework.security.web.authentication.www.DigestAuthenticationEntryPoint; import org.springframework.security.web.authentication.www.DigestAuthenticationFilter; import com.example.demo.auth.LoginUserDigestService;
/**
* Digest認証
*/
@EnableWebSecurity
@Order(Ordered.HIGHEST_PRECEDENCE)
public class WebSecurityDigestConfig extends WebSecurityConfigurerAdapter {
@Autowired private LoginUserDigestService userDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/api/**"); // コンフィグを適用するURI
http.authorizeRequests().anyRequest().authenticated(); // 適用された全リクエストに対して認証を要求
// Digest認証
DigestAuthenticationEntryPoint authenticationEntryPoint = digestAuthenticationEntryPoint();
http.addFilter(digestAuthenticationFilter(authenticationEntryPoint))
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
http.csrf().disable();
}
private DigestAuthenticationFilter digestAuthenticationFilter(DigestAuthenticationEntryPoint authenticationEntryPoint) throws Exception {
DigestAuthenticationFilter digestAuthenticationFilter = new DigestAuthenticationFilter();
digestAuthenticationFilter.setAuthenticationEntryPoint(authenticationEntryPoint);
digestAuthenticationFilter.setUserDetailsService(userDetailsService);
digestAuthenticationFilter.setUserCache(new SpringCacheBasedUserCache(new ConcurrentMapCache("digestUserCache")));
digestAuthenticationFilter.setPasswordAlreadyEncoded(true);
return digestAuthenticationFilter;
}
private DigestAuthenticationEntryPoint digestAuthenticationEntryPoint() {
DigestAuthenticationEntryPoint entry = new DigestAuthenticationEntryPoint();
entry.setRealmName("example.both.realm");
entry.setKey("digest-key");
entry.setNonceValiditySeconds(60);
return entry;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
}
「auth.userDetailsService(this.userDetailsService)」(PasswordEncoderを指定していない)は重要。
これが無いと、WebSecurityFormConfigで定義しているPasswordEncoderをDigest認証でも使おうとして、常に認証失敗になってしまう。
Digest認証用のUserDetailsServiceクラス。
ウェブ用Digest認証のLoginUserServiceと同じ。(クラス名とrealmだけ異なる)
Form認証用のConfigクラス。
Form認証とBasic認証を混在させる方法のWebSecurityFormConfigと全く同じ。
Form認証用のUserDetailsServiceクラス。
ウェブ用Basic認証のLoginUserServiceと同じ。
ただし、UserDetailsServiceがDigest認証用のクラスと重複しているので、
@Primaryアノテーションを付けて、デフォルトではForm認証用のUserDetailsServiceが使われるようにする。
import org.springframework.context.annotation.Primary;
@Service
@Primary
public class LoginUserService implements UserDetailsService {
〜
}
@Primaryアノテーションを付けていないと、Spring Boot起動時に以下のようなエラーが発生する。
*************************** APPLICATION FAILED TO START *************************** Description: Field userDetailsService in com.example.demo.auth.both.WebSecurityFormConfig required a single bean, but 2 were found: - loginUserDigestService: defined in file [D:\workspace\demo\bin\com\example\demo\auth\LoginUserDigestService.class] - loginUserService: defined in file [D:\workspace\demo\bin\com\example\demo\auth\LoginUserService.class] Action: Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
UNIXのcurlコマンドでは、-uでユーザーID(とパスワード)を指定し、--digestでDigest認証を行う事が出来る。[/2017-09-20]
$ curl --digest http://127.0.0.1:8080/api/example -X GET -u hishidama:hoge
ちなみに、-iでレスポンスの内容、-vでリクエストとレスポンスの内容も表示される。
毎回ユーザーIDとパスワードを指定するのではなく、初回のみ指定し、クッキーにセッションを保持しておくことも出来る。
$ curl --digest http://127.0.0.1:8080/api/example -u hishidama:hoge -c cookie.txt $ curl --digest http://127.0.0.1:8080/api/example -b cookie.txt
-cでクッキーをファイルに保存し、-bでそのクッキーファイルを読み込む。