SAML单点登录-spring-security-saml客户端SP

article/2025/9/14 9:47:51

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


http://chatgpt.dhexx.cn/article/OOlFG3xL.shtml

相关文章

SAML入门

SAML (Security Assertion Markup Language)入门 提到SAML (Security Assertion Markup Language), 很多人都会联想到单点登录SSO。那么Saml到底是什么&#xff0c;它跟sso到底有什么联系&#xff1f;这里给大家分享一下我在读完了saml差不多全部规范之后的一些心得。希望给sa…

SAML

SAML SAML&#xff08;Security Assertion Markup Language&#xff09;是一个基于XML的开源标准数据格式&#xff0c;它在当事方之间交换身份验证和授权数据&#xff0c;尤其是在身份提供者和服务提供者之间交换。SAML2.0可以实现基于网络跨域的单点登录&#xff08;SSO&…

基于SAML的单点登录介绍

一、背景知识&#xff1a; SAML即安全断言标记语言&#xff0c;英文全称是Security Assertion Markup Language。它是一个基于XML的标准&#xff0c;用于在不同的安全域(security domain)之间交换认证和授权数据。在SAML标准定义了身份提供者(identity provider)和服务提供者(s…

走进SAML——基础篇

SAML的全称是Security Assertion Markup Language。提到SAML&#xff0c;我们主要想到的是其在各种单点登录场景中大行其道。单点登录我们通常叫做SSO&#xff0c;那么SAML到底是如何实现SSO的呢&#xff1f;在这个系列的文章中&#xff0c;我将为大家阐释清楚。不过&#xff0…

深入浅出SAML协议

SAML概述 SAML&#xff08;Security Assertion Markup Language 安全断言标记语言&#xff09;是一个基于XML的开源标准数据格式&#xff0c;为在安全域间交换身份认证和授权数据&#xff0c;尤其是在IDP&#xff08;Identity Provider身份提供方&#xff09;和SP&#xff08;…

SAML2.0 笔记(一)

文章目录 一、前言二、初识概念1、SP & IDP 的概念2、认识元数据2.1 IDP MetaData2.1.1 SingleLogoutService2.1.2 SingleSignOnService 2.2 SP MetaData2.2.1 SingleLogoutService2.2.2 AssertionConsumerService 2.3 通用节点2.3.1 EntityId2.3.2 KeyDescriptor2.3.3 Nam…

【学习笔记】白盒及黑盒测试方法简介

目录 测试用例什么是测试用例测试用例的要素 白盒测试白盒测试的基本介绍白盒测试用例设计方法一、 逻辑覆盖法1.语句覆盖2. 判定覆盖3.条件覆盖4.判定-条件覆盖5.条件组合覆盖6.路径覆盖 二、基本路径测试法总结 黑盒测试分类功能测试性能测试 测试设计方法1.等价类法2.边界值…

白盒测试方法的简单理解(通俗易懂)

白盒测试主要使用逻辑覆盖测试方法&#xff0c;包括语句覆盖、判定覆盖、条件覆盖、判定-条件覆盖、条件组合覆盖、路径覆盖等。 假设逻辑判断流程图如下图所示&#xff0c;我们简单来说说每种白盒测试方法是如何来进行的。 一、语句覆盖 语句覆盖的定义是&#xff1a;程序中…

详解软件测试中白盒测试基本概念及四种白盒测试方法以及六种逻辑覆盖法(语句覆盖、判定覆盖、条件覆盖、判定条件覆盖、条件组合覆盖、路径覆盖)

在这篇文章中&#xff0c;我们将讲解白盒测试的基本概念&#xff0c;以及四大常用的白盒测试方法。 一、白盒测试基本概念 1、白盒测试的定义 白盒测试又称为结构测试或逻辑驱动测试&#xff0c;它是把测试对象看成一个透明的盒子&#xff0c;它允许测试人员利用程序内部的逻…

【软件测试】软件测试方法之黑盒测试方法和白盒测试

白盒测试方法 一、概念 白盒测试也称结构测试或逻辑驱动测试&#xff0c;是针对被测单元内部是如何进行工作的测试。它根据程序的控制结构设计测试用例&#xff0c;主要用于软件或程序验证。它可以形象得用下图表示&#xff1a; 二、白盒测试方法应该遵循的原则 保证一个模…

白盒测试及用例详解

目录 第一部分&#xff1a;概念理解 第二部分&#xff1a;上例题 第三部分&#xff1a;例题解答 附&#xff1a;纸质版解答过程 参考链接 第一部分&#xff1a;概念理解 在白盒测试中&#xff0c;逻辑覆盖测试是使用较多的方法。按照其对测试的有效程度&#xff0c;又将其…

白盒测试内容

白盒测试方法根据模块内部结构&#xff0c;基于程序内部逻辑结构&#xff0c;针对程序语句、路径、变量状态等来进行测试。 单元测试主要采用白盒测试方法&#xff0c;辅以黑盒测试方法。白盒测试方法应用于代码评审、单元程序之中&#xff0c;而黑盒测试方法则应用于模块、组件…

白盒测试

一、逻辑覆盖 逻辑覆盖法是最常用的白盒测试方法&#xff0c;它包括以下5种方法&#xff1a; ● 语句覆盖 ● 判定覆盖 ● 条件覆盖 ● 判定-条件覆盖 ● 条件组合覆盖 1.语句覆盖 语句覆盖(Statement Coverage)又称行覆盖、段覆盖、基本块覆盖&#xff0c;它是最常见的覆盖方式…

软件测试——白盒测试

目录 1.什么是白盒测试 1.1 白盒测试优缺点 2.白盒测试方法 2.1 静态 2.2 动态 2.2.1 语句覆盖 2.2.2 判断覆盖 2.2.3 条件覆盖 2.2.4 判定条件覆盖 2.2.5 条件组合覆盖 2.2.6 路径覆盖 2.2.7 基本路径测试法(最常使用) 1.什么是白盒测试 白盒测试也称结构测试&…

白盒测试中的几种覆盖方法

​ ​白盒测试用例设计的一个很重要的评估标准就是对代码的覆盖度。一说到覆盖&#xff0c;大家都感觉非常熟悉&#xff0c;但是常见的覆盖都有哪些&#xff1f;各自有什么优缺点&#xff1f;在白盒测试的用例设计中我们应该如何自如地运用呢&#xff1f;今天小编就为大家总…

「软件测试4」一文详解四大典型的白盒测试方法

软件测试——详解白盒测试基本概念&#xff0c;四种白盒测试方法 这是我参与更文挑战的第3天&#xff0c;活动详情查看&#xff1a;更文挑战 在上一篇文章中&#xff0c;我们讲到了黑盒测试。黑盒测试相较于白盒测试来说比较简单&#xff0c;不需要了解程序内部的代码&#x…

白盒测试方法 + 实战

定义 白盒测试又称结构测试,透明盒测试、逻辑驱动测试或基于代码的测试。白盒测试是一种测试用例设计方法&#xff0c;白盒指的是程序的内部结构和运作机制是可见的。    目的   通过检查软件内部的逻辑结构&#xff0c;对软件中的逻辑路径进行覆盖测试&#xff1b;在程序…

白盒测试的方法笔记

白盒测试的方法笔记 一、概述&#xff1a;二、方法2.1 语句覆盖&#xff1a;2.2 判定覆盖2.3 条件覆盖2.4 判定条件覆盖2.5 条件组合覆盖2.6 路径覆盖2.7、逻辑覆盖总结 一、概述&#xff1a; 白盒测试也称结构测试或逻辑驱动测试&#xff0c;是针对被测单元内部是如何进行工作…

白盒测试的概念、目的是什么?及主要方法有哪些?

目录 1 白盒测试的概念 2 白盒测试的主要目的 3 测试覆盖标准 4 白盒测试的主要方法 4.1 逻辑驱动测试 4.1.1 语句覆盖 4.1.2 判定覆盖&#xff08;分支覆盖&#xff09; 4.1.3 条件覆盖 4.1.4 判定/条件覆盖 4.1.5 条件组合覆盖 4.1.6 黑盒法补充测试用例 4.2 路径…

白盒测试方法

一、概述&#xff1a; 白盒测试也称结构测试或逻辑驱动测试&#xff0c;是针对被测单元内部是如何进行工作的测试。它根据程序的控制结构设计测试用例&#xff0c;主要用于软件或程序验证。 白盒测试法检查程序内部逻辑结构&#xff0c;对所有逻辑路径进行测试&#xff0c;是…