S-JIS[2017-08-29/2017-09-20] 変更履歴

Spring Boot Rest Digest認証

Spring BootのRest APIのDigest認証のサンプル。


概要

Rest API用のDigest認証も、ウェブアプリケーションのDigest認証と同じ。


Digset認証とForm認証を混在させる例

ウェブアプリケーションを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を別途作成する必要がある。

src/main/java/com/example/demo/auth/both/WebSecurityDigestConfig.java:

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クラス。

src/main/java/com/example/demo/auth/LoginUserDigestService.java:

ウェブ用Digest認証のLoginUserServiceと同じ。(クラス名とrealmだけ異なる)


Form認証用のConfigクラス。

src/main/java/com/example/demo/auth/both/WebSecurityFormConfig.java:

Form認証とBasic認証を混在させる方法のWebSecurityFormConfigと全く同じ。


Form認証用のUserDetailsServiceクラス。

src/main/java/com/example/demo/auth/LoginUserService.java:

ウェブ用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

クライアントにcurlを使う例

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


Spring Bootへ戻る / Spring Frameworkへ戻る / 技術メモへ戻る
メールの送信先:ひしだま