はじめに
なぜに Digest 認証なのか?
RESTful な Web アプリでユーザ認証を考えた場合
普通に Form 認証的な感じで Cookie に Session Id を保存するパターンでもいいのでは?
と思ったのですが RESTful の要件に Stateless というのもがあり
アクセスは 1回ごとに完結しないといけないとのことです。
クライアントのセッション状態をサーバーで管理するのはよくないようです。
このあたりは、理念というか、こだわりというか、なんとなくどちらでもいいような…
まぁ、とりあえず、Cookie / Session はダメということで
Http 認証ということになる訳ですが
Basic 認証では、ちょっと…なので Digest 認証になりました。
どの場合であっても SSL にはしておきたいところです。
今回は、簡単な Web アプリとテストを作成して動作を検証してみたいと思います。
また、Spring Framework 3.2 から Spring MVC Test Framework が利用できるので、これも試してみます。
ではでは!Getting Started!!
環境
| OS | Windows XP |
|---|---|
| アプリケーションサーバー | Tomcat 7.0.37 |
| Java | JDK 1.7.0_21 |
| フレームワーク | Spring Framework 3.2.2.RELEASE |
| Spring Security 3.1.4.RELEASE | |
| ライブラリー | HttpClient 4.2.5 |
| JsonPath 0.8.1 | |
| Jackson 2.2.1 | |
| JXPath Component 1.3 | |
| ビルドツール | Maven 3.0.5 ※mvnコマンドが実行できるように設定しておきます。 |
プロジェクト構成
Cドライブ直下に「studying-spring-security-digest」フォルダを作成し各ファイルを以下のように配置します。
ファイルは、すべて UTF-8 で保存します。
ServletとLoggerの設定
web.xml ファイルを以下の内容で作成する。web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xml="http://www.w3.org/XML/1998/namespace"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<display-name>studying-spring-security-digest</display-name>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:META-INF/spring/applicationContext*.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<filter>
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>springMVCServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>WEB-INF/spring/webmvc-config.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springMVCServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
ログ設定ファイルを以下の内容で作成する。
Digest 認証の Debug ログがコンソールに出力されるようにしています。
log4j.properties
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
log4j.rootLogger=error, stdout
log4j.logger.org.springframework.security.web.authentication.www.DigestAuthenticationEntryPoint=debug, stdout
log4j.logger.org.springframework.security.web.authentication.www.DigestAuthenticationFilter=debug, stdout
Spring MVCの設定
Spring MVCの設定 ファイルを以下の内容で作成する。webmvc-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd">
<context:component-scan base-package="com.mydomain" />
<mvc:annotation-driven />
</beans>
Spring Securityの設定
「/」は、認証を設定せず、それ以外の URL に認証を設定しています。
「/Admin」へのアクセスは、管理者権限を持つユーザに制限しています。
セッションは使用しないので stateless としています。
Spring Security で Digest 認証を行うためには、いろいろ決め事があるみたいで
とりあえず、ユーザーのパスワードは平文で保存しておかないといけないようです。
ハッシュ化して保存したい場合は、 DigestAuthenticationFilter あたりを改造することになるのでしょうか?
Digest 認証の Filter も明示的に定義する必要があるようです。
今回、Basic 認証を使わないので、その位置に挟んでいます。
また、Digest 認証の Filter には、AuthenticationManager でなく
直接 UserDetailsService を設定するようです。
nonce の有効期限は 3秒に設定しています。
ログインユーザー情報は、InMemoryDaoImpl から取得するようにしました。
ユーザーアカウントは一般ユーザと管理者ユーザを登録しています。
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans
xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.1.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
<http pattern="/" security="none" create-session="stateless"/>
<http entry-point-ref="digestEntryPoint" create-session="stateless">
<intercept-url pattern="/Admin" access="ROLE_ADMIN" />
<intercept-url pattern="/**" access="ROLE_USER" />
<custom-filter position="BASIC_AUTH_FILTER" ref="digestFilter"/>
</http>
<beans:bean id="digestFilter"
class="org.springframework.security.web.authentication.www.DigestAuthenticationFilter">
<beans:property name="userDetailsService" ref="inMemoryDaoImpl" />
<beans:property name="authenticationEntryPoint" ref="digestEntryPoint" />
</beans:bean>
<beans:bean id="digestEntryPoint"
class="org.springframework.security.web.authentication.www.DigestAuthenticationEntryPoint">
<beans:property name="realmName" value="My Realm" />
<beans:property name="key" value="mykey" />
<beans:property name="nonceValiditySeconds" value="3" />
</beans:bean>
<authentication-manager>
<authentication-provider>
<user-service id="inMemoryDaoImpl">
<user name="admin" password="admin-pass" authorities="ROLE_USER, ROLE_ADMIN" />
<user name="user" password="user-pass" authorities="ROLE_USER" />
</user-service>
</authentication-provider>
</authentication-manager>
</beans:beans>
Controller クラスの作成
ApiController クススを以下の内容で作成する。呼出可能な URL は、「/」「/Home」「/Admin」「UserDetails」4つです。
「/」「/Home」「/Admin」は、文字列を返します。
「UserDetails」は、ログインユーザ情報を Json 形式で返します。
ApiController.java
package com.mydomain.web;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class ApiController {
@RequestMapping("/")
@ResponseBody
public String index() { return "Index"; }
@RequestMapping("/Home")
@ResponseBody
public String home() { return "Home"; }
@RequestMapping("/Admin")
@ResponseBody
public String admin() { return "Admin"; }
@RequestMapping("/UserDetails")
@ResponseBody
public UserDetails userDetails() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
return (UserDetails)auth.getPrincipal();
}
}
Controller クラスのテスト作成
Spring Security が起動するように setup() メソッドの中で FilterChainProxy をMockMvc の Filter に追加しています。
MockHttpServletRequestBuilder から Digest 認証の Authorization リクエストヘッダー を
追加できそうになかったので Apache HttpComponents を使用することにしました。
このあたりを自作してしまうと今度はそこのテストが必要になってきそうなので…
Json データの検証は、JsonPath を使う方法と Jackson と JXPath を使う方法を試してみました。
ApiControllerDigestAuthTest.java
package com.mydomain;
import static org.junit.Assert.assertEquals;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import java.util.Map;
import org.apache.commons.jxpath.JXPathContext;
import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.impl.auth.DigestScheme;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicHttpRequest;
import org.apache.http.protocol.BasicHttpContext;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.ServletWebRequest;
import com.fasterxml.jackson.databind.ObjectMapper;
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration()
@ContextConfiguration({
"file:src/main/webapp/WEB-INF/spring/webmvc-config.xml",
"file:src/main/resources/META-INF/spring/applicationContext-security.xml"})
public class ApiControllerDigestAuthTest {
@Autowired
private WebApplicationContext wac;
@Autowired
private FilterChainProxy springSecurityFilterChain;
private MockMvc mockMvc;
@Before
public void setup() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac)
.addFilters(this.springSecurityFilterChain).build();
}
@SuppressWarnings("unchecked")
@Test
public void RoleUserTest() throws Exception {
// ===== RoleUser Test Data ===== //
String userName = "user";
String password = "user-pass";
UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(userName, password);
BasicHttpRequest request = new BasicHttpRequest("GET", "/");
BasicHttpContext context = new BasicHttpContext();
BasicHeader header = null;
DigestScheme digestScheme = null;
Header reqHeader = null;
String resHeader = "";
MvcResult mvcResult = null;
// ===== Test 1 ===== //
this.mockMvc
.perform(
get("/")
.accept(MediaType.TEXT_PLAIN))
.andExpect(status().isOk())
.andExpect(content().string("Index"));
// ===== Test 2 ===== //
mvcResult = this.mockMvc
.perform(
get("/Home")
.accept(MediaType.TEXT_PLAIN))
.andExpect(status().isUnauthorized())
.andReturn();
// ===== Test 3 ===== //
// Response Header から Authorization Digest Request Header を作成する。
resHeader = mvcResult.getResponse().getHeaderValue("WWW-Authenticate").toString();
resHeader = resHeader.substring(7); // 先頭文字列 Digest を削除
header = new BasicHeader("WWW-Authenticate", resHeader);
digestScheme = new DigestScheme();
for (HeaderElement elm : header.getElements()) {
switch (elm.getName()) {
case "realm" :
case "nonce" :
digestScheme.overrideParamter(elm.getName(), elm.getValue());
break;
}
}
digestScheme.overrideParamter("qop", "auth");
reqHeader = digestScheme.authenticate(credentials, request, context);
// URL 呼出し
this.mockMvc
.perform(
get("/Home")
.accept(MediaType.TEXT_PLAIN)
.header(reqHeader.getName(), reqHeader.getValue()))
.andExpect(status().isOk())
.andExpect(content().string("Home"));
// ===== Test 4 ===== //
reqHeader = digestScheme.authenticate(credentials, request, context);
this.mockMvc
.perform(
get("/Admin")
.accept(MediaType.TEXT_PLAIN)
.header(reqHeader.getName(), reqHeader.getValue()))
.andExpect(status().isForbidden());
// ===== Test 5 ===== //
// 3秒スリープして nonce を期限切れにする。
Thread.sleep(3000);
reqHeader = digestScheme.authenticate(credentials, request, context);
mvcResult = this.mockMvc
.perform(
get("/UserDetails")
.accept(MediaType.TEXT_PLAIN)
.header(reqHeader.getName(), reqHeader.getValue()))
.andExpect(status().isUnauthorized())
.andReturn();
// ===== Test 6 ===== //
// Response Header から Authorization Digest Request Header を作成する。
resHeader = mvcResult.getResponse().getHeaderValue("WWW-Authenticate").toString();
resHeader = resHeader.substring(7);
header = new BasicHeader("WWW-Authenticate", resHeader);
digestScheme = new DigestScheme();
for (HeaderElement elm : header.getElements()) {
switch (elm.getName()) {
case "realm" :
case "nonce" :
digestScheme.overrideParamter(elm.getName(), elm.getValue());
break;
}
}
digestScheme.overrideParamter("qop", "auth");
reqHeader = digestScheme.authenticate(credentials, request, context);
// URL 呼出し - JsonPath で JSON データを検証
this.mockMvc
.perform(
get("/UserDetails")
.accept(MediaType.APPLICATION_JSON)
.header(reqHeader.getName(), reqHeader.getValue()))
.andExpect(status().isOk())
.andExpect(jsonPath("$.username").value(userName))
.andExpect(jsonPath("$.authorities").isArray())
.andExpect(jsonPath("$.authorities[?(@.authority == 'ROLE_USER')]").exists())
.andExpect(jsonPath("$.authorities[?(@.authority == 'ROLE_ADMIN')]").doesNotExist());
}
@SuppressWarnings("unchecked")
@Test
public void RoleAdminTest() throws Exception {
// ===== RoleAdmin Test Data ===== //
String userName = "admin";
String password = "admin-pass";
UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(userName, password);
BasicHttpRequest request = new BasicHttpRequest("GET", "/");
BasicHttpContext context = new BasicHttpContext();
BasicHeader header = null;
DigestScheme digestScheme = null;
Header reqHeader = null;
String resHeader = "";
MvcResult mvcResult = null;
// ===== Test 7 ===== //
mvcResult = this.mockMvc
.perform(
get("/Admin")
.accept(MediaType.TEXT_PLAIN))
.andExpect(status().isUnauthorized())
.andReturn();
// ===== Test 8 ===== //
// Response Header から Authorization Digest Request Header を作成する。
resHeader = mvcResult.getResponse().getHeaderValue("WWW-Authenticate").toString();
resHeader = resHeader.substring(7);
header = new BasicHeader("WWW-Authenticate", resHeader);
digestScheme = new DigestScheme();
for (HeaderElement elm : header.getElements()) {
switch (elm.getName()) {
case "realm" :
case "nonce" :
digestScheme.overrideParamter(elm.getName(), elm.getValue());
break;
}
}
digestScheme.overrideParamter("qop", "auth");
reqHeader = digestScheme.authenticate(credentials, request, context);
// URL 呼出し
this.mockMvc
.perform(
get("/Admin")
.accept(MediaType.TEXT_PLAIN)
.header(reqHeader.getName(), reqHeader.getValue()))
.andExpect(status().isOk())
.andExpect(content().string("Admin"));
// ===== Test 9 ===== //
mvcResult = this.mockMvc
.perform(
get("/UserDetails")
.accept(MediaType.APPLICATION_JSON)
.header(reqHeader.getName(), reqHeader.getValue()))
.andExpect(status().isOk())
.andReturn();
// Jackson と JXPath で JSON データを検証
String response = mvcResult.getResponse().getContentAsString();
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> userDetails = mapper.readValue(response, Map.class);
JXPathContext jxContext = JXPathContext.newContext(userDetails);
assertEquals(userName, jxContext.getValue("username").toString());
assertEquals(2, jxContext.selectNodes("authorities").size());
assertEquals(1, jxContext.selectNodes("authorities[@authority='ROLE_USER']").size());
assertEquals(1, jxContext.selectNodes("authorities[@authority='ROLE_ADMIN']").size());
}
}
- RoleUser Test Data
- 一般ユーザのテストデータ
- Test 1
-
「/」の呼出しテスト
認証が不要な URL にアクセスする。
レスポンスコード 200:OK と共に文字列 Index を取得する。 - Test 2
-
「/Home」の呼出しテストA
認証が必要な URL に Authorization リクエストヘッダーなしでアクセスする。
レスポンスコード 401:Unauthorized が返る。 - Test 3
-
「/Home」の呼出しテストB
Test 2 のレスポンスヘッダー WWW-Authenticate から realm と nonce を取得し
ユーザー と パスワードを加えて Authorization リクエストヘッダー作成する。
Authorization リクエストヘッダーを付けてアクセスする。
レスポンスコード 200:OK と共に文字列 Home を取得する。 - Test 4
-
「/Admin」の呼出しテスト
Admin 権限が必要な URL に 一般ユーザでアクセスする。
Test 3 で作成した Authorization リクエストヘッダーを使用する。
レスポンスコード 403:Forbidden が返る。 - Test 5
-
「/UserDetails」の呼出しテストA
有効期限切れ nonce でアクセスする。
nonce を有効期限切れにするため 3秒スリープする。
Test 3 で作成した Authorization リクエストヘッダーを使用する。
レスポンスコード 401:Unauthorized が返る。 - Test 6
-
「/UserDetails」の呼出しテストB
Test 5 のレスポンスヘッダー WWW-Authenticate から realm と nonce を取得し
ユーザー と パスワードを加えて Authorization リクエストヘッダー再作成する。
Authorization リクエストヘッダーを付けてアクセスする。
レスポンスコード 200:OK と共にログインユーザ情報を Json 形式で取得する。
JSON データを JsonPath で検証する。
- RoleAdmin Test Data
- 管理者ユーザのテストデータ
- Test 7
-
「/Admin」の呼出しテストA
認証が必要な URL に Authorization リクエストヘッダーなしでアクセスする。
レスポンスコード 401:Unauthorized が返る。 - Test 8
-
「/Admin」の呼出しテストB
Test 7 のレスポンスヘッダー WWW-Authenticate から realm と nonce を取得し
ユーザー と パスワードを加えて Authorization リクエストヘッダー作成する。
Authorization リクエストヘッダーを付けてアクセスする。
Admin 権限が必要な URL に 管理者ユーザでアクセスする。
レスポンスコード 200:OK と共に文字列 Admin を取得する。 - Test 9
-
「/UserDetails」の呼出しテスト
Test 8 で作成した Authorization リクエストヘッダーを使用する。
レスポンスコード 200:OK と共にログインユーザ情報を Json 形式で取得する。
JSON データを Jackson と JXPath で 検証する。
POM ファイルの作成
POM ファイルを以下の内容で作成する。pom.xml
<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mydomain</groupId>
<artifactId>studying-spring-security-digest</artifactId>
<packaging>war</packaging>
<version>1.0</version>
<name>studying-spring-security-digest</name>
<properties>
<java.version>1.7</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>3.2.2.RELEASE</spring.version>
<spring-security.version>3.1.4.RELEASE</spring-security.version>
</properties>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<version>${spring-security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>${spring-security.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>${spring-security.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.2.5</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path-assert</artifactId>
<version>0.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-jxpath</groupId>
<artifactId>commons-jxpath</artifactId>
<version>1.3</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
<build>
<finalName>studying-spring-security-digest</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.1</version>
</plugin>
</plugins>
</build>
</project>
ビルドと実行
コマンドプロンプトを開きCドライブ直下の「studying-spring-security-digest」フォルダに移動後、以下の mvn コマンドを実行します。
cd c:\studying-spring-security-digest mvn clean package mvn tomcat7:run
テストがパスして war ファイルが生成されれば、とりあえず OK です。
実際に直接ブラウザから「http://localhost:8080/studying-spring-security-digest/」にアクセスしてみるのもよいと思います。
Controller の URL 「/」「/Home」「/Admin」「/UserDetails」を一般ユーザと管理者ユーザで呼出してコンソールログを確認してみる。
設定した有効期限(3秒)のタイミングで nc がリセットされ 新しい nonce に変わっているのも確認できると思います。
おわりに
テストコードの中で digestScheme.authenticate() メソッドを呼出して nc をインクリメントなどして
Authorization リクエストヘッダーを更新しているのですが、この呼出しをコメントアウトしてもテストは通ってしまいます。
一度受け入れたことのある Response ダイジェスト文字列が送られてきた場合、エラーにしてほしいような気がしますが…
リプレイ攻撃とか大丈夫なのだろうか?
とりあえず、nonce の有効期限は効いているようなので問題ないかな?
RESTful の要件は満たさないのかもしれないけど
Spring Security で WebAPI の認証をする場合、Cookie / Session が無難かも?
もしくは、OAuth の対応に期待か?!
Spring MVC Test Framework は、かなりいい感じだ。
いろいろテストできそうだ!
- 参考URL
- http://static.springsource.org/spring-security/site/docs/3.1.x/reference/basic.html
- http://static.springsource.org/spring/docs/3.2.x/spring-framework-reference/html/testing.html
- http://www.baeldung.com/2011/11/20/basic-and-digest-authentication-for-a-restful-service-with-spring-security-3-1/
- http://d.hatena.ne.jp/kuwalab/20130402/p1
- http://x68000.q-e-d.net/~68user/net/http-auth-2.html
- http://hc.apache.org/
- http://code.google.com/p/json-path/
- http://jackson.codehaus.org/
- http://commons.apache.org/proper/commons-jxpath/