<saml2:Response>
の認証
SAML 2.0 レスポンスを検証するために、Spring Security は Saml2AuthenticationTokenConverter
を使用して Authentication
リクエストを入力し、OpenSaml4AuthenticationProvider
を使用してそれを認証します。
これは、次のようないくつかの方法で構成できます。
RelyingPartyRegistration
の検索方法の変更タイムスタンプ検証へのクロックスキューの設定
レスポンスを
GrantedAuthority
インスタンスのリストにマッピングするアサーションを検証するための戦略のカスタマイズ
レスポンス要素とアサーション要素を復号化するための戦略のカスタマイズ
これらを構成するには、DSL で saml2Login#authenticationManager
メソッドを使用します。
SAML レスポンス処理エンドポイントの変更
デフォルトのエンドポイントは /login/saml2/sso/{registrationId}
です。これは、DSL および関連するメタデータで次のように変更できます。
Java
Kotlin
@Bean
SecurityFilterChain securityFilters(HttpSecurity http) throws Exception {
http
// ...
.saml2Login((saml2) -> saml2.loginProcessingUrl("/saml2/login/sso"))
// ...
return http.build();
}
@Bean
fun securityFilters(val http: HttpSecurity): SecurityFilterChain {
http {
// ...
.saml2Login {
loginProcessingUrl = "/saml2/login/sso"
}
// ...
}
return http.build()
}
および:
Java
Kotlin
relyingPartyRegistrationBuilder.assertionConsumerServiceLocation("/saml/SSO")
relyingPartyRegistrationBuilder.assertionConsumerServiceLocation("/saml/SSO")
RelyingPartyRegistration
ルックアップの変更
デフォルトでは、このコンバーターは、関連付けられている <saml2:AuthnRequest>
または URL で見つかった registrationId
と照合します。または、これらのケースのいずれかで見つからない場合は、<saml2:Response#Issuer>
要素によって検索を試みます。
ARTIFACT
バインディングをサポートしている場合など、より洗練されたものが必要になる状況がいくつかあります。そのような場合、次のようにカスタマイズできるカスタム AuthenticationConverter
を介してルックアップをカスタマイズできます。
Java
Kotlin
@Bean
SecurityFilterChain securityFilters(HttpSecurity http, AuthenticationConverter authenticationConverter) throws Exception {
http
// ...
.saml2Login((saml2) -> saml2.authenticationConverter(authenticationConverter))
// ...
return http.build();
}
@Bean
fun securityFilters(val http: HttpSecurity, val converter: AuthenticationConverter): SecurityFilterChain {
http {
// ...
.saml2Login {
authenticationConverter = converter
}
// ...
}
return http.build()
}
クロックスキューの設定
アサーティングパーティと依存パーティが完全に同期されていないシステムクロックを持つことは珍しくありません。そのため、OpenSaml4AuthenticationProvider
のデフォルトのアサーションバリデーターをある程度の許容範囲で構成できます。
Java
Kotlin
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
OpenSaml4AuthenticationProvider authenticationProvider = new OpenSaml4AuthenticationProvider();
authenticationProvider.setAssertionValidator(OpenSaml4AuthenticationProvider
.createDefaultAssertionValidatorWithParameters(assertionToken -> {
Map<String, Object> params = new HashMap<>();
params.put(CLOCK_SKEW, Duration.ofMinutes(10).toMillis());
// ... other validation parameters
return new ValidationContext(params);
})
);
http
.authorizeHttpRequests(authz -> authz
.anyRequest().authenticated()
)
.saml2Login(saml2 -> saml2
.authenticationManager(new ProviderManager(authenticationProvider))
);
return http.build();
}
}
@Configuration
@EnableWebSecurity
open class SecurityConfig {
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
val authenticationProvider = OpenSaml4AuthenticationProvider()
authenticationProvider.setAssertionValidator(
OpenSaml4AuthenticationProvider
.createDefaultAssertionValidatorWithParameters(Converter<OpenSaml4AuthenticationProvider.AssertionToken, ValidationContext> {
val params: MutableMap<String, Any> = HashMap()
params[CLOCK_SKEW] =
Duration.ofMinutes(10).toMillis()
ValidationContext(params)
})
)
http {
authorizeRequests {
authorize(anyRequest, authenticated)
}
saml2Login {
authenticationManager = ProviderManager(authenticationProvider)
}
}
return http.build()
}
}
UserDetailsService
との調整
または、レガシー UserDetailsService
からのユーザーの詳細を含めることもできます。その場合、以下に示すように、レスポンス認証コンバーターが役立ちます。
Java
Kotlin
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
UserDetailsService userDetailsService;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
OpenSaml4AuthenticationProvider authenticationProvider = new OpenSaml4AuthenticationProvider();
authenticationProvider.setResponseAuthenticationConverter(responseToken -> {
Saml2Authentication authentication = OpenSaml4AuthenticationProvider
.createDefaultResponseAuthenticationConverter() (1)
.convert(responseToken);
Assertion assertion = responseToken.getResponse().getAssertions().get(0);
String username = assertion.getSubject().getNameID().getValue();
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); (2)
return MySaml2Authentication(userDetails, authentication); (3)
});
http
.authorizeHttpRequests(authz -> authz
.anyRequest().authenticated()
)
.saml2Login(saml2 -> saml2
.authenticationManager(new ProviderManager(authenticationProvider))
);
return http.build();
}
}
@Configuration
@EnableWebSecurity
open class SecurityConfig {
@Autowired
var userDetailsService: UserDetailsService? = null
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
val authenticationProvider = OpenSaml4AuthenticationProvider()
authenticationProvider.setResponseAuthenticationConverter { responseToken: OpenSaml4AuthenticationProvider.ResponseToken ->
val authentication = OpenSaml4AuthenticationProvider
.createDefaultResponseAuthenticationConverter() (1)
.convert(responseToken)
val assertion: Assertion = responseToken.response.assertions[0]
val username: String = assertion.subject.nameID.value
val userDetails = userDetailsService!!.loadUserByUsername(username) (2)
MySaml2Authentication(userDetails, authentication) (3)
}
http {
authorizeRequests {
authorize(anyRequest, authenticated)
}
saml2Login {
authenticationManager = ProviderManager(authenticationProvider)
}
}
return http.build()
}
}
1 | 最初に、レスポンスから属性と権限を抽出するデフォルトのコンバーターを呼び出します |
2 | 次に、関連情報を使用して UserDetailsService を呼び出します |
3 | 3 番目に、ユーザーの詳細を含むカスタム認証を返します |
OpenSaml4AuthenticationProvider のデフォルトの認証コンバーターを呼び出す必要はありません。AttributeStatement から抽出した属性を含む Saml2AuthenticatedPrincipal と単一の ROLE_USER 権限を返します。 |
追加のレスポンス検証の実行
OpenSaml4AuthenticationProvider
は、Response
を復号化した直後に、Issuer
値と Destination
値を検証します。独自のレスポンスバリデーターと連結するデフォルトのバリデーターを継承することで検証をカスタマイズすることも、完全に自分のものに置き換えることもできます。
例: 次のように、Response
オブジェクトで利用可能な追加情報を使用してカスタム例外をスローできます。
OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider();
provider.setResponseValidator((responseToken) -> {
Saml2ResponseValidatorResult result = OpenSamlAuthenticationProvider
.createDefaultResponseValidator()
.convert(responseToken)
.concat(myCustomValidator.convert(responseToken));
if (!result.getErrors().isEmpty()) {
String inResponseTo = responseToken.getInResponseTo();
throw new CustomSaml2AuthenticationException(result, inResponseTo);
}
return result;
});
追加のアサーション検証の実行
OpenSaml4AuthenticationProvider
は、SAML 2.0 アサーションに対して最小限の検証を実行します。署名を確認すると、次のようになります。
<AudienceRestriction>
および<DelegationRestriction>
条件を検証する<SubjectConfirmation>
を検証し、任意の IP アドレス情報を期待する
追加の検証を実行するには、OpenSaml4AuthenticationProvider
のデフォルトに委譲して独自に実行する独自のアサーションバリデーターを構成できます。
例: OpenSAML の OneTimeUseConditionValidator
を使用して、<OneTimeUse>
条件を検証することもできます。
Java
Kotlin
OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider();
OneTimeUseConditionValidator validator = ...;
provider.setAssertionValidator(assertionToken -> {
Saml2ResponseValidatorResult result = OpenSaml4AuthenticationProvider
.createDefaultAssertionValidator()
.convert(assertionToken);
Assertion assertion = assertionToken.getAssertion();
OneTimeUse oneTimeUse = assertion.getConditions().getOneTimeUse();
ValidationContext context = new ValidationContext();
try {
if (validator.validate(oneTimeUse, assertion, context) = ValidationResult.VALID) {
return result;
}
} catch (Exception e) {
return result.concat(new Saml2Error(INVALID_ASSERTION, e.getMessage()));
}
return result.concat(new Saml2Error(INVALID_ASSERTION, context.getValidationFailureMessage()));
});
var provider = OpenSaml4AuthenticationProvider()
var validator: OneTimeUseConditionValidator = ...
provider.setAssertionValidator { assertionToken ->
val result = OpenSaml4AuthenticationProvider
.createDefaultAssertionValidator()
.convert(assertionToken)
val assertion: Assertion = assertionToken.assertion
val oneTimeUse: OneTimeUse = assertion.conditions.oneTimeUse
val context = ValidationContext()
try {
if (validator.validate(oneTimeUse, assertion, context) = ValidationResult.VALID) {
return@setAssertionValidator result
}
} catch (e: Exception) {
return@setAssertionValidator result.concat(Saml2Error(INVALID_ASSERTION, e.message))
}
result.concat(Saml2Error(INVALID_ASSERTION, context.validationFailureMessage))
}
推奨されていますが、OpenSaml4AuthenticationProvider のデフォルトのアサーションバリデーターを呼び出す必要はありません。スキップする状況は、<AudienceRestriction> または <SubjectConfirmation> を自分でチェックする必要がないためです。 |
復号化のカスタマイズ
Spring Security は、RelyingPartyRegistration
に登録されている復号化 Saml2X509Credential
インスタンスを使用して、<saml2:EncryptedAssertion>
、<saml2:EncryptedAttribute>
、<saml2:EncryptedID>
要素を自動的に復号化します。
OpenSaml4AuthenticationProvider
は、2 つの復号化戦略を公開しています。レスポンス復号化機能は、<saml2:EncryptedAssertion>
などの <saml2:Response>
の暗号化された要素を復号化するためのものです。アサーション復号化機能は、<saml2:EncryptedAttribute>
や <saml2:EncryptedID>
などの <saml2:Assertion>
の暗号化された要素を復号化するためのものです。
OpenSaml4AuthenticationProvider
のデフォルトの復号化戦略を独自のものに置き換えることができます。例: <saml2:Response>
のアサーションを復号化する別のサービスがある場合は、代わりに次のように使用できます。
Java
Kotlin
MyDecryptionService decryptionService = ...;
OpenSaml4AuthenticationProvider provider = new OpenSaml4AuthenticationProvider();
provider.setResponseElementsDecrypter((responseToken) -> decryptionService.decrypt(responseToken.getResponse()));
val decryptionService: MyDecryptionService = ...
val provider = OpenSaml4AuthenticationProvider()
provider.setResponseElementsDecrypter { responseToken -> decryptionService.decrypt(responseToken.response) }
<saml2:Assertion>
の個々の要素も復号化する場合は、アサーション復号化機能をカスタマイズすることもできます。
Java
Kotlin
provider.setAssertionElementsDecrypter((assertionToken) -> decryptionService.decrypt(assertionToken.getAssertion()));
provider.setAssertionElementsDecrypter { assertionToken -> decryptionService.decrypt(assertionToken.assertion) }
アサーションはレスポンスとは別に署名できるため、2 つの別個の復号化機能があります。署名の検証前に署名されたアサーションの要素を復号化しようとすると、署名が無効になる場合があります。主張する当事者がレスポンスのみに署名する場合は、レスポンス復号化機能のみを使用してすべての要素を復号化しても安全です。 |
カスタム認証マネージャーの使用
もちろん、authenticationManager
DSL メソッドを使用して、完全にカスタムの SAML 2.0 認証を実行することもできます。この認証マネージャーは、SAML 2.0 レスポンス XML データを含む Saml2AuthenticationToken
オブジェクトを予期する必要があります。
Java
Kotlin
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
AuthenticationManager authenticationManager = new MySaml2AuthenticationManager(...);
http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
.saml2Login(saml2 -> saml2
.authenticationManager(authenticationManager)
)
;
return http.build();
}
}
@Configuration
@EnableWebSecurity
open class SecurityConfig {
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
val customAuthenticationManager: AuthenticationManager = MySaml2AuthenticationManager(...)
http {
authorizeRequests {
authorize(anyRequest, authenticated)
}
saml2Login {
authenticationManager = customAuthenticationManager
}
}
return http.build()
}
}
Saml2AuthenticatedPrincipal
を使用する
依存パーティが特定のアサーティングパーティに対して正しく構成されているため、アサーションを受け入れる準備ができています。依存パーティがアサーションを検証すると、結果は Saml2AuthenticatedPrincipal
を含む Saml2Authentication
になります。
つまり、次のようにコントローラーのプリンシパルにアクセスできます。
Java
Kotlin
@Controller
public class MainController {
@GetMapping("/")
public String index(@AuthenticationPrincipal Saml2AuthenticatedPrincipal principal, Model model) {
String email = principal.getFirstAttribute("email");
model.setAttribute("email", email);
return "index";
}
}
@Controller
class MainController {
@GetMapping("/")
fun index(@AuthenticationPrincipal principal: Saml2AuthenticatedPrincipal, model: Model): String {
val email = principal.getFirstAttribute<String>("email")
model.setAttribute("email", email)
return "index"
}
}
SAML 2.0 仕様では、各属性に複数の値を設定できるため、getAttribute を呼び出して属性のリストを取得するか、getFirstAttribute を呼び出してリストの最初の属性を取得できます。getFirstAttribute は、値が 1 つしかないことがわかっている場合に非常に便利です。 |