Spring Securityの追加ログインフィールド
1前書き
この記事では、https://projects.spring.io/spring-security/[Spring Security]を使用したカスタム認証シナリオを、標準のログインフォームに追加のフィールドを追加して実装します。
フレームワークの多様性とそれを使用できる柔軟な方法を示すために、
2つの異なるアプローチ
に焦点を当てます。
-
私たちの最初のアプローチ** は、既存の中核となるSpring Securityの実装の再利用に焦点を当てた簡単な解決策です。
-
私たちの2番目のアプローチは** 高度なユースケースにより適しているかもしれない、よりカスタムな解決策になるでしょう。
/spring-security-login[Spring Securityログインに関する以前の記事]で説明されている概念に基づいて構築します。
2 Mavenのセットアップ
Spring Bootスターターを使用してプロジェクトをブートストラップし、必要なすべての依存関係を取り込みます。
ここで使用する設定には、親の宣言、Webスターター、セキュリティスターターが必要です。私たちはthymeleafも含みます:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.M7</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
</dependency>
</dependencies>
Spring Bootセキュリティスターターの最新版はhttps://search.maven.org/classic/#search%7Cga%7C1%7Cg%3A%22org.springframework.boot%22%20AND%20a%3A%22springにあります-boot-starter-security%22[Maven Centralで終わりました]。
3簡単なプロジェクト設定
最初のアプローチでは、Spring Securityが提供する実装の再利用に焦点を当てます。特に、
DaoAuthenticationProvider
と
UsernamePasswordToken
は、そのまま使用できるので再利用します。
主なコンポーネントは次のとおりです。
-
SimpleAuthenticationFilter
–
の拡張子
UsernamePasswordAuthenticationFilter
SimpleUserDetailsService
– ** の実装
UserDetailsService
User
– ** Springが提供する
User
クラスの拡張
追加の
domain
フィールドを宣言するセキュリティ
SecurityConfig
– ** 挿入するSpring Securityの設定
SimpleAuthenticationFilter
をフィルタチェーンに追加し、宣言します。
セキュリティルールと依存関係の配線
login.html
– **
ユーザー名
を収集するログインページ
password
、および
domain
3.1. 簡易認証フィルタ
私たちの
SimpleAuthenticationFilter
では、
domainとusernameフィールドはリクエストから抽出されました
。これらの値を連結し、それらを使用して
UsernamePasswordAuthenticationToken
のインスタンスを作成します。
-
トークンは認証のために
AuthenticationProvider
** に渡されます
_:
_
public class SimpleAuthenticationFilter
extends UsernamePasswordAuthenticationFilter {
@Override
public Authentication attemptAuthentication(
HttpServletRequest request,
HttpServletResponse response)
throws AuthenticationException {
//...
UsernamePasswordAuthenticationToken authRequest
= getAuthRequest(request);
setDetails(request, authRequest);
return this.getAuthenticationManager()
.authenticate(authRequest);
}
private UsernamePasswordAuthenticationToken getAuthRequest(
HttpServletRequest request) {
String username = obtainUsername(request);
String password = obtainPassword(request);
String domain = obtainDomain(request);
//...
String usernameDomain = String.format("%s%s%s", username.trim(),
String.valueOf(Character.LINE__SEPARATOR), domain);
return new UsernamePasswordAuthenticationToken(
usernameDomain, password);
}
//other methods
}
3.2. 簡易
詳細サービス
サービス
UserDetailsService
コントラクトは、
loadUserByUsername.
という単一のメソッドを定義しています。** この実装では、
username
と__domainを抽出しています。
public class SimpleUserDetailsService implements UserDetailsService {
//...
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
String[]usernameAndDomain = StringUtils.split(
username, String.valueOf(Character.LINE__SEPARATOR));
if (usernameAndDomain == null || usernameAndDomain.length != 2) {
throw new UsernameNotFoundException("Username and domain must be provided");
}
User user = userRepository.findUser(usernameAndDomain[0], usernameAndDomain[1]);
if (user == null) {
throw new UsernameNotFoundException(
String.format("Username not found for domain, username=%s, domain=%s",
usernameAndDomain[0], usernameAndDomain[1]));
}
return user;
}
}
3.3. Springのセキュリティ設定
私たちの設定は標準のSpring Security設定とは異なります。なぜなら、私たちはデフォルトの前に
SimpleAuthenticationFilter
をフィルターチェーンに挿入します。
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(authenticationFilter(),
UsernamePasswordAuthenticationFilter.class)
.authorizeRequests()
.antMatchers("/css/** ** ", "/index").permitAll()
.antMatchers("/user/** ** ").authenticated()
.and()
.formLogin().loginPage("/login")
.and()
.logout()
.logoutUrl("/logout");
}
SimpleUserDetailsService
を使用して設定しているので、提供された
DaoAuthenticationProvider
を使用できます。
SimpleUserDetailsService
は
username
フィールドと
domain
フィールドを解析する方法を知っていて、認証時に使用する適切な
User
を返すことを思い出してください。
public AuthenticationProvider authProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(passwordEncoder());
return provider;
}
SimpleAuthenticationFilter
を使用しているので、失敗したログイン試行が適切に処理されるように、独自の
AuthenticationFailureHandler
を構成します。
public SimpleAuthenticationFilter authenticationFilter() throws Exception {
SimpleAuthenticationFilter filter = new SimpleAuthenticationFilter();
filter.setAuthenticationManager(authenticationManagerBean());
filter.setAuthenticationFailureHandler(failureHandler());
return filter;
}
3.4. ログインページ
使用するログインページは、
SimpleAuthenticationFilterによって抽出された追加の
domain__フィールドを収集します。
<form class="form-signin" th:action="@{/login}" method="post">
<h2 class="form-signin-heading">Please sign in</h2>
<p>Example: user/domain/password</p>
<p th:if="${param.error}" class="error">Invalid user, password, or domain</p>
<p>
<label for="username" class="sr-only">Username</label>
<input type="text" id="username" name="username" class="form-control"
placeholder="Username" required autofocus/>
</p>
<p>
<label for="domain" class="sr-only">Domain</label>
<input type="text" id="domain" name="domain" class="form-control"
placeholder="Domain" required autofocus/>
</p>
<p>
<label for="password" class="sr-only">Password</label>
<input type="password" id="password" name="password" class="form-control"
placeholder="Password" required autofocus/>
</p>
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button><br/>
<p><a href="/index" th:href="@{/index}">Back to home page</a></p>
</form>
アプリケーションを実行してhttp://localhost:8081でコンテキストにアクセスすると、保護されたページにアクセスするためのリンクが表示されます。リンクをクリックするとログインページが表示されます。予想通り、
追加のドメインフィールド
が表示されます。
3.5. 概要
最初の例では、usernameフィールドを「偽造」することで、
DaoAuthenticationProvider
と
UsernamePasswordAuthenticationToken
を再利用できました。
その結果、最小限の設定と追加のコードで** 追加のログインフィールドのサポートを追加することができました。
4カスタムプロジェクト設定
私たちの2番目のアプローチは最初のものと非常に似ているでしょうが、自明でないユースケースのためにより適切であるかもしれません。
私たちの2番目のアプローチの主な要素は次のとおりです。
-
CustomAuthenticationFilter
–
の拡張子
UsernamePasswordAuthenticationFilter
CustomUserDetailsService
– ** を宣言するカスタムインタフェース
loadUserbyUsernameAndDomain
メソッド
CustomUserDetailsServiceImpl
– ** 私たちの実装
CustomUserDetailsService
CustomUserDetailsAuthenticationProvider
– ** の拡張子
AbstractUserDetailsAuthenticationProvider
CustomAuthenticationToken
– ** の拡張子
UsernamePasswordAuthenticationToken
User
– ** Springが提供する
User
クラスの拡張
追加の
domain
フィールドを宣言するセキュリティ
SecurityConfig
– ** 挿入するSpring Securityの設定
CustomAuthenticationFilter
をフィルタチェーンに追加し、宣言します。
セキュリティルールと依存関係の配線
login.html
– **
ユーザー名
を収集するログインページ
password
、および
domain
4.1. カスタム認証フィルタ
CustomAuthenticationFilter
では、リクエストからユーザー名、パスワード、ドメインの各フィールドを抽出します。これらの値は、認証のために
AuthenticationProvider
に渡されるCustom
__AuthenticationToken
__のインスタンスを作成するために使用されます。
public class CustomAuthenticationFilter
extends UsernamePasswordAuthenticationFilter {
public static final String SPRING__SECURITY__FORM__DOMAIN__KEY = "domain";
@Override
public Authentication attemptAuthentication(
HttpServletRequest request,
HttpServletResponse response)
throws AuthenticationException {
//...
CustomAuthenticationToken authRequest = getAuthRequest(request);
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
private CustomAuthenticationToken getAuthRequest(HttpServletRequest request) {
String username = obtainUsername(request);
String password = obtainPassword(request);
String domain = obtainDomain(request);
//...
return new CustomAuthenticationToken(username, password, domain);
}
4.2. カスタム
ユーザー詳細
サービス
CustomUserDetailsService
コントラクトでは、
loadUserByUsernameAndDomain.
という名前の単一のメソッドを定義しています。
作成した
CustomUserDetailsServiceImpl
クラスは単純にコントラクトを実装し、
User
を取得するために
CustomUserRepository
に委任します。
public UserDetails loadUserByUsernameAndDomain(String username, String domain)
throws UsernameNotFoundException {
if (StringUtils.isAnyBlank(username, domain)) {
throw new UsernameNotFoundException("Username and domain must be provided");
}
User user = userRepository.findUser(username, domain);
if (user == null) {
throw new UsernameNotFoundException(
String.format("Username not found for domain, username=%s, domain=%s",
username, domain));
}
return user;
}
4.3. カスタム
UserDetailsAuthenticationProvider
CustomUserDetailsAuthenticationProvider
は
AbstractUserDetailsAuthenticationProvider
を拡張し、
CustomUserDetailService
に委任して
User
を取得します。
このクラスの最も重要な機能は
retrieveUser
メソッドの実装です
。
カスタムフィールドにアクセスするには、認証トークンを
CustomAuthenticationToken
にキャストする必要があります。
@Override
protected UserDetails retrieveUser(String username,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
CustomAuthenticationToken auth = (CustomAuthenticationToken) authentication;
UserDetails loadedUser;
try {
loadedUser = this.userDetailsService
.loadUserByUsernameAndDomain(auth.getPrincipal()
.toString(), auth.getDomain());
} catch (UsernameNotFoundException notFound) {
if (authentication.getCredentials() != null) {
String presentedPassword = authentication.getCredentials()
.toString();
passwordEncoder.matches(presentedPassword, userNotFoundEncodedPassword);
}
throw notFound;
} catch (Exception repositoryProblem) {
throw new InternalAuthenticationServiceException(
repositoryProblem.getMessage(), repositoryProblem);
}
//...
return loadedUser;
}
4.4. 概要
私たちの2番目のアプローチは、最初に紹介した単純なアプローチとほぼ同じです。独自の
AuthenticationProvider
および
CustomAuthenticationToken
を実装することで、ユーザー名フィールドをカスタム解析ロジックで調整する必要がなくなりました。
5結論
この記事では、Spring Securityに追加のログインフィールドを利用したフォームログインを実装しました。これを2つの方法で行いました。
-
簡単な方法では、必要なコード量を最小限に抑えました。
書きます。
DaoAuthenticationProvider
を再利用することができました。
ユーザー名
をカスタムに適応させたUsernamePasswordAuthentication
解析ロジック
私達のよりカスタマイズされたアプローチでは、私達はによってカスタムフィールドサポートを提供しました
-
AbstractUserDetailsAuthenticationProviderを拡張し、
CustomAuthenticationToken
を使用して独自の
CustomUserDetailsService
を提供する**
いつものように、すべてのソースコードはhttps://github.com/eugenp/tutorials/tree/master/spring-5-security[over GitHub]で見つけることができます。