拨开荷叶行,寻梦已然成。仙女莲花里,翩翩白鹭情。
IMG-LOGO
主页 文章列表 Spring Security的SAML指南

Spring Security的SAML指南

白鹭 - 2021-11-24 1276 0 0

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应用程序集成:

Spring

接下来,我们将填写常规信息,例如App名称和App徽标:

Spring

3.2。编辑SAML集成

在此步骤中,我们将提供SAML设置,例如SSO URL和Audience URI:

Spring

最后,我们可以提供有关集成的反馈:

Spring

3.3。查看安装说明

完成后,我们可以查看我们的Spring Boot App的设置说明:

Spring

注意:我们应该复制IdP发行者URL和IdP元数据XML之类的说明,这些要求在Spring Security配置中将进一步需要:

Spring

4. Spring Boot设置

除了通常的Maven依赖项(例如spring-boot-starter-webspring-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();

 }

 }

 }

另外,我们应该SecurityConfigCustomSAMLAuthenticationProvider配置为Bean:

@Bean

 public SAMLAuthenticationProvider samlAuthenticationProvider() {

 return new CustomSAMLAuthenticationProvider();

 }

5.8 SecurityConfig

最后,我们将使用已经讨论过的samlEntryPointsamlFilter配置基本的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 评论

发表评论

您的电子邮件地址不会被公开。 必填的字段已做标记 *