Spring SecurityでのRESTサービスに対する認証
1概要
この記事は、RESTfulユーザーアカウントと認証サービスを提供する安全なREST APIに対して認証する方法に焦点を当てています。
2目標
まず、アクターを調べてみましょう – 典型的なSpring Security対応アプリケーションは、何かに対する認証を受ける必要があります。
-
データベース
-
LDAP
-
RESTサービス
データベースは最も一般的なシナリオです。ただし、RESTful UAA(ユーザーアカウントと認証)サービスも同様に機能します。
この記事の目的のために、REST UAAサービスは
/authentication
に対して単一のGET操作を公開します。これは、
完全認証プロセスを実行するためにSpring Securityが必要とする
プリンシパル情報を返します。
3クライアント
通常、単純なSpring Security対応アプリケーションは、認証元として単純なユーザサービスを使用します。
<authentication-manager alias="authenticationManager">
<authentication-provider user-service-ref="customUserDetailsService"/>
</authentication-manager>
これは
org.springframework.security.core.userdetails.UserDetailsService
を実装し、提供された
username
に基づいて
Principal
を返します。
@Component
public class CustomUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) {
...
}
}
クライアントがRESTful UAAサービスに対して認証するとき、
username
だけでは十分ではなくなります –
クライアントは、認証要求をサービスに送信するときに、完全な認証情報を必要とします
–
username
と
password
の両方)。サービス自体は保護されているので、これは完全に理にかなっています。そのため、要求を正しく処理するには認証資格情報を含める必要があります。
その観点では
password
は使用できなくなっているため、
loadUserByUsername
内からこれを行うことはできません。
これを行うには、Spring Securityに完全な認証プロバイダを提供します。
<authentication-manager alias="authenticationManager">
<authentication-provider ref="restAuthenticationProvider"/>
</authentication-manager>
認証プロバイダ全体をオーバーライドすると、サービスからプリンシパルをカスタム検索するための自由度が高まりますが、かなり複雑になります。標準の認証プロバイダ
DaoAuthenticationProvider
– に必要なもののほとんどが含まれているので、単純にそれを拡張して必要なものだけを変更するのが良い方法です。
残念ながら、これは不可能です、
retrieveUser
– 私たちが拡張したい方法 – は
final
です。これはやや直観的ではありません(https://jira.springsource.org/browse/SEC-1954[この問題について議論しています]) – ここでの設計意図は単に理想的ではない代替実装を提供することにあるようです。
RestAuthenticationProvider
は、
DaoAuthenticationProvider
の実装の大部分をコピーして貼り付ける必要があるものを書き換え、サービスからプリンシパルを取得します。
@Override
protected UserDetails retrieveUser(String name, UsernamePasswordAuthenticationToken auth){
String password = auth.getCredentials().toString();
UserDetails loadedUser = null;
try {
ResponseEntity<Principal> authenticationResponse =
authenticationApi.authenticate(name, password);
if (authenticationResponse.getStatusCode().value() == 401) {
return new User("wrongUsername", "wrongPass",
Lists.<GrantedAuthority> newArrayList());
}
Principal principalFromRest = authenticationResponse.getBody();
Set<String> privilegesFromRest = Sets.newHashSet();
//fill in the privilegesFromRest from the Principal
String[]authoritiesAsArray =
privilegesFromRest.toArray(new String[privilegesFromRest.size()]);
List<GrantedAuthority> authorities =
AuthorityUtils.createAuthorityList(authoritiesAsArray);
loadedUser = new User(name, password, true, authorities);
} catch (Exception ex) {
throw new AuthenticationServiceException(repositoryProblem.getMessage(), ex);
}
return loadedUser;
}
-
最初から始めましょう** – RESTサービスとのHTTP通信 – これは
authenticationApi – 実際のサービスのための
authenticate
オペレーションを提供する単純なAPIによって処理されます。操作自体は、HTTP対応の任意のライブラリで実装できます。この場合、実装は
RestTemplate__を使用しています。
public ResponseEntity<Principal> authenticate(String username, String pass) {
HttpEntity<Principal> entity = new HttpEntity<Principal>(createHeaders(username, pass))
return restTemplate.exchange(authenticationUri, HttpMethod.GET, entity, Principal.class);
}
HttpHeaders createHeaders(String email, String password) {
HttpHeaders acceptHeaders = new HttpHeaders() {
{
set(com.google.common.net.HttpHeaders.ACCEPT,
MediaType.APPLICATION__JSON.toString());
}
};
String authorization = username + ":" + password;
String basic = new String(Base64.encodeBase64
(authorization.getBytes(Charset.forName("US-ASCII"))));
acceptHeaders.set("Authorization", "Basic " + basic);
return acceptHeaders;
}
FactoryBean
を使用して、次のリンクを設定できます。
-
次に** 、認証要求の結果が
HTTP 401 Unauthorized
になった場合、おそらくクライアントからの認証情報が正しくないため、Spring Security認証プロセスがそれらを拒否できるように、認証情報が間違ったプリンシパルが返されます。
return new User("wrongUsername", "wrongPass", Lists.<GrantedAuthority> newArrayList());
最後に、Spring Security Principalはいくつかの権限を必要とします – その特定の主体が認証プロセスの後にローカルに持っていて使うであろう特権。/
authenticate
操作は特権を含む完全なプリンシパルを取得したので、これらは要求の結果から抽出され、Spring Securityの要求に従って
GrantedAuthority
オブジェクトに変換される必要があります。
これらの特権がどのように格納されるかの詳細はここでは関係ありません – それらは単純な文字列または複雑なロール – 特権構造として格納されるかもしれません – しかし、詳細に関係なく、我々は
GrantedAuthoritiy
オブジェクトを構築するためにそれらの名前を使うだけです。最後のSpring Securityプリンシパルが作成された後、それは標準認証プロセスに返されます。
List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList(authoritiesAsArray);
loadedUser = new User(name, password, true, authorities);
4認証サービスのテスト
ハッピーパスで認証RESTサービスを消費する統合テストを書くことは十分簡単です:
@Test
public void whenAuthenticating__then200IsReceived() {
//When
ResponseEntity<Principal> response
= authenticationRestTemplate.authenticate("admin", "adminPass");
//Then
assertThat(response.getStatusCode().value(), is(200));
}
この単純なテストに続いて、より複雑な統合テストも実装することができます – しかし、これはこの記事の範囲外です。
5結論
この記事では、データベースなどのローカルシステムに対してではなく、REST APIに対して認証する方法について説明しました。
認証プロバイダとして使用できる安全なRESTfulサービスの完全な実装については、https://github.com/eugenp/REST[GitHubプロジェクト]を調べてください。