SpringSecurityを使用したSAMLのガイド
1. 概要
このチュートリアルでは、SpringセキュリティSAMLとOktaをIDプロバイダー(IdP)として使用する方法について説明します。
2. SAMLとは何ですか?
Security Assertion Markup Language(SAML)は、IdPがユーザーの認証と承認の詳細をサービスプロバイダー(SP)に安全に送信できるようにするオープンスタンダードです。 IdPとSP間の通信にXMLベースのメッセージを使用します。
つまり、ユーザーがサービスにアクセスしようとすると、IdPを使用してログインする必要があります。 ログインすると、IdPは承認と認証の詳細を含むSAML属性をXML形式でSPに送信します。
安全な認証送信メカニズムを提供するほかに、 SAMLはシングルサインオン(SSO)も促進し、ユーザーが一度ログインして同じ資格情報を再利用して他のサービスプロバイダーにログインできるようにします。
3. OktaSAMLセットアップ
まず、前提条件として、Okta開発者アカウントを設定する必要があります。
3.1. 新しいアプリケーションを作成する
次に、SAML2.0をサポートする新しいWebアプリケーション統合を作成します。
次に、アプリ名やアプリのロゴなどの一般的な情報を入力します。
3.2. SAML統合の編集
このステップでは、SSOURLやオーディエンスURIなどのSAML設定を提供します。
最後に、統合に関するフィードバックを提供できます。
3.3. セットアップ手順を表示する
完了すると、SpringBootAppのセットアップ手順を表示できます。
注:SpringSecurity構成でさらに必要となるIdP発行者URLやIdPメタデータXMLなどの手順をコピーする必要があります。
4. スプリングブートセットアップ
spring-boot-starter-webやspring-boot-starter-security、などの通常のMaven依存関係以外に、spring-が必要になります。 security-saml2-core 依存関係:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.6.1</version>
</dependency>
<dependency>
<groupId>org.springframework.security.extensions</groupId>
<artifactId>spring-security-saml2-core</artifactId>
<version>1.0.10.RELEASE</version>
</dependency>
また、Shibbolethリポジトリを追加して、spring-security-saml2-core依存関係に必要な最新のopensamljarをダウンロードしてください。
<repository>
<id>Shibboleth</id>
<name>Shibboleth</name>
<url>https://build.shibboleth.net/nexus/content/repositories/releases/</url>
</repository>
または、Gradleプロジェクトで依存関係を設定することもできます。
compile group: 'org.springframework.boot', name: 'spring-boot-starter-web', version: "2.5.1"
compile group: 'org.springframework.boot', name: 'spring-boot-starter-security', version: "2.5.1"
compile group: 'org.springframework.security.extensions', name: 'spring-security-saml2-core', version: "1.0.10.RELEASE"
5. Springセキュリティ構成
Okta SAMLセットアップとSpring Bootプロジェクトの準備ができたので、SAML2.0とOktaの統合に必要なSpringセキュリティ構成から始めましょう。
5.1. SAMLエントリポイント
まず、SAML認証のエントリポイントとして機能するSAMLEntryPointクラスのbeanを作成します。
@Bean
public WebSSOProfileOptions defaultWebSSOProfileOptions() {
WebSSOProfileOptions webSSOProfileOptions = new WebSSOProfileOptions();
webSSOProfileOptions.setIncludeScoping(false);
return webSSOProfileOptions;
}
@Bean
public SAMLEntryPoint samlEntryPoint() {
SAMLEntryPoint samlEntryPoint = new SAMLEntryPoint();
samlEntryPoint.setDefaultProfileOptions(defaultWebSSOProfileOptions());
return samlEntryPoint;
}
ここで、 WebSSOProfileOptions Beanを使用すると、SPからIdPに送信されてユーザー認証を要求する要求のパラメーターを設定できます。
5.2. ログインとログアウト
次に、/ Discovery、 / login 、/ logoutなどのSAMLURI用のフィルターをいくつか作成しましょう。
@Bean
public FilterChainProxy samlFilter() throws Exception {
List<SecurityFilterChain> chains = new ArrayList<>();
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SSO/**"),
samlWebSSOProcessingFilter()));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/discovery/**"),
samlDiscovery()));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/login/**"),
samlEntryPoint));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/logout/**"),
samlLogoutFilter));
chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SingleLogout/**"),
samlLogoutProcessingFilter));
return new FilterChainProxy(chains);
}
次に、対応するフィルターとハンドラーをいくつか追加します。
@Bean
public SAMLProcessingFilter samlWebSSOProcessingFilter() throws Exception {
SAMLProcessingFilter samlWebSSOProcessingFilter = new SAMLProcessingFilter();
samlWebSSOProcessingFilter.setAuthenticationManager(authenticationManager());
samlWebSSOProcessingFilter.setAuthenticationSuccessHandler(successRedirectHandler());
samlWebSSOProcessingFilter.setAuthenticationFailureHandler(authenticationFailureHandler());
return samlWebSSOProcessingFilter;
}
@Bean
public SAMLDiscovery samlDiscovery() {
SAMLDiscovery idpDiscovery = new SAMLDiscovery();
return idpDiscovery;
}
@Bean
public SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler() {
SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler = new SavedRequestAwareAuthenticationSuccessHandler();
successRedirectHandler.setDefaultTargetUrl("/home");
return successRedirectHandler;
}
@Bean
public SimpleUrlAuthenticationFailureHandler authenticationFailureHandler() {
SimpleUrlAuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();
failureHandler.setUseForward(true);
failureHandler.setDefaultFailureUrl("/error");
return failureHandler;
}
これまで、認証用のエントリポイント( samlEntryPoint )といくつかのフィルターチェーンを構成しました。 それでは、それらの詳細を深く掘り下げてみましょう。
ユーザーが初めてログインしようとすると、samlEntryPointがエントリ要求を処理します。 次に、 samlDiscovery Bean(有効な場合)は、認証のために接続するIdPを検出します。
次に、ユーザーがログインすると、IdPはSAML応答を/saml / sso URIにリダイレクトしてを処理し、対応するsamlWebSSOProcessingFilterは関連する認証トークンを認証します。
成功すると、 successRedirectHandler はユーザーをデフォルトのターゲットURL( / home )にリダイレクトします。 それ以外の場合、authenticationFailureHandlerはユーザーを/errorURLにリダイレクトします。
最後に、シングルおよびグローバルログアウト用のログアウトハンドラーを追加しましょう。
@Bean
public SimpleUrlLogoutSuccessHandler successLogoutHandler() {
SimpleUrlLogoutSuccessHandler successLogoutHandler = new SimpleUrlLogoutSuccessHandler();
successLogoutHandler.setDefaultTargetUrl("/");
return successLogoutHandler;
}
@Bean
public SecurityContextLogoutHandler logoutHandler() {
SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler();
logoutHandler.setInvalidateHttpSession(true);
logoutHandler.setClearAuthentication(true);
return logoutHandler;
}
@Bean
public SAMLLogoutProcessingFilter samlLogoutProcessingFilter() {
return new SAMLLogoutProcessingFilter(successLogoutHandler(), logoutHandler());
}
@Bean
public SAMLLogoutFilter samlLogoutFilter() {
return new SAMLLogoutFilter(successLogoutHandler(),
new LogoutHandler[] { logoutHandler() },
new LogoutHandler[] { logoutHandler() });
}
5.3. メタデータ処理
次に、IdPメタデータXMLをSPに提供します。 ユーザーがログインしたときにリダイレクトするSPエンドポイントをIdPに知らせると役立ちます。
したがって、 MetadataGenerator beanを構成して、SpringSAMLがメタデータを処理できるようにします。
public MetadataGenerator metadataGenerator() {
MetadataGenerator metadataGenerator = new MetadataGenerator();
metadataGenerator.setEntityId(samlAudience);
metadataGenerator.setExtendedMetadata(extendedMetadata());
metadataGenerator.setIncludeDiscoveryExtension(false);
metadataGenerator.setKeyManager(keyManager());
return metadataGenerator;
}
@Bean
public MetadataGeneratorFilter metadataGeneratorFilter() {
return new MetadataGeneratorFilter(metadataGenerator());
}
@Bean
public ExtendedMetadata extendedMetadata() {
ExtendedMetadata extendedMetadata = new ExtendedMetadata();
extendedMetadata.setIdpDiscoveryEnabled(false);
return extendedMetadata;
}
MetadataGenerator Beanには、SPとIdP間の交換を暗号化するためにKeyManagerのインスタンスが必要です。
@Bean
public KeyManager keyManager() {
DefaultResourceLoader loader = new DefaultResourceLoader();
Resource storeFile = loader.getResource(samlKeystoreLocation);
Map<String, String> passwords = new HashMap<>();
passwords.put(samlKeystoreAlias, samlKeystorePassword);
return new JKSKeyManager(storeFile, samlKeystorePassword, passwords, samlKeystoreAlias);
}
ここでは、キーストアを作成してKeyManagerBeanに提供する必要があります。 JREコマンドを使用して、自己署名キーとキーストアを作成できます。
keytool -genkeypair -alias baeldungspringsaml -keypass baeldungsamlokta -keystore saml-keystore.jks
5.4. MetadataManager
次に、 ExtendedMetadataDelegate インスタンスを使用して、IdPメタデータをSpring Bootアプリケーションに構成します。
@Bean
@Qualifier("okta")
public ExtendedMetadataDelegate oktaExtendedMetadataProvider() throws MetadataProviderException {
org.opensaml.util.resource.Resource resource = null
try {
resource = new ClasspathResource("/saml/metadata/sso.xml");
} catch (ResourceException e) {
e.printStackTrace();
}
Timer timer = new Timer("saml-metadata")
ResourceBackedMetadataProvider provider = new ResourceBackedMetadataProvider(timer,resource);
provider.setParserPool(parserPool());
return new ExtendedMetadataDelegate(provider, extendedMetadata());
}
@Bean
@Qualifier("metadata")
public CachingMetadataManager metadata() throws MetadataProviderException, ResourceException {
List<MetadataProvider> providers = new ArrayList<>();
providers.add(oktaExtendedMetadataProvider());
CachingMetadataManager metadataManager = new CachingMetadataManager(providers);
metadataManager.setDefaultIDP(defaultIdp);
return metadataManager;
}
ここでは、セットアップ手順を表示しながらOkta開発者アカウントからコピーされたIdPメタデータXMLを含むsso.xmlファイルのメタデータを解析しました。
同様に、 defaultIdp 変数には、Okta開発者アカウントからコピーされたIdP発行者のURLが含まれています。
5.5. XML解析
XML解析には、StaticBasicParserPoolクラスのインスタンスを使用できます。
@Bean(initMethod = "initialize")
public StaticBasicParserPool parserPool() {
return new StaticBasicParserPool();
}
@Bean(name = "parserPoolHolder")
public ParserPoolHolder parserPoolHolder() {
return new ParserPoolHolder();
}
5.6. SAMLプロセッサ
次に、HTTPリクエストからのSAMLメッセージを解析するプロセッサが必要です。
@Bean
public HTTPPostBinding httpPostBinding() {
return new HTTPPostBinding(parserPool(), VelocityFactory.getEngine());
}
@Bean
public HTTPRedirectDeflateBinding httpRedirectDeflateBinding() {
return new HTTPRedirectDeflateBinding(parserPool());
}
@Bean
public SAMLProcessorImpl processor() {
ArrayList<SAMLBinding> bindings = new ArrayList<>();
bindings.add(httpRedirectDeflateBinding());
bindings.add(httpPostBinding());
return new SAMLProcessorImpl(bindings);
}
ここでは、Okta開発者アカウントの構成に関してPOSTおよびリダイレクトバインディングを使用しました。
5.7. SAMLAuthenticationProviderの実装
最後に、 SAMLAuthenticationProvider クラスのカスタム実装が必要で、 ExpiringUsernameAuthenticationToken クラスのインスタンスをチェックし、取得した権限を設定します。
public class CustomSAMLAuthenticationProvider extends SAMLAuthenticationProvider {
@Override
public Collection<? extends GrantedAuthority> getEntitlements(SAMLCredential credential, Object userDetail) {
if (userDetail instanceof ExpiringUsernameAuthenticationToken) {
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.addAll(((ExpiringUsernameAuthenticationToken) userDetail).getAuthorities());
return authorities;
} else {
return Collections.emptyList();
}
}
}
また、CustomSAMLAuthenticationProviderをSecurityConfigクラスのBeanとして構成する必要があります。
@Bean
public SAMLAuthenticationProvider samlAuthenticationProvider() {
return new CustomSAMLAuthenticationProvider();
}
5.8. SecurityConfig
最後に、すでに説明したsamlEntryPointおよびsamlFilterを使用して、基本的なHTTPセキュリティを構成します。
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.httpBasic().authenticationEntryPoint(samlEntryPoint);
http
.addFilterBefore(metadataGeneratorFilter(), ChannelProcessingFilter.class)
.addFilterAfter(samlFilter(), BasicAuthenticationFilter.class)
.addFilterBefore(samlFilter(), CsrfFilter.class);
http
.authorizeRequests()
.antMatchers("/").permitAll()
.anyRequest().authenticated();
http
.logout()
.addLogoutHandler((request, response, authentication) -> {
response.sendRedirect("/saml/logout");
});
}
出来上がり! ユーザーがIdPにログインし、IdPからXML形式でユーザーの認証の詳細を受信できるようにするSpringセキュリティSAML構成を完了しました。 最後に、ユーザートークンを認証して、Webアプリへのアクセスを許可します。
6. HomeController
Spring Security SAML構成とOkta開発者アカウントのセットアップの準備ができたので、シンプルなコントローラーをセットアップしてランディングページとホームページを提供できます。
6.1. インデックスと認証のマッピング
まず、デフォルトのターゲットURI (/)および/ authURIにマッピングを追加しましょう。
@RequestMapping("/")
public String index() {
return "index";
}
@GetMapping(value = "/auth")
public String handleSamlAuth() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) {
return "redirect:/home";
} else {
return "/";
}
}
次に、ユーザーがloginリンクを使用してOktaSAML認証をリダイレクトできるようにする単純なindex.htmlを追加します。
<!doctype html>
<html>
<head>
<title>Baeldung Spring Security SAML</title>
</head>
<body>
<h3><Strong>Welcome to Baeldung Spring Security SAML</strong></h3>
<a th:href="@{/auth}">Login</a>
</body>
</html>
これで、Spring Bootアプリを実行し、 http:// localhost:8080/でアクセスする準備が整いました。
ログインリンクをクリックすると、Oktaサインインページが開きます。
6.2. ホームページ
次に、マッピングを / home URIに追加して、認証に成功したときにユーザーをリダイレクトしましょう。
@RequestMapping("/home")
public String home(Model model) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
model.addAttribute("username", authentication.getPrincipal());
return "home";
}
また、 home.html を追加して、ログインしているユーザーとログアウトリンクを表示します。
<!doctype html>
<html>
<head>
<title>Baeldung Spring Security SAML: Home</title>
</head>
<body>
<h3><Strong>Welcome!</strong><br/>You are successfully logged in!</h3>
<p>You are logged as <span th:text="${username}">null</span>.</p>
<small>
<a th:href="@{/logout}">Logout</a>
</small>
</body>
</html>
正常にログインすると、ホームページが表示されます。
7. 結論
このチュートリアルでは、SpringSecuritySAMLとOktaの統合について説明しました。
まず、SAML2.0Web統合を使用してOkta開発者アカウントを設定します。 次に、必要なMaven依存関係を持つSpringBootプロジェクトを作成しました。
次に、samlEntryPoint、samlFilter、メタデータ処理、SAMLプロセッサなど、SpringSecuritySAMLに必要なすべてのセットアップを実行しました。
最後に、コントローラーと、indexやhomeなどのいくつかのページを作成して、OktaとのSAML統合をテストしました。
いつものように、ソースコードはGitHubでから入手できます。