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.jks5.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 评论