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でそのクッキーファイルを読み込む。