1.概述
在本教程中,我们将使用Okta作为身份提供者(IdP)探索Spring Security SAML。
2.什么是SAML?
安全声明标记语言(SAML)是一种开放标准,允许IdP将用户的身份验证和授权详细信息安全地发送到服务提供商(SP) 。它使用基于XML的消息进行IdP和SP之间的通信。
换句话说,当用户尝试访问服务时,要求他使用IdP登录。登录后, IdP将带有XML格式的授权和身份验证详细信息的SAML属性发送到SP。
除了提供安全的身份验证传输机制外, SAML还促进了单一登录(SSO) ,允许用户登录一次并重复使用相同的凭据登录其他服务提供商。
3. Okta SAML设置
首先,作为先决条件,我们应该设置一个Okta开发人员账户。
3.1。创建新的应用程序
然后,我们将创建一个具有SAML 2.0支持的新Web应用程序集成:
接下来,我们将填写常规信息,例如App名称和App徽标:
3.2。编辑SAML集成
在此步骤中,我们将提供SAML设置,例如SSO URL和Audience URI:
最后,我们可以提供有关集成的反馈:
3.3。查看安装说明
完成后,我们可以查看我们的Spring Boot App的设置说明:
注意:我们应该复制IdP发行者URL和IdP元数据XML之类的说明,这些要求在Spring Security配置中将进一步需要:
4. Spring Boot设置
除了通常的Maven依赖项(例如spring-boot-starter-web
和spring-boot-starter-security
,我们还需要spring-security-saml2-core
依赖项:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.4.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.4.2</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
依赖项所需**opensaml
jar:**
<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.4.2"
compile group: 'org.springframework.boot', name: 'spring-boot-starter-security', version: "2.4.2"
compile group: 'org.springframework.security.extensions', name: 'spring-security-saml2-core', version: "1.0.10.RELEASE"
5. Spring安全配置
现在我们已经准备好Okta SAML安装程序和Spring Boot项目,让我们从与Okta集成SAML 2.0所需的Spring Security配置开始。
5.1 SAML入口点
首先,我们将创建SAMLEntryPoint
类的bean,它将用作SAML身份验证的入口点:
@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 登录和注销
接下来,让我们为SAML URI创建一些过滤器,例如/ discovery,
/ login
和/ logout
:
@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
会将用户重定向到/error
URL。
最后,让我们为单个和全局注销添加注销处理程序:
@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 元数据处理
现在,我们将向SP提供IdP元数据XML。一旦用户登录,让我们的IdP知道应该重定向到哪个SP端点将很有帮助。
因此,我们将配置MetadataGenerator
bean来启用Spring SAML处理元数据:
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需要KeyManager
的实例来加密SP和IdP之间的交换:
@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);
}
在这里,我们必须创建一个密钥库并将其提供给KeyManager
bean。我们可以使用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 {
File metadata = null;
try {
metadata = new File("./src/main/resources/saml/metadata/sso.xml");
} catch (Exception e) {
e.printStackTrace();
}
FilesystemMetadataProvider provider = new FilesystemMetadataProvider(metadata);
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;
}
在这里,我们从sso.xml
文件中解析了包含IdP元数据XML的元数据,该文件是在查看设置说明时从Okta开发人员账户复制的。
同样, 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();
}
}
}
另外,我们应该SecurityConfig
类CustomSAMLAuthenticationProvider
配置为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");
});
}
瞧!我们完成了Spring Security SAML配置,该配置允许用户登录到IdP,然后从IdP接收XML格式的用户身份验证详细信息。最后,它对用户令牌进行身份验证,以允许访问我们的Web应用程序。
6. HomeController
现在我们已经准备好了Spring Security SAML配置以及Okta开发者账户设置,我们可以设置一个简单的控制器来提供登录页面和主页。
6.1 索引和授权映射
首先,让我们将映射添加到默认目标URI (/)
和/ auth
URI:
@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 "/";
}
}
然后,我们将添加一个简单的index.html
,它允许用户使用login
链接重定向Okta SAML身份验证:
<!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 App并通过http:// localhost:8080 /进行访问:
0 评论