Spring Security的SAML指南

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 /進行訪問

Spring
Login鏈接時,應打開“ Okta登錄”頁面:

Spring

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>

成功登錄後,我們應該看到主頁:

Spring

7.結論

在本教程中,我們討論了Spring Security SAML與Okta的集成。

首先,我們使用SAML 2.0 Web集成設置了Okta開發人員帳戶。然後,我們創建了一個具有必需的Maven依賴項的Spring Boot項目。

接下來,我們完成了Spring Security SAML的samlEntryPointsamlFilter ,元數據處理和SAML處理器

最後,我們創建了一個控制器以及一些頁面(如indexhome來測試我們與Okta的SAML集成。