I am integrating Spring Security SAML2 with Azure AD (Microsoft Entra ID) Single Sign-On (SSO) in a Spring application using XML-based security configuration.
Everything seems correctly configured, but after entering my credentials in Azure, my application redirects to /saml/SSO, which returns a 404 error. This is the example I've followed.
None of the /saml/**
endpoints work.
This is my Azure config:
This is my App Federation Metadata URL
This is my Spring config:
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns=";
xmlns:sec=";
xmlns:context=";
xmlns:xsi=";
xsi:schemaLocation="
.xsd
.0.xsd"
>
<!-- Scan for auto-wiring classes in spring saml packages -->
<!-- <context:component-scan base-package=".springframework.security.saml"/>-->
<sec:http auto-config="false" use-expressions="true" entry-point-ref="samlEntryPoint">
<!-- <sec:intercept-url pattern="/login.xhtml**" filters="none"/>-->
<!-- Add a security rule to allow SAML endpoints -->
<sec:intercept-url pattern="/saml/**" access="permitAll()"/>
<sec:intercept-url pattern="/**"
access="isAuthenticated()"/>
<sec:access-denied-handler error-page="/accessDenied.xhtml"/>
<!-- <sec:form-login login-processing-url="/j_spring_security_check" -->
<!-- login-page="/login.xhtml"-->
<!-- always-use-default-target="false"/>-->
<sec:logout success-handler-ref="successLogoutHandler"/>
<sec:custom-filter before="FIRST"
ref="metadataGeneratorFilter"/>
<sec:custom-filter after="BASIC_AUTH_FILTER"
ref="samlFilter"/>
<!-- <sec:session-management session-authentication-strategy-ref="sas" />-->
<!-- <sec:custom-filter position="CONCURRENT_SESSION_FILTER" ref="concurrencyFilter" />-->
<sec:custom-filter ref="sensitiveDataAuditFilter" after="LAST"/>
</sec:http>
<bean id="samlFilter" class=".springframework.security.web.FilterChainProxy">
<sec:filter-chain-map path-type="ant">
<sec:filter-chain pattern="/saml/login/**"
filters="samlEntryPoint"/>
<sec:filter-chain pattern="/saml/logout/**"
filters="samlLogoutFilter"/>
<sec:filter-chain pattern="/saml/metadata/**"
filters="metadataDisplayFilter"/>
<sec:filter-chain pattern="/saml/SSO/**"
filters="samlWebSSOProcessingFilter"/>
<sec:filter-chain pattern="/saml/SSOHoK/**"
filters="samlWebSSOHoKProcessingFilter"/>
<!-- <sec:filter-chain pattern="/saml/SingleLogout/**"
filters="samlSingleLogout" /> -->
</sec:filter-chain-map>
</bean>
<bean id="samlEntryPoint"
class=".springframework.security.saml.SAMLEntryPoint">
<property name="defaultProfileOptions">
<bean
class=".springframework.security.saml.websso.WebSSOProfileOptions">
<property name="includeScoping" value="false"/>
</bean>
</property>
</bean>
<bean id="metadataDisplayFilter" class=".springframework.security.saml.metadata.MetadataDisplayFilter"/>
<bean id="metadata"
class=".springframework.security.saml.metadata.CachingMetadataManager">
<constructor-arg>
<list>
<bean class=".springframework.security.saml.metadata.ExtendedMetadataDelegate">
<constructor-arg>
<bean class=".opensaml.saml2.metadata.provider.FilesystemMetadataProvider">
<constructor-arg>
<bean class="java.io.File">
<constructor-arg value=".../src/main/resources/saml/sso.xml"/>
</bean>
</constructor-arg>
<property name="parserPool" ref="parsePool"/>
</bean>
</constructor-arg>
<constructor-arg>
<bean class=".springframework.security.saml.metadata.ExtendedMetadata">
</bean>
</constructor-arg>
<property name="metadataTrustCheck" value="false"/>
</bean>
</list>
</constructor-arg>
<property name="defaultIDP"
value="/"/>
</bean>
<bean id="metadataGeneratorFilter"
class=".springframework.security.saml.metadata.MetadataGeneratorFilter">
<constructor-arg>
<bean
class=".springframework.security.saml.metadata.MetadataGenerator">
<property name="extendedMetadata">
<bean
class=".springframework.security.saml.metadata.ExtendedMetadata">
<property name="idpDiscoveryEnabled" value="false"/>
<property name="signMetadata" value="false"/>
</bean>
</property>
<property name="entityId" value="api://9d212449-b84a-4cbb-b0e1-7f500c925960"/>
<!-- <property name="requestSigned" value="false"></property>
<property name="wantAssertionSigned" value="false"></property>
<property name="nameID" value="UNSPECIFIED"></property> -->
<property name="includeDiscoveryExtension" value="false"/>
<property name="keyManager" ref="keyManager"/>
<property name="entityBaseURL" value="https://localhost:8443"/>
</bean>
</constructor-arg>
</bean>
<bean id="successRedirectHandler"
class="com.xxx.admin.security.AuthenticationSuccessHandler">
<!-- <qualifier value="saml"></qualifier>-->
<!-- <property name="defaultTargetUrl" value="/home.xhtml"></property>-->
</bean>
<bean id="failureRedirectHandler" class="com.xxx.admin.security.AuthenticationFailureHandler"/>
<sec:authentication-manager
alias="authenticationManager">
<sec:authentication-provider
ref="samlAuthenticationProvider"/>
</sec:authentication-manager>
<bean id="samlAuthenticationProvider"
class=".springframework.security.saml.SAMLAuthenticationProvider">
</bean>
<bean id="contextProvider" class=".springframework.security.saml.context.SAMLContextProviderImpl"/>
<!-- todo burda direk line 814 teki gibi ref verebilir miyiz bak
authentication-success-handler-ref="authenticationSuccessHandler"
authentication-failure-handler-ref="authenticationFailureHandler"
-->
<bean id="samlWebSSOProcessingFilter"
class=".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=".springframework.security.saml.SAMLWebSSOHoKProcessingFilter">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="authenticationSuccessHandler" ref="successRedirectHandler"/>
<property name="authenticationFailureHandler" ref="failureRedirectHandler"/>
</bean>
<bean id="successLogoutHandler" class="com.xxx.admin.security.LogoutHandler"/>
<bean id="logoutHandler"
class=".springframework.security.web.authentication.logout.SecurityContextLogoutHandler">
<property name="invalidateHttpSession" value="true"></property>
</bean>
<bean id="samlLogoutFilter"
class=".springframework.security.saml.SAMLLogoutFilter">
<constructor-arg index="0" ref="successLogoutHandler"></constructor-arg>
<constructor-arg index="1" ref="logoutHandler"></constructor-arg>
<constructor-arg index="2" ref="logoutHandler"></constructor-arg>
</bean>
<bean id="samlLogoutProcessingFilter"
class=".springframework.security.saml.SAMLLogoutProcessingFilter">
<constructor-arg index="0" ref="successLogoutHandler"></constructor-arg>
<constructor-arg index="1" ref="logoutHandler"></constructor-arg>
</bean>
<bean id="processor"
class=".springframework.security.saml.processor.SAMLProcessorImpl">
<constructor-arg>
<list>
<ref bean="redirectBinding"/>
<ref bean="postBinding"/>
</list>
</constructor-arg>
</bean>
<bean id="soapBinding" class=".springframework.security.saml.processor.HTTPSOAP11Binding">
<constructor-arg ref="parsePool"/>
</bean>
<bean id="paosBinding" class=".springframework.security.saml.processor.HTTPPAOS11Binding">
<constructor-arg ref="parsePool"/>
</bean>
<bean id="webSSOprofileConsumer"
class=".springframework.security.saml.websso.WebSSOProfileConsumerImpl">
</bean>
<bean id="webSSOprofile"
class=".springframework.security.saml.websso.WebSSOProfileImpl">
</bean>
<bean id="hokWebSSOProfileConsumer"
class=".springframework.security.saml.websso.WebSSOProfileConsumerHoKImpl">
<qualifier value="hokWebSSOprofileConsumer"/>
</bean>
<bean id="hokWebSSOProfile"
class=".springframework.security.saml.websso.WebSSOProfileConsumerHoKImpl">
</bean>
<bean id="ecpProfile"
class=".springframework.security.saml.websso.WebSSOProfileECPImpl">
</bean>
<bean id="logoutProfile"
class=".springframework.security.saml.websso.SingleLogoutProfileImpl">
</bean>
<bean id="postBinding"
class=".springframework.security.saml.processor.HTTPPostBinding">
<constructor-arg ref="parsePool"></constructor-arg>
<constructor-arg ref="velocityEngine"></constructor-arg>
</bean>
<bean id="redirectBinding"
class=".springframework.security.saml.processor.HTTPRedirectDeflateBinding">
<constructor-arg ref="parsePool"></constructor-arg>
</bean>
<bean id="samlBootstrap" class=".springframework.security.saml.SAMLBootstrap">
</bean>
<bean id="velocityEngine"
class=".springframework.security.saml.util.VelocityFactory"
factory-method="getEngine">
</bean>
<bean id="parsePool"
class=".opensaml.xml.parse.StaticBasicParserPool"
init-method="initialize" />
<bean id="parsePoolHolder"
class=".springframework.security.saml.parser.ParserPoolHolder">
</bean>
<bean id="samlLogger"
class=".springframework.security.saml.log.SAMLDefaultLogger">
<property name="logErrors" value="true"></property>
</bean>
<bean id="keyManager" class=".springframework.security.saml.key.JKSKeyManager">
<constructor-arg value="classpath:/saml/saml-keystore.jks"/>
<constructor-arg type="java.lang.String" value="azuresamlsso"/>
<constructor-arg>
<map>
<entry key="azuresaml" value="azuresamlsso">
</entry>
</map>
</constructor-arg>
<constructor-arg type="java.lang.String" value="azuresaml"/>
</bean>
<!-- <sec:ldap-server url="${ldap.url}" root="${ldap.basedn}" manager-dn="${ldap.user}" manager-password="${ldap.password}" />-->
<!-- <sec:authentication-manager>-->
<!-- <sec:ldap-authentication-provider user-search-filter="(sAMAccountName={0})" user-context-mapper-ref="ldapContextMapper" />-->
<!-- </sec:authentication-manager>-->
<!-- <bean id="sas" class=".springframework.security.web.authentication.session.ConcurrentSessionControlStrategy">-->
<!-- <constructor-arg name="sessionRegistry" ref="sessionRegistry" />-->
<!-- <property name="maximumSessions" value="${concurrent.users}" />-->
<!-- </bean>-->
<!-- <bean id="concurrencyFilter" class=".springframework.security.web.session.ConcurrentSessionFilter">-->
<!-- <property name="sessionRegistry" ref="sessionRegistry" />-->
<!-- <property name="expiredUrl" value="/logout.xhtml" />-->
<!-- </bean>-->
<bean id="sessionRegistry" class=".springframework.security.core.session.SessionRegistryImpl"/>
</beans>
Thanks in advance!
I am integrating Spring Security SAML2 with Azure AD (Microsoft Entra ID) Single Sign-On (SSO) in a Spring application using XML-based security configuration.
Everything seems correctly configured, but after entering my credentials in Azure, my application redirects to /saml/SSO, which returns a 404 error. This is the example I've followed.
None of the /saml/**
endpoints work.
This is my Azure config:
This is my App Federation Metadata URL
This is my Spring config:
<?xml version="1.0" encoding="UTF-8"?>
<beans
xmlns="http://www.springframework./schema/beans"
xmlns:sec="http://www.springframework./schema/security"
xmlns:context="http://www.springframework./schema/context"
xmlns:xsi="http://www.w3./2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework./schema/beans http://www.springframework./schema/beans/spring-beans.xsd
http://www.springframework./schema/security http://www.springframework./schema/security/spring-security-3.0.xsd"
>
<!-- Scan for auto-wiring classes in spring saml packages -->
<!-- <context:component-scan base-package=".springframework.security.saml"/>-->
<sec:http auto-config="false" use-expressions="true" entry-point-ref="samlEntryPoint">
<!-- <sec:intercept-url pattern="/login.xhtml**" filters="none"/>-->
<!-- Add a security rule to allow SAML endpoints -->
<sec:intercept-url pattern="/saml/**" access="permitAll()"/>
<sec:intercept-url pattern="/**"
access="isAuthenticated()"/>
<sec:access-denied-handler error-page="/accessDenied.xhtml"/>
<!-- <sec:form-login login-processing-url="/j_spring_security_check" -->
<!-- login-page="/login.xhtml"-->
<!-- always-use-default-target="false"/>-->
<sec:logout success-handler-ref="successLogoutHandler"/>
<sec:custom-filter before="FIRST"
ref="metadataGeneratorFilter"/>
<sec:custom-filter after="BASIC_AUTH_FILTER"
ref="samlFilter"/>
<!-- <sec:session-management session-authentication-strategy-ref="sas" />-->
<!-- <sec:custom-filter position="CONCURRENT_SESSION_FILTER" ref="concurrencyFilter" />-->
<sec:custom-filter ref="sensitiveDataAuditFilter" after="LAST"/>
</sec:http>
<bean id="samlFilter" class=".springframework.security.web.FilterChainProxy">
<sec:filter-chain-map path-type="ant">
<sec:filter-chain pattern="/saml/login/**"
filters="samlEntryPoint"/>
<sec:filter-chain pattern="/saml/logout/**"
filters="samlLogoutFilter"/>
<sec:filter-chain pattern="/saml/metadata/**"
filters="metadataDisplayFilter"/>
<sec:filter-chain pattern="/saml/SSO/**"
filters="samlWebSSOProcessingFilter"/>
<sec:filter-chain pattern="/saml/SSOHoK/**"
filters="samlWebSSOHoKProcessingFilter"/>
<!-- <sec:filter-chain pattern="/saml/SingleLogout/**"
filters="samlSingleLogout" /> -->
</sec:filter-chain-map>
</bean>
<bean id="samlEntryPoint"
class=".springframework.security.saml.SAMLEntryPoint">
<property name="defaultProfileOptions">
<bean
class=".springframework.security.saml.websso.WebSSOProfileOptions">
<property name="includeScoping" value="false"/>
</bean>
</property>
</bean>
<bean id="metadataDisplayFilter" class=".springframework.security.saml.metadata.MetadataDisplayFilter"/>
<bean id="metadata"
class=".springframework.security.saml.metadata.CachingMetadataManager">
<constructor-arg>
<list>
<bean class=".springframework.security.saml.metadata.ExtendedMetadataDelegate">
<constructor-arg>
<bean class=".opensaml.saml2.metadata.provider.FilesystemMetadataProvider">
<constructor-arg>
<bean class="java.io.File">
<constructor-arg value=".../src/main/resources/saml/sso.xml"/>
</bean>
</constructor-arg>
<property name="parserPool" ref="parsePool"/>
</bean>
</constructor-arg>
<constructor-arg>
<bean class=".springframework.security.saml.metadata.ExtendedMetadata">
</bean>
</constructor-arg>
<property name="metadataTrustCheck" value="false"/>
</bean>
</list>
</constructor-arg>
<property name="defaultIDP"
value="https://sts.windows/cd857f0c-ad35-49e9-9555-3e0f108aa92e/"/>
</bean>
<bean id="metadataGeneratorFilter"
class=".springframework.security.saml.metadata.MetadataGeneratorFilter">
<constructor-arg>
<bean
class=".springframework.security.saml.metadata.MetadataGenerator">
<property name="extendedMetadata">
<bean
class=".springframework.security.saml.metadata.ExtendedMetadata">
<property name="idpDiscoveryEnabled" value="false"/>
<property name="signMetadata" value="false"/>
</bean>
</property>
<property name="entityId" value="api://9d212449-b84a-4cbb-b0e1-7f500c925960"/>
<!-- <property name="requestSigned" value="false"></property>
<property name="wantAssertionSigned" value="false"></property>
<property name="nameID" value="UNSPECIFIED"></property> -->
<property name="includeDiscoveryExtension" value="false"/>
<property name="keyManager" ref="keyManager"/>
<property name="entityBaseURL" value="https://localhost:8443"/>
</bean>
</constructor-arg>
</bean>
<bean id="successRedirectHandler"
class="com.xxx.admin.security.AuthenticationSuccessHandler">
<!-- <qualifier value="saml"></qualifier>-->
<!-- <property name="defaultTargetUrl" value="/home.xhtml"></property>-->
</bean>
<bean id="failureRedirectHandler" class="com.xxx.admin.security.AuthenticationFailureHandler"/>
<sec:authentication-manager
alias="authenticationManager">
<sec:authentication-provider
ref="samlAuthenticationProvider"/>
</sec:authentication-manager>
<bean id="samlAuthenticationProvider"
class=".springframework.security.saml.SAMLAuthenticationProvider">
</bean>
<bean id="contextProvider" class=".springframework.security.saml.context.SAMLContextProviderImpl"/>
<!-- todo burda direk line 814 teki gibi ref verebilir miyiz bak
authentication-success-handler-ref="authenticationSuccessHandler"
authentication-failure-handler-ref="authenticationFailureHandler"
-->
<bean id="samlWebSSOProcessingFilter"
class=".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=".springframework.security.saml.SAMLWebSSOHoKProcessingFilter">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="authenticationSuccessHandler" ref="successRedirectHandler"/>
<property name="authenticationFailureHandler" ref="failureRedirectHandler"/>
</bean>
<bean id="successLogoutHandler" class="com.xxx.admin.security.LogoutHandler"/>
<bean id="logoutHandler"
class=".springframework.security.web.authentication.logout.SecurityContextLogoutHandler">
<property name="invalidateHttpSession" value="true"></property>
</bean>
<bean id="samlLogoutFilter"
class=".springframework.security.saml.SAMLLogoutFilter">
<constructor-arg index="0" ref="successLogoutHandler"></constructor-arg>
<constructor-arg index="1" ref="logoutHandler"></constructor-arg>
<constructor-arg index="2" ref="logoutHandler"></constructor-arg>
</bean>
<bean id="samlLogoutProcessingFilter"
class=".springframework.security.saml.SAMLLogoutProcessingFilter">
<constructor-arg index="0" ref="successLogoutHandler"></constructor-arg>
<constructor-arg index="1" ref="logoutHandler"></constructor-arg>
</bean>
<bean id="processor"
class=".springframework.security.saml.processor.SAMLProcessorImpl">
<constructor-arg>
<list>
<ref bean="redirectBinding"/>
<ref bean="postBinding"/>
</list>
</constructor-arg>
</bean>
<bean id="soapBinding" class=".springframework.security.saml.processor.HTTPSOAP11Binding">
<constructor-arg ref="parsePool"/>
</bean>
<bean id="paosBinding" class=".springframework.security.saml.processor.HTTPPAOS11Binding">
<constructor-arg ref="parsePool"/>
</bean>
<bean id="webSSOprofileConsumer"
class=".springframework.security.saml.websso.WebSSOProfileConsumerImpl">
</bean>
<bean id="webSSOprofile"
class=".springframework.security.saml.websso.WebSSOProfileImpl">
</bean>
<bean id="hokWebSSOProfileConsumer"
class=".springframework.security.saml.websso.WebSSOProfileConsumerHoKImpl">
<qualifier value="hokWebSSOprofileConsumer"/>
</bean>
<bean id="hokWebSSOProfile"
class=".springframework.security.saml.websso.WebSSOProfileConsumerHoKImpl">
</bean>
<bean id="ecpProfile"
class=".springframework.security.saml.websso.WebSSOProfileECPImpl">
</bean>
<bean id="logoutProfile"
class=".springframework.security.saml.websso.SingleLogoutProfileImpl">
</bean>
<bean id="postBinding"
class=".springframework.security.saml.processor.HTTPPostBinding">
<constructor-arg ref="parsePool"></constructor-arg>
<constructor-arg ref="velocityEngine"></constructor-arg>
</bean>
<bean id="redirectBinding"
class=".springframework.security.saml.processor.HTTPRedirectDeflateBinding">
<constructor-arg ref="parsePool"></constructor-arg>
</bean>
<bean id="samlBootstrap" class=".springframework.security.saml.SAMLBootstrap">
</bean>
<bean id="velocityEngine"
class=".springframework.security.saml.util.VelocityFactory"
factory-method="getEngine">
</bean>
<bean id="parsePool"
class=".opensaml.xml.parse.StaticBasicParserPool"
init-method="initialize" />
<bean id="parsePoolHolder"
class=".springframework.security.saml.parser.ParserPoolHolder">
</bean>
<bean id="samlLogger"
class=".springframework.security.saml.log.SAMLDefaultLogger">
<property name="logErrors" value="true"></property>
</bean>
<bean id="keyManager" class=".springframework.security.saml.key.JKSKeyManager">
<constructor-arg value="classpath:/saml/saml-keystore.jks"/>
<constructor-arg type="java.lang.String" value="azuresamlsso"/>
<constructor-arg>
<map>
<entry key="azuresaml" value="azuresamlsso">
</entry>
</map>
</constructor-arg>
<constructor-arg type="java.lang.String" value="azuresaml"/>
</bean>
<!-- <sec:ldap-server url="${ldap.url}" root="${ldap.basedn}" manager-dn="${ldap.user}" manager-password="${ldap.password}" />-->
<!-- <sec:authentication-manager>-->
<!-- <sec:ldap-authentication-provider user-search-filter="(sAMAccountName={0})" user-context-mapper-ref="ldapContextMapper" />-->
<!-- </sec:authentication-manager>-->
<!-- <bean id="sas" class=".springframework.security.web.authentication.session.ConcurrentSessionControlStrategy">-->
<!-- <constructor-arg name="sessionRegistry" ref="sessionRegistry" />-->
<!-- <property name="maximumSessions" value="${concurrent.users}" />-->
<!-- </bean>-->
<!-- <bean id="concurrencyFilter" class=".springframework.security.web.session.ConcurrentSessionFilter">-->
<!-- <property name="sessionRegistry" ref="sessionRegistry" />-->
<!-- <property name="expiredUrl" value="/logout.xhtml" />-->
<!-- </bean>-->
<bean id="sessionRegistry" class=".springframework.security.core.session.SessionRegistryImpl"/>
</beans>
Thanks in advance!
Share Improve this question asked 2 days ago fukit0fukit0 9741 gold badge16 silver badges39 bronze badges 5 |1 Answer
Reset to default 0I found out that my filter configuration in WEB-INF/web.xml
was wrong.
This was my wrong configuration:
<filter>
<filter-name>samlFilter</filter-name>
<filter-class>.springframework.security.saml.SAMLProcessingFilter</filter-class>
</filter>
The filter-class
element should have .springframework.web.filter.DelegatingFilterProxy
.
This is the mapping config in case you need it:
<filter-mapping>
<filter-name>samlFilter</filter-name>
<url-pattern>/saml/*</url-pattern>
</filter-mapping>
https://login.microsoftonline/TenantID/saml2
– Rukmini Commented yesterdayentityId
in the beanmetadataGeneratorFilter
. It says "AADSTS700016: Application with identifier 'login.microsoftonline{tenantId}/saml2' was not found in the directory 'Default Directory'" as theIdentifier (Entity ID)
in basic saml config isapi://9d212449-b84a-4cbb-b0e1-7f500c925960
– fukit0 Commented yesterdayhttps://login.microsoftonline/TenantID/saml2
– Rukmini Commented yesterdayIdentifier (Entity ID)
in the example is alsohttps://login.microsoftonline/TenantID/saml2
. They must match, otherwise you get the error I just shared. – fukit0 Commented yesterday