SAML单点登录-spring-security-saml客户端SP
使用spring-security-saml搭建SAML协议的客户端,该依赖是spring框架的官方库,配置方便、文档详细。提供了包括单点登录、单点登出、获取sq元数据文件等接口,无需自己实现,参考:spring-security-saml与应用程序的集成
SpringMVC接入
Maven添加spring-security-saml依赖
<dependency><groupId>org.springframework.security.extensions</groupId><artifactId>spring-security-saml2-core</artifactId><version>1.0.10.RELEASE</version>
</dependency>
配置文件中添加客户端相关配置
# 认证中心服务信息 -> IDP元数据URL
sp.idpMetadataUrl=http://192.168.59.117:30030/gc-starter-ac/idp/metadata
# entityId,服务提供商唯一标识
sp.entityId=cas:saml:sp:springboot
# 是否签名断言,则需要在idp上传sp的证书/公钥文件以供解密
sp.wantAssertionSigned=false
# 是否签名元数据
sp.signMetadata=false
# 签名算法
sp.signAlg=http://www.w3.org/2001/04/xmldsig-more#rsa-sha256
# 是否启用服务发现。一个sp可以配置多个idp,启动服务发现允许进入idp选择页面选择idp,如果不启用的话默认使用idp列表的第一个
sp.idpDiscoveryEnable=true
# 服务发现选择页面路由
sp.IdpSelectionPath=/saml/discovery
# idp登录成功后的重定向的页面路由,也就是首页路由
sp.successLoginUrl=/landing
# idp登录失败后的重定向的页面路由
sp.failLoginUrl=/error
# 登出成功后跳转的页面路由
sp.successLogoutUrl=/
# jks文件位置
sp.jks.path=classpath:/saml/samlKeystore.jks
# jks密码
sp.jks.password=nalle123
# 默认密钥
sp.jks.defaultKey=apollo
通过xml文件的方式配置bean,classpath下新建securityContext.xml
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:security="http://www.springframework.org/schema/security"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans-3.1.xsdhttp://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context-3.1.xsd"><!-- 引用配置文件 --><context:property-placeholder location="classpath:application.properties"/><!-- Enable auto-wiring --><context:annotation-config/><!-- Scan for auto-wiring classes in spring saml packages --><context:component-scan base-package="org.springframework.security.saml"/><!-- IDP Metadata configuration - paths to metadata of IDPs in circle of trust is here --><bean id="metadata" class="org.springframework.security.saml.metadata.CachingMetadataManager"><constructor-arg><list><!-- Example of classpath metadata with Extended Metadata --><!-- <bean class="org.springframework.security.saml.metadata.ExtendedMetadataDelegate">--><!-- <constructor-arg>--><!-- <bean class="org.opensaml.saml2.metadata.provider.ResourceBackedMetadataProvider">--><!-- <constructor-arg>--><!-- <bean class="java.util.Timer"/>--><!-- </constructor-arg>--><!-- <constructor-arg>--><!-- <bean class="org.opensaml.util.resource.ClasspathResource">--><!-- <constructor-arg value="/metadata/idp.xml"/>--><!-- </bean>--><!-- </constructor-arg>--><!-- <property name="parserPool" ref="parserPool"/>--><!-- </bean>--><!-- </constructor-arg>--><!-- <constructor-arg>--><!-- <bean class="org.springframework.security.saml.metadata.ExtendedMetadata">--><!-- </bean>--><!-- </constructor-arg>--><!-- </bean>--><!-- Example of HTTP metadata without Extended Metadata --><bean class="org.opensaml.saml2.metadata.provider.HTTPMetadataProvider"><!-- URL containing the metadata --><!--idp元数据的url--><constructor-arg><value type="java.lang.String">${sp.idpMetadataUrl}</value></constructor-arg><!-- Timeout for metadata loading in ms --><constructor-arg><value type="int">5000</value></constructor-arg><property name="parserPool" ref="parserPool"/></bean><!-- Example of file system metadata without Extended Metadata --><!--<bean class="org.opensaml.saml2.metadata.provider.FilesystemMetadataProvider"><constructor-arg><value type="java.io.File">/usr/local/metadata/idp.xml</value></constructor-arg><property name="parserPool" ref="parserPool"/></bean>--></list></constructor-arg><!-- OPTIONAL used when one of the metadata files contains information about this service provider --><!-- <property name="hostedSPName" value=""/> --><!-- OPTIONAL property: can tell the system which IDP should be used for authenticating user by default. --><!-- <property name="defaultIDP" value="http://localhost:8080/opensso"/> --></bean><!-- Filter automatically generates default SP metadata --><!--客户端元数据生成的相关配置--><bean id="metadataGeneratorFilter" class="org.springframework.security.saml.metadata.MetadataGeneratorFilter"><constructor-arg><bean class="org.springframework.security.saml.metadata.MetadataGenerator"><!--客户端唯一标识--><property name="entityId" value="${sp.entityId}" /><!--是否签名断言--><property name="wantAssertionSigned" value="${sp.wantAssertionSigned}" /><property name="extendedMetadata"><bean class="org.springframework.security.saml.metadata.ExtendedMetadata"><!--是否签名元数据--><property name="signMetadata" value="${sp.signMetadata}"/><!--签名算法--><property name="signingAlgorithm" value="${sp.signAlg}"/><!--是否启用服务发现--><property name="idpDiscoveryEnabled" value="${sp.idpDiscoveryEnable}"/></bean></property></bean></constructor-arg></bean><!-- IDP Discovery Service --><!--当启用idp发现的时候,选择idp的页面路径--><bean id="samlIDPDiscovery" class="org.springframework.security.saml.SAMLDiscovery"><property name="idpSelectionPath" value="${sp.IdpSelectionPath}"/></bean><!-- Handler deciding where to redirect user after successful login --><!-- 登录成功后跳转的页面,即客户端首页地址 --><bean id="successRedirectHandler"class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler"><property name="defaultTargetUrl" value="${sp.successLoginUrl}"/></bean><!-- Handler deciding where to redirect user after failed login --><!-- 登录失败后跳转的页面 --><bean id="failureRedirectHandler"class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler"><property name="useForward" value="true"/><property name="defaultFailureUrl" value="${sp.failLoginUrl}"/></bean><!-- Handler for successful logout --><!-- 登出成功后跳转的页面,一般会跳转到登录页面或是应用非登录状态的首页 --><bean id="successLogoutHandler" class="org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler"><property name="defaultTargetUrl" value="${sp.successLogoutUrl}"/></bean><!-- Central storage of cryptographic keys --><!-- jks密钥库设置,密钥库中可包含多个公钥私钥对--><bean id="keyManager" class="org.springframework.security.saml.key.JKSKeyManager"><!--jks文件位置--><constructor-arg value="${sp.jks.path}"/><!--密钥库密码--><constructor-arg type="java.lang.String" value="${sp.jks.password}"/><!--密钥库中可用的密钥集合,key为别名,value为密码--><constructor-arg><map><entry key="${sp.jks.defaultKey}" value="${sp.jks.password}"/></map></constructor-arg><!--客户端默认使用的密钥别名--><constructor-arg type="java.lang.String" value="${sp.jks.defaultKey}"/></bean><!-- Unsecured pages --><!-- 设置springmvc 静态资源放行,如果有图片、页面或者公钥文件给予放行 --><security:http security="none" pattern="/favicon.ico"/><security:http security="none" pattern="/images/**"/><security:http security="none" pattern="/css/**"/><security:http security="none" pattern="/logout.jsp"/><security:http security="none" pattern="/key/**"/><!-- Security for the administration UI --><security:http pattern="/saml/web/**" use-expressions="false"><security:access-denied-handler error-page="/saml/web/metadata/login"/><security:form-login login-processing-url="/saml/web/login" login-page="/saml/web/metadata/login" default-target-url="/saml/web/metadata"/><security:intercept-url pattern="/saml/web/metadata/login" access="IS_AUTHENTICATED_ANONYMOUSLY"/><security:intercept-url pattern="/saml/web/**" access="ROLE_ADMIN"/><security:custom-filter before="FIRST" ref="metadataGeneratorFilter"/></security:http><!-- Secured pages with SAML as entry point --><security:http entry-point-ref="samlEntryPoint" use-expressions="false"><security:intercept-url pattern="/**" access="IS_AUTHENTICATED_FULLY"/><security:custom-filter before="FIRST" ref="metadataGeneratorFilter"/><security:custom-filter after="BASIC_AUTH_FILTER" ref="samlFilter"/></security:http><!-- Filters for processing of SAML messages --><!-- 单点登录相关端点 --><bean id="samlFilter" class="org.springframework.security.web.FilterChainProxy"><security:filter-chain-map request-matcher="ant"><security:filter-chain pattern="/saml/login/**" filters="samlEntryPoint"/><security:filter-chain pattern="/saml/logout/**" filters="samlLogoutFilter"/><security:filter-chain pattern="/saml/metadata/**" filters="metadataDisplayFilter"/><security:filter-chain pattern="/saml/SSO/**" filters="samlWebSSOProcessingFilter"/><security:filter-chain pattern="/saml/SSOHoK/**" filters="samlWebSSOHoKProcessingFilter"/><security:filter-chain pattern="/saml/SingleLogout/**" filters="samlLogoutProcessingFilter"/><security:filter-chain pattern="/saml/discovery/**" filters="samlIDPDiscovery"/></security:filter-chain-map></bean><!--Use the following for interpreting RelayState coming from unsolicited response as redirect URL:<bean id="successRedirectHandler" class="org.springframework.security.saml.SAMLRelayStateSuccessHandler"><property name="defaultTargetUrl" value="/" /></bean>--><security:authentication-manager alias="authenticationManager"><!-- Register authentication manager for SAML provider --><security:authentication-provider ref="samlAuthenticationProvider"/><!-- Register authentication manager for administration UI --><security:authentication-provider><security:user-service id="adminInterfaceService"><security:user name="admin" password="admin" authorities="ROLE_ADMIN"/></security:user-service></security:authentication-provider></security:authentication-manager><!-- Logger for SAML messages and events --><bean id="samlLogger" class="org.springframework.security.saml.log.SAMLDefaultLogger"><!-- Enable these to see the actual SAML Messages in logs --><!-- <property name="logAllMessages" value="true"/> --><!-- <property name="logErrors" value="true"/> --><!-- <property name="logMessagesOnException" value="true"/> --></bean><!-- Entry point to initialize authentication, default values taken from properties file --><bean id="samlEntryPoint" class="org.springframework.security.saml.SAMLEntryPoint"><property name="defaultProfileOptions"><bean class="org.springframework.security.saml.websso.WebSSOProfileOptions"><property name="includeScoping" value="false"/></bean></property></bean><!-- The filter is waiting for connections on URL suffixed with filterSuffix and presents SP metadata there --><bean id="metadataDisplayFilter" class="org.springframework.security.saml.metadata.MetadataDisplayFilter"/><!-- Configure HTTP Client to accept certificates from the keystore for HTTPS verification --><!--<bean class="org.springframework.security.saml.trust.httpclient.TLSProtocolConfigurer"><property name="sslHostnameVerification" value="default"/></bean>--><!-- SAML Authentication Provider responsible for validating of received SAML messages --><bean id="samlAuthenticationProvider" class="org.springframework.security.saml.SAMLAuthenticationProvider"><!-- OPTIONAL property: can be used to store/load user data after login --><!--<property name="userDetails" ref="bean" />--></bean><!-- Provider of default SAML Context --><bean id="contextProvider" class="org.springframework.security.saml.context.SAMLContextProviderImpl"/><!-- Processing filter for WebSSO profile messages --><bean id="samlWebSSOProcessingFilter" class="org.springframework.security.saml.SAMLProcessingFilter"><property name="authenticationManager" ref="authenticationManager"/><property name="authenticationSuccessHandler" ref="successRedirectHandler"/><property name="authenticationFailureHandler" ref="failureRedirectHandler"/></bean><!-- Processing filter for WebSSO Holder-of-Key profile --><bean id="samlWebSSOHoKProcessingFilter" class="org.springframework.security.saml.SAMLWebSSOHoKProcessingFilter"><property name="authenticationManager" ref="authenticationManager"/><property name="authenticationSuccessHandler" ref="successRedirectHandler"/><property name="authenticationFailureHandler" ref="failureRedirectHandler"/></bean><!-- Logout handler terminating local session --><bean id="logoutHandler"class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler"><property name="invalidateHttpSession" value="false"/></bean><!-- Override default logout processing filter with the one processing SAML messages --><bean id="samlLogoutFilter" class="org.springframework.security.saml.SAMLLogoutFilter"><constructor-arg index="0" ref="successLogoutHandler"/><constructor-arg index="1" ref="logoutHandler"/><constructor-arg index="2" ref="logoutHandler"/></bean><!-- Filter processing incoming logout messages --><!-- First argument determines URL user will be redirected to after successful global logout --><bean id="samlLogoutProcessingFilter" class="org.springframework.security.saml.SAMLLogoutProcessingFilter"><constructor-arg index="0" ref="successLogoutHandler"/><constructor-arg index="1" ref="logoutHandler"/></bean><!-- Class loading incoming SAML messages from httpRequest stream --><bean id="processor" class="org.springframework.security.saml.processor.SAMLProcessorImpl"><constructor-arg><list><ref bean="redirectBinding"/><ref bean="postBinding"/><ref bean="artifactBinding"/><ref bean="soapBinding"/><ref bean="paosBinding"/></list></constructor-arg></bean><!-- SAML 2.0 WebSSO Assertion Consumer --><bean id="webSSOprofileConsumer" class="org.springframework.security.saml.websso.WebSSOProfileConsumerImpl"/><!-- SAML 2.0 Holder-of-Key WebSSO Assertion Consumer --><bean id="hokWebSSOprofileConsumer" class="org.springframework.security.saml.websso.WebSSOProfileConsumerHoKImpl"/><!-- SAML 2.0 Web SSO profile --><bean id="webSSOprofile" class="org.springframework.security.saml.websso.WebSSOProfileImpl"/><!-- SAML 2.0 Holder-of-Key Web SSO profile --><bean id="hokWebSSOProfile" class="org.springframework.security.saml.websso.WebSSOProfileConsumerHoKImpl"/><!-- SAML 2.0 ECP profile --><bean id="ecpprofile" class="org.springframework.security.saml.websso.WebSSOProfileECPImpl"/><!-- SAML 2.0 Logout Profile --><bean id="logoutprofile" class="org.springframework.security.saml.websso.SingleLogoutProfileImpl"/><!-- Bindings, encoders and decoders used for creating and parsing messages --><bean id="postBinding" class="org.springframework.security.saml.processor.HTTPPostBinding"><constructor-arg ref="parserPool"/><constructor-arg ref="velocityEngine"/></bean><bean id="redirectBinding" class="org.springframework.security.saml.processor.HTTPRedirectDeflateBinding"><constructor-arg ref="parserPool"/></bean><bean id="artifactBinding" class="org.springframework.security.saml.processor.HTTPArtifactBinding"><constructor-arg ref="parserPool"/><constructor-arg ref="velocityEngine"/><constructor-arg><bean class="org.springframework.security.saml.websso.ArtifactResolutionProfileImpl"><constructor-arg><bean class="org.apache.commons.httpclient.HttpClient"><constructor-arg><bean class="org.apache.commons.httpclient.MultiThreadedHttpConnectionManager"/></constructor-arg></bean></constructor-arg><property name="processor"><bean class="org.springframework.security.saml.processor.SAMLProcessorImpl"><constructor-arg ref="soapBinding"/></bean></property></bean></constructor-arg></bean><bean id="soapBinding" class="org.springframework.security.saml.processor.HTTPSOAP11Binding"><constructor-arg ref="parserPool"/></bean><bean id="paosBinding" class="org.springframework.security.saml.processor.HTTPPAOS11Binding"><constructor-arg ref="parserPool"/></bean><!-- Initialization of OpenSAML library--><bean class="org.springframework.security.saml.SAMLBootstrap"/><!-- Initialization of the velocity engine --><bean id="velocityEngine" class="org.springframework.security.saml.util.VelocityFactory" factory-method="getEngine"/><!-- XML parser pool needed for OpenSAML parsingWARNING: If customizing a ParserPool implementation See https://shibboleth.net/community/advisories/secadv_20131213.txtSpecifically the following should be explicitly set to avoid exploits:1) set pool property 'expandEntityReferences' to 'false'2) set feature 'javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING' to true3) set feature 'https://apache.org/xml/features/disallow-doctype-decl' to true. This is a Xerces-specific feature,including derivatives such as the internal JAXP implementations supplied with the Oracle and OpenJDK JREs. Forother JAXP implementations, consult the documentation for the implementation for guidance on how to achieve asimilar configuration.--><bean id="parserPool" class="org.opensaml.xml.parse.StaticBasicParserPool" init-method="initialize"/><bean id="parserPoolHolder" class="org.springframework.security.saml.parser.ParserPoolHolder"/></beans>
web.xml文件中添加该bean
<context-param><param-name>contextConfigLocation</param-name><param-value>/WEB-INF/securityContext.xml</param-value>
</context-param>
SpringBoot接入
Maven添加spring-security-saml依赖
<dependency><groupId>org.springframework.security.extensions</groupId><artifactId>spring-security-saml2-core</artifactId><version>1.0.10.RELEASE</version>
</dependency>
配置文件添加客户端相关配置,其实与SpringMVC工程一致,这里展示的是yml文件方式
sp:# 认证中心服务信息 -> IDP元数据URLidpMetadataUrl: http://localhost:8080/gc-starter-ac/idp/metadata# entityId,服务提供商唯一标识entityId: cas:saml:sp:springboot# 是否签名断言,则需要在idp上传sp的证书/公钥文件以供解密wantAssertionSigned: false# 是否签名元数据signMetadata: false# 签名算法signAlg: http://www.w3.org/2001/04/xmldsig-more#rsa-sha256# 是否启用服务发现。一个sp可以配置多个idp,启动服务发现允许进入idp选择页面选择idp,如果不启用的话默认使用idp列表的第一个idpDiscoveryEnable: true# 服务发现选择页面路由IdpSelectionPath: /saml/discovery# idp登录成功后的重定向的页面路由,也就是首页路由successLoginUrl: /landing# idp登录失败后的重定向的页面路由failLoginUrl: /error# 登出成功后跳转的页面路由successLogoutUrl: /# 密钥库设置jks:# jks文件位置path: classpath:/saml/samlKeystore.jks# jks密码password: nalle123# 默认密钥defaultKey: apollo
创建spring-security-saml的配置Bean,WebSecurityConfig.class
/** Copyright 2021 Vincenzo De Notaris** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License. */package com.vdenotaris.spring.boot.security.saml.web.config;import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Timer;import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.velocity.app.VelocityEngine;
import org.opensaml.saml2.metadata.provider.HTTPMetadataProvider;
import org.opensaml.saml2.metadata.provider.MetadataProvider;
import org.opensaml.saml2.metadata.provider.MetadataProviderException;
import org.opensaml.xml.parse.ParserPool;
import org.opensaml.xml.parse.StaticBasicParserPool;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.saml.SAMLAuthenticationProvider;
import org.springframework.security.saml.SAMLBootstrap;
import org.springframework.security.saml.SAMLDiscovery;
import org.springframework.security.saml.SAMLEntryPoint;
import org.springframework.security.saml.SAMLLogoutFilter;
import org.springframework.security.saml.SAMLLogoutProcessingFilter;
import org.springframework.security.saml.SAMLProcessingFilter;
import org.springframework.security.saml.SAMLWebSSOHoKProcessingFilter;
import org.springframework.security.saml.context.SAMLContextProviderImpl;
import org.springframework.security.saml.key.JKSKeyManager;
import org.springframework.security.saml.key.KeyManager;
import org.springframework.security.saml.log.SAMLDefaultLogger;
import org.springframework.security.saml.metadata.CachingMetadataManager;
import org.springframework.security.saml.metadata.ExtendedMetadata;
import org.springframework.security.saml.metadata.ExtendedMetadataDelegate;
import org.springframework.security.saml.metadata.MetadataDisplayFilter;
import org.springframework.security.saml.metadata.MetadataGenerator;
import org.springframework.security.saml.metadata.MetadataGeneratorFilter;
import org.springframework.security.saml.parser.ParserPoolHolder;
import org.springframework.security.saml.processor.HTTPArtifactBinding;
import org.springframework.security.saml.processor.HTTPPAOS11Binding;
import org.springframework.security.saml.processor.HTTPPostBinding;
import org.springframework.security.saml.processor.HTTPRedirectDeflateBinding;
import org.springframework.security.saml.processor.HTTPSOAP11Binding;
import org.springframework.security.saml.processor.SAMLBinding;
import org.springframework.security.saml.processor.SAMLProcessorImpl;
import org.springframework.security.saml.util.VelocityFactory;
import org.springframework.security.saml.websso.ArtifactResolutionProfile;
import org.springframework.security.saml.websso.ArtifactResolutionProfileImpl;
import org.springframework.security.saml.websso.SingleLogoutProfile;
import org.springframework.security.saml.websso.SingleLogoutProfileImpl;
import org.springframework.security.saml.websso.WebSSOProfile;
import org.springframework.security.saml.websso.WebSSOProfileConsumer;
import org.springframework.security.saml.websso.WebSSOProfileConsumerHoKImpl;
import org.springframework.security.saml.websso.WebSSOProfileConsumerImpl;
import org.springframework.security.saml.websso.WebSSOProfileECPImpl;
import org.springframework.security.saml.websso.WebSSOProfileImpl;
import org.springframework.security.saml.websso.WebSSOProfileOptions;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;import com.vdenotaris.spring.boot.security.saml.web.core.SAMLUserDetailsServiceImpl;@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter implements InitializingBean, DisposableBean {@Autowiredprivate SpConfig spConfig;private Timer backgroundTaskTimer;private MultiThreadedHttpConnectionManager multiThreadedHttpConnectionManager;public void init() {this.backgroundTaskTimer = new Timer(true);this.multiThreadedHttpConnectionManager = new MultiThreadedHttpConnectionManager();}public void shutdown() {this.backgroundTaskTimer.purge();this.backgroundTaskTimer.cancel();this.multiThreadedHttpConnectionManager.shutdown();}@Autowiredprivate SAMLUserDetailsServiceImpl samlUserDetailsServiceImpl;// Initialization of the velocity engine@Beanpublic VelocityEngine velocityEngine() {return VelocityFactory.getEngine();}// XML parser pool needed for OpenSAML parsing@Bean(initMethod = "initialize")public StaticBasicParserPool parserPool() {return new StaticBasicParserPool();}@Bean(name = "parserPoolHolder")public ParserPoolHolder parserPoolHolder() {return new ParserPoolHolder();}// Bindings, encoders and decoders used for creating and parsing messages@Beanpublic HttpClient httpClient() {return new HttpClient(this.multiThreadedHttpConnectionManager);}// SAML Authentication Provider responsible for validating of received SAML// messages@Beanpublic SAMLAuthenticationProvider samlAuthenticationProvider() {SAMLAuthenticationProvider samlAuthenticationProvider = new SAMLAuthenticationProvider();samlAuthenticationProvider.setUserDetails(samlUserDetailsServiceImpl);samlAuthenticationProvider.setForcePrincipalAsString(false);return samlAuthenticationProvider;}// Provider of default SAML Context@Beanpublic SAMLContextProviderImpl contextProvider() {return new SAMLContextProviderImpl();}// Initialization of OpenSAML library@Beanpublic static SAMLBootstrap sAMLBootstrap() {return new SAMLBootstrap();}// Logger for SAML messages and events@Beanpublic SAMLDefaultLogger samlLogger() {return new SAMLDefaultLogger();}// SAML 2.0 WebSSO Assertion Consumer@Beanpublic WebSSOProfileConsumer webSSOprofileConsumer() {return new WebSSOProfileConsumerImpl();}// SAML 2.0 Holder-of-Key WebSSO Assertion Consumer@Beanpublic WebSSOProfileConsumerHoKImpl hokWebSSOprofileConsumer() {return new WebSSOProfileConsumerHoKImpl();}// SAML 2.0 Web SSO profile@Beanpublic WebSSOProfile webSSOprofile() {return new WebSSOProfileImpl();}// SAML 2.0 Holder-of-Key Web SSO profile@Beanpublic WebSSOProfileConsumerHoKImpl hokWebSSOProfile() {return new WebSSOProfileConsumerHoKImpl();}// SAML 2.0 ECP profile@Beanpublic WebSSOProfileECPImpl ecpprofile() {return new WebSSOProfileECPImpl();}@Beanpublic SingleLogoutProfile logoutprofile() {return new SingleLogoutProfileImpl();}// sp密钥库// Central storage of cryptographic keys@Beanpublic KeyManager keyManager() {DefaultResourceLoader loader = new DefaultResourceLoader();Resource storeFile = loader.getResource(spConfig.getJks().getPath());String storePass = spConfig.getJks().getPassword();Map<String, String> passwords = new HashMap<String, String>();passwords.put(spConfig.getJks().getDefaultKey(), spConfig.getJks().getPassword());String defaultKey = spConfig.getJks().getDefaultKey();return new JKSKeyManager(storeFile, storePass, passwords, defaultKey);}@Beanpublic WebSSOProfileOptions defaultWebSSOProfileOptions() {WebSSOProfileOptions webSSOProfileOptions = new WebSSOProfileOptions();webSSOProfileOptions.setIncludeScoping(false);return webSSOProfileOptions;}// Entry point to initialize authentication, default values taken from// properties file@Beanpublic SAMLEntryPoint samlEntryPoint() {SAMLEntryPoint samlEntryPoint = new SAMLEntryPoint();samlEntryPoint.setDefaultProfileOptions(defaultWebSSOProfileOptions());return samlEntryPoint;}// 扩展元数据// Setup advanced info about metadata@Beanpublic ExtendedMetadata extendedMetadata() {ExtendedMetadata extendedMetadata = new ExtendedMetadata();extendedMetadata.setIdpDiscoveryEnabled(spConfig.getIdpDiscoveryEnable());extendedMetadata.setSigningAlgorithm(spConfig.getSignAlg());extendedMetadata.setSignMetadata(spConfig.getSignMetadata());extendedMetadata.setEcpEnabled(true);return extendedMetadata;}// 服务发现页面地址// IDP Discovery Service@Beanpublic SAMLDiscovery samlIDPDiscovery() {SAMLDiscovery idpDiscovery = new SAMLDiscovery();idpDiscovery.setIdpSelectionPath(spConfig.getIdpSelectionPath());return idpDiscovery;}@Bean@Qualifier("idp-ssocircle")public ExtendedMetadataDelegate ssoCircleExtendedMetadataProvider()throws MetadataProviderException {String idpSSOCircleMetadataURL = spConfig.getIdpMetadataUrl();HTTPMetadataProvider httpMetadataProvider = new HTTPMetadataProvider(this.backgroundTaskTimer, httpClient(), idpSSOCircleMetadataURL);httpMetadataProvider.setParserPool(parserPool());ExtendedMetadataDelegate extendedMetadataDelegate = new ExtendedMetadataDelegate(httpMetadataProvider, extendedMetadata());extendedMetadataDelegate.setMetadataTrustCheck(false);extendedMetadataDelegate.setMetadataRequireSignature(false);backgroundTaskTimer.purge();return extendedMetadataDelegate;}// IDP Metadata configuration - paths to metadata of IDPs in circle of trust// is here// Do no forget to call iniitalize method on providers@Bean@Qualifier("metadata")public CachingMetadataManager metadata() throws MetadataProviderException {List<MetadataProvider> providers = new ArrayList<MetadataProvider>();providers.add(ssoCircleExtendedMetadataProvider());return new CachingMetadataManager(providers);}// 元数据生成bean// Filter automatically generates default SP metadata@Beanpublic MetadataGenerator metadataGenerator() {MetadataGenerator metadataGenerator = new MetadataGenerator();metadataGenerator.setEntityId(spConfig.getEntityId());metadataGenerator.setExtendedMetadata(extendedMetadata());metadataGenerator.setIncludeDiscoveryExtension(false);metadataGenerator.setKeyManager(keyManager());metadataGenerator.setWantAssertionSigned(spConfig.getWantAssertionSigned());return metadataGenerator;}// The filter is waiting for connections on URL suffixed with filterSuffix// and presents SP metadata there@Beanpublic MetadataDisplayFilter metadataDisplayFilter() {return new MetadataDisplayFilter();}// 设置登陆成功后的重定向地址,或者说是首页地址// Handler deciding where to redirect user after successful login@Beanpublic SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler() {SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler =new SavedRequestAwareAuthenticationSuccessHandler();successRedirectHandler.setDefaultTargetUrl(spConfig.getSuccessLoginUrl());return successRedirectHandler;}// Handler deciding where to redirect user after failed login@Beanpublic SimpleUrlAuthenticationFailureHandler authenticationFailureHandler() {SimpleUrlAuthenticationFailureHandler failureHandler =new SimpleUrlAuthenticationFailureHandler();failureHandler.setUseForward(true);failureHandler.setDefaultFailureUrl(spConfig.getFailLoginUrl());return failureHandler;}@Beanpublic SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter() throws Exception {SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter = new SAMLWebSSOHoKProcessingFilter();samlWebSSOHoKProcessingFilter.setAuthenticationSuccessHandler(successRedirectHandler());samlWebSSOHoKProcessingFilter.setAuthenticationManager(authenticationManager());samlWebSSOHoKProcessingFilter.setAuthenticationFailureHandler(authenticationFailureHandler());return samlWebSSOHoKProcessingFilter;}// Processing filter for WebSSO profile messages@Beanpublic SAMLProcessingFilter samlWebSSOProcessingFilter() throws Exception {SAMLProcessingFilter samlWebSSOProcessingFilter = new SAMLProcessingFilter();samlWebSSOProcessingFilter.setAuthenticationManager(authenticationManager());samlWebSSOProcessingFilter.setAuthenticationSuccessHandler(successRedirectHandler());samlWebSSOProcessingFilter.setAuthenticationFailureHandler(authenticationFailureHandler());return samlWebSSOProcessingFilter;}@Beanpublic MetadataGeneratorFilter metadataGeneratorFilter() {return new MetadataGeneratorFilter(metadataGenerator());}// Handler for successful logout@Beanpublic SimpleUrlLogoutSuccessHandler successLogoutHandler() {SimpleUrlLogoutSuccessHandler successLogoutHandler = new SimpleUrlLogoutSuccessHandler();successLogoutHandler.setDefaultTargetUrl(spConfig.getSuccessLogoutUrl());return successLogoutHandler;}// Logout handler terminating local session@Beanpublic SecurityContextLogoutHandler logoutHandler() {SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler();logoutHandler.setInvalidateHttpSession(true);logoutHandler.setClearAuthentication(true);return logoutHandler;}// Filter processing incoming logout messages// First argument determines URL user will be redirected to after successful// global logout@Beanpublic SAMLLogoutProcessingFilter samlLogoutProcessingFilter() {return new SAMLLogoutProcessingFilter(successLogoutHandler(),logoutHandler());}// Overrides default logout processing filter with the one processing SAML// messages@Beanpublic SAMLLogoutFilter samlLogoutFilter() {return new SAMLLogoutFilter(successLogoutHandler(),new LogoutHandler[] { logoutHandler() },new LogoutHandler[] { logoutHandler() });}// Bindingsprivate ArtifactResolutionProfile artifactResolutionProfile() {final ArtifactResolutionProfileImpl artifactResolutionProfile = new ArtifactResolutionProfileImpl(httpClient());artifactResolutionProfile.setProcessor(new SAMLProcessorImpl(soapBinding()));return artifactResolutionProfile;}@Beanpublic HTTPArtifactBinding artifactBinding(ParserPool parserPool, VelocityEngine velocityEngine) {return new HTTPArtifactBinding(parserPool, velocityEngine, artifactResolutionProfile());}@Beanpublic HTTPSOAP11Binding soapBinding() {return new HTTPSOAP11Binding(parserPool());}@Beanpublic HTTPPostBinding httpPostBinding() {return new HTTPPostBinding(parserPool(), velocityEngine());}@Beanpublic HTTPRedirectDeflateBinding httpRedirectDeflateBinding() {return new HTTPRedirectDeflateBinding(parserPool());}@Beanpublic HTTPSOAP11Binding httpSOAP11Binding() {return new HTTPSOAP11Binding(parserPool());}@Beanpublic HTTPPAOS11Binding httpPAOS11Binding() {return new HTTPPAOS11Binding(parserPool());}// Processor@Beanpublic SAMLProcessorImpl processor() {Collection<SAMLBinding> bindings = new ArrayList<SAMLBinding>();bindings.add(httpRedirectDeflateBinding());bindings.add(httpPostBinding());bindings.add(artifactBinding(parserPool(), velocityEngine()));bindings.add(httpSOAP11Binding());bindings.add(httpPAOS11Binding());return new SAMLProcessorImpl(bindings);}/*** Define the security filter chain in order to support SSO Auth by using SAML 2.0* * @return Filter chain proxy* @throws Exception*/@Beanpublic FilterChainProxy samlFilter() throws Exception {List<SecurityFilterChain> chains = new ArrayList<SecurityFilterChain>();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/metadata/**"),metadataDisplayFilter()));chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SSO/**"),samlWebSSOProcessingFilter()));chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SSOHoK/**"),samlWebSSOHoKProcessingFilter()));chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/SingleLogout/**"),samlLogoutProcessingFilter()));chains.add(new DefaultSecurityFilterChain(new AntPathRequestMatcher("/saml/discovery/**"),samlIDPDiscovery()));return new FilterChainProxy(chains);}/*** Returns the authentication manager currently used by Spring.* It represents a bean definition with the aim allow wiring from* other classes performing the Inversion of Control (IoC).* * @throws Exception */@Bean@Overridepublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}/*** Defines the web based security configuration.* * @param http It allows configuring web based security for specific http requests.* @throws Exception */@Override protected void configure(HttpSecurity http) throws Exception {http.httpBasic().authenticationEntryPoint(samlEntryPoint()); http.addFilterBefore(metadataGeneratorFilter(), ChannelProcessingFilter.class).addFilterAfter(samlFilter(), BasicAuthenticationFilter.class).addFilterBefore(samlFilter(), CsrfFilter.class);http .authorizeRequests().antMatchers("/").permitAll().antMatchers("/saml/**").permitAll().antMatchers("/css/**").permitAll().antMatchers("/img/**").permitAll().antMatchers("/js/**").permitAll().anyRequest().authenticated();http.logout().disable(); // The logout procedure is already handled by SAML filters.}/*** Sets a custom authentication provider.* * @param auth SecurityBuilder used to create an AuthenticationManager.* @throws Exception */@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.authenticationProvider(samlAuthenticationProvider());}@Overridepublic void afterPropertiesSet() throws Exception {init();}@Overridepublic void destroy() throws Exception {shutdown();}
}
生成密钥库jks文件
SAML客户端在发送SAML请求时需要进行加密和签名,这就需要密钥,上面配置文件中也有需要去配置jks。jks即密钥库(Java Key Store),里面包含多个公钥和私钥,也可以将认证中心的公钥放入其中,进行解密和验签。这里介绍如何使用jdk的keytool工具生成私钥和自签名证书。
生成密钥库,密钥库包含了公钥和私钥
keytool -genkeypair -alias qianxing -keyalg RSA -keystore samlKeystore.jks
生成公钥,IDP解密时需要使用
keytool -alias qianxing -exportcert -keystore samlKeystore.jks -file public.cer
根据jks生成私钥
keytool -v -importkeystore -srckeystore samlKeystore.jks -srcstoretype jks -destkeystore qianxing.pfx -deststoretype pkcs12
openssl pkcs12 -in qianxing.pfx -nocerts -nodes -out private.key
在idp中注册应用
-
在之前的文章中的idp工程中classpath下创建一个services目录
-
新建一个json文件,文件名格式为SAML-XXXXXXXX(唯一标识).json
{"@class": "org.apereo.cas.support.saml.services.SamlRegisteredService",# sp的entityId"serviceId": "com:ustcinfo:qianxing",# 服务名称"name": "SAMLService",# 唯一标识id"id": 10000004,"evaluationOrder": 10,# sp元数据位置"metadataLocation": "http://localhost:8100/spring_security_saml2_sample_war/saml/metadata",# sp公钥位置,如果不需要加密和签名的话可以先不配置"metadataSignatureLocation": "http://localhost:8100/spring_security_saml2_sample_war/key/qianxing.cer",# 这是关于加密和签名的配置,暂时先不说,先都设置为false好测试"signAssertions": false,"signResponses": false,"encryptAssertions": false
}
- 重新部署idp
- 待idp部署成功后,sp客户端打包部署
- 访问sp客户端
sp常用API
获取sp元数据:http://ip:port/cotext-path/saml/metadata