Category Archives: Spring

LdapTemplate: AttributesMapper vs ContextMapper

BACKGROUND

When using Spring’s LdapTemplate, there are two ways to transform the queried results: AttributesMapper and ContextMapper.

List<MyBean> list = ldapTemplate.search(
    '',
    '(cn=some-group-name)',
    // AttributesMapper or ContextMapper 
)

Here’s the comparison between these mapper classes.

AttributesMapper

If you are migrating your existing LDAP queries to Spring’s LdapTemplate, AttributesMapper seems ideal because you can copy most of the code over because it provides javax.naming.directory.Attributes.

List<MyBean> list = ldapTemplate.search(
    '',
    '(cn=some-group-name)',
    new AttributesMapper<MyBean>() {
        @Override
        MyBean mapFromAttributes(final Attributes attributes) throws NamingException {
            return new MyBean(
                cn: attributes.get('cn')?.get(),
                members: attributes.get('member')?.getAll()?.toSet() as Set<String> ?: []
            )
        }
    }
)

However, you have to handle possible null values if the attribute keys do not exist.

ContextMapper

With ContextMapper, it handles null values for us. Spring also provides an abstract class called AbstractContextMapper to further simplify the code.

List<MyBean> list = ldapTemplate.search(
    '',
    '(cn=some-group-name)',
    new AbstractContextMapper<MyBean>() {
        @Override
        protected MyBean doMapFromContext(final DirContextOperations ctx) {
            return new MyBean(
                cn: ctx.getStringAttribute('cn'),
                members: ctx.getStringAttributes('member')
            )
        }
    }
)
Advertisements

Spring: Component Scan Selected Classes

PROBLEM

Let’s assume we have a package with the following classes where each class is either annotated with Spring’s @Service, @Component, @Controller or @Repository.

app
├── A.groovy
├── B.groovy
├── C.groovy
├── D.groovy
└── E.groovy

When writing unit test, we want Spring to component scan class A and class B.

SOLUTION

Before we begin, we configure Log4j to log Spring in debug level.

<logger name="org.springframework">
    <level value="debug"/>
</logger>

Step 1

If we configure the test class like this…

@ContextConfiguration
class ASpec extends Specification {
    @Configuration
    @ComponentScan(
            basePackageClasses = [A]
	)
    static class TestConfig {
    }

    def "..."() {
        // ...
    }
}

It will scan all Spring components that reside in the same package as class A.

Debugging log:-

[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/A.class]
[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/B.class]
[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/C.class]
[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/D.class]
[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/E.class]

Step 2

We can set includeFilters to include just class A and class B…

@ContextConfiguration
class ASpec extends Specification {
    @Configuration
    @ComponentScan(
            basePackageClasses = [A],
            includeFilters = [@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = [A, B])]
	)
    static class TestConfig {
    }

    def "..."() {
        // ...
    }
}

… but it doesn’t do anything.

Debugging log:-

[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/A.class]
[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/B.class]
[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/C.class]
[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/D.class]
[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/E.class]

Step 3

To fix this, we set useDefaultFilters to false to disable any automatic detection of classes annotated with Spring’s @Service, @Component, @Controller or @Repository.

@ContextConfiguration
class ASpec extends Specification {
    @Configuration
    @ComponentScan(
            basePackageClasses = [A],
            useDefaultFilters = false,
            includeFilters = [@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = [A, B])]
    )
    static class TestConfig {
    }

    def "..."() {
        // ...
    }
}

Now, we get the intended behavior.

Debugging log:-

[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/A.class]
[DEBUG] [ClassPathBeanDefinitionScanner] [findCandidateComponents:294] - Identified candidate component class: file [/path/target/classes/app/B.class]

Spring Security SAML: Replacing SHA-1 with SHA-256 on Signature and Digest Algorithms

PROBLEM

By default, Spring Security SAML’s SAMLBootstrap uses SHA1withRSA for signature algorithm and SHA-1 for digest algorithm.

@Configuration
@EnableWebSecurity
public abstract class AppSAMLConfig extends WebSecurityConfigurerAdapter {
	...

    @Bean
    public static SAMLBootstrap SAMLBootstrap() {
        return new SAMLBootstrap();
    }
	
	...
}

For example, the above configuration will generate the following SAML request payload when using HTTP-POST binding:-

<?xml version="1.0" encoding="UTF-8"?>
<saml2p:AuthnRequest
    AssertionConsumerServiceURL="https://server/app/saml/SSO"
    Destination="https://adfs-server/adfs/ls/" ForceAuthn="true"
    ID="a3bj4e05i70f6946gi85299i51i02a" IsPassive="false"
    IssueInstant="2016-02-23T15:10:26.414Z"
    ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
    Version="2.0" xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol">
    <saml2:Issuer xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">https://server/app/saml/metadata</saml2:Issuer>
    <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
        <ds:SignedInfo>
            <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
            <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
            <ds:Reference URI="#a3bj4e05i70f6946gi85299i51i02a">
                <ds:Transforms>
                    <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
                    <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                </ds:Transforms>
                <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                <ds:DigestValue>u25hV7rk8hIpXYLJQs0aZjkueP0=</ds:DigestValue>
            </ds:Reference>
        </ds:SignedInfo>
        <ds:SignatureValue>YDR9ybi...</ds:SignatureValue>
        <ds:KeyInfo>
            <ds:X509Data>
                <ds:X509Certificate>MIICxz...</ds:X509Certificate>
            </ds:X509Data>
        </ds:KeyInfo>
    </ds:Signature>
    <saml2p:RequestedAuthnContext Comparison="exact">
        <saml2:AuthnContextClassRef xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml2:AuthnContextClassRef>
    </saml2p:RequestedAuthnContext>
</saml2p:AuthnRequest>

Unfortunately, SHA-1 is now deemed insecure due to “Freestart Collision” attack.

Further, most modern browsers have ceased to trust SHA-1 code signing certificates starting January 2016 and will eventually stop accepting these certificates by January 2017.

SOLUTION

To fix this, we could replace SHA-1 with stronger secure hash algorithm, such as SHA-256.

To do so, create a class that extends SAMLBootstrap that uses SHA256withRSA for signature algorithm and SHA-256 for digest algorithm.

public final class CustomSAMLBootstrap extends SAMLBootstrap {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        super.postProcessBeanFactory(beanFactory);
        BasicSecurityConfiguration config = (BasicSecurityConfiguration) Configuration.getGlobalSecurityConfiguration();
        config.registerSignatureAlgorithmURI("RSA", SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256);
        config.setSignatureReferenceDigestMethod(SignatureConstants.ALGO_ID_DIGEST_SHA256);
    }
}

Then, return CustomSAMLBootstrap instead of SAMLBootstrap

@Configuration
@EnableWebSecurity
public abstract class AppSAMLConfig extends WebSecurityConfigurerAdapter {
	...

    @Bean
    public static SAMLBootstrap SAMLBootstrap() {
        return new CustomSAMLBootstrap();
    }
	
	...
}

Now, the generated SAML request payload using HTTP-POST binding looks like this:-

<?xml version="1.0" encoding="UTF-8"?>
<saml2p:AuthnRequest
    AssertionConsumerServiceURL="https://server/app/saml/SSO"
    Destination="https://adfs-server/adfs/ls/" ForceAuthn="true"
    ID="a2e7f98agfaec7d253714fjdbcf8a83" IsPassive="false"
    IssueInstant="2016-02-23T15:18:43.452Z"
    ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
    Version="2.0" xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol">
    <saml2:Issuer xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">https://server/app/saml/metadata</saml2:Issuer>
    <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
        <ds:SignedInfo>
            <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
            <ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>
            <ds:Reference URI="#a2e7f98agfaec7d253714fjdbcf8a83">
                <ds:Transforms>
                    <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
                    <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                </ds:Transforms>
                <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
                <ds:DigestValue>w4qHFsBxFGifzemEJCYcuGOt+oZJ9N2DQM+Q2aEqJFI=</ds:DigestValue>
            </ds:Reference>
        </ds:SignedInfo>
        <ds:SignatureValue>YDR9ybi...</ds:SignatureValue>
        <ds:KeyInfo>
            <ds:X509Data>
                <ds:X509Certificate>MIICxz...</ds:X509Certificate>
            </ds:X509Data>
        </ds:KeyInfo>
    </ds:Signature>
    <saml2p:RequestedAuthnContext Comparison="exact">
        <saml2:AuthnContextClassRef xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml2:AuthnContextClassRef>
    </saml2p:RequestedAuthnContext>
</saml2p:AuthnRequest>

Spring Security SAML: Handling IdP’s Public Certificate When Loading Metadata Over HTTPS

PROBLEM

By default, when loading IdP’s metadata over HTTPS (ex: https://adfs-server/federationmetadata/2007-06/federationmetadata.xml), Spring Security SAML will perform the trust verification configured in JDK.

However, there are times we do not have direct access to JDK home directory especially if the web apps are hosted on someone else’s JEE or PaaS servers.

SOLUTION

To fix this, the IdP’s public certificate can be imported into the app’s keystore instead of JDK’s keystore.

keytool -importcert -file adfs-server.cer -keystore app-keystore.jks -alias "adfs-server"

Then, configure Spring Security SAML to use TLSProtocolConfigurer, which will use all public certificates stored in the app’s keystore as trust anchors for PKIX validation.

@Configuration
@EnableWebSecurity
public abstract class SecuritySAMLConfig extends WebSecurityConfigurerAdapter {

    ...
	
	// in this case, `app-keystore.jks` contains the app's public/private keys and 
	// the imported IdP's public certificate
    @Bean
    public KeyManager keyManager() {
        DefaultResourceLoader loader = new DefaultResourceLoader();
        Resource storeFile = loader.getResource("classpath:app-keystore.jks");
        Map<String, String> passwords = new HashMap<>();
        passwords.put("app_alias", "app_password");
        return new JKSKeyManager(storeFile, "app_password", passwords, "app_alias");
    }

    @Bean
    public TLSProtocolConfigurer tlsProtocolConfigurer() {
        return new TLSProtocolConfigurer();
    }

    @Bean
    public ProtocolSocketFactory protocolSocketFactory(KeyManager keyManager) {
        return new TLSProtocolSocketFactory(keyManager, null, "default");
    }

    @Bean
    public Protocol protocol(ProtocolSocketFactory protocolSocketFactory) {
        return new Protocol("https", protocolSocketFactory, 443);
    }

    @Bean
    public MethodInvokingFactoryBean socketFactoryInitialization(Protocol protocol) {
        MethodInvokingFactoryBean methodInvokingFactoryBean = new MethodInvokingFactoryBean();
        methodInvokingFactoryBean.setTargetClass(Protocol.class);
        methodInvokingFactoryBean.setTargetMethod("registerProtocol");
        Object[] args = {"https", protocol};
        methodInvokingFactoryBean.setArguments(args);
        return methodInvokingFactoryBean;
    }

    ...
}

Doing so makes the app more portable so that it can be deployed into any JEE or PaaS servers without any problem.

Spring Security SAML: Configuring Binding for Sending SAML Messages to IdP

PROBLEM

Depending on each institution’s Identity Provider (IdP) configuration, the Service Provider (Sp) may need to configure the correct binding for sending SAML messages to IdP.

SOLUTION

Using Spring Security SAML, the binding configuration is highlighted below:-

@Configuration
@EnableWebSecurity
public abstract class SecuritySAMLConfig extends WebSecurityConfigurerAdapter {
    
	...
	
    @Bean
    public WebSSOProfileOptions webSSOProfileOptions() {
        WebSSOProfileOptions webSSOProfileOptions = new WebSSOProfileOptions();
        webSSOProfileOptions.setIncludeScoping(false);
        webSSOProfileOptions.setBinding(...);
    }
	
    @Bean
    public SAMLEntryPoint samlEntryPoint(WebSSOProfileOptions webSSOProfileOptions) {
        SAMLEntryPoint samlEntryPoint = new SAMLEntryPoint();
        samlEntryPoint.setDefaultProfileOptions(webSSOProfileOptions);
        return samlEntryPoint;
    }

	...
}

HTTP-POST Binding

Configuration:-

webSSOProfileOptions.setBinding(SAMLConstants.SAML2_POST_BINDING_URI);

Using HTTP-POST binding, the SAML message to IdP will contain the signature information:-

<?xml version="1.0" encoding="UTF-8"?>
<samlp:Response
        Consent="urn:oasis:names:tc:SAML:2.0:consent:unspecified"
        Destination="https://server/my-app/saml/SSO"
        ID="_370d6ba5-177c-494b-9147-2eafd9ecb6c9"
        InResponseTo="a5c5dja1i5fgb2bf2e66f6g9g5398gj"
        IssueInstant="2016-02-18T15:28:43.473Z"
        Version="2.0"
        xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol">
    <Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">http://adfs-server/adfs/services/trust</Issuer>
    <samlp:Status>
        <samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
    </samlp:Status>
    <EncryptedAssertion xmlns="urn:oasis:names:tc:SAML:2.0:assertion">
        <xenc:EncryptedData Type="http://www.w3.org/2001/04/xmlenc#Element" xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
            <xenc:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#aes256-cbc"/>
            <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
                <e:EncryptedKey xmlns:e="http://www.w3.org/2001/04/xmlenc#">
                    <e:EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p">
                        <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                    </e:EncryptionMethod>
                    <KeyInfo>
                        <ds:X509Data xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
                            <ds:X509IssuerSerial>
                                <ds:X509IssuerName>CN=server</ds:X509IssuerName>
                                <ds:X509SerialNumber>1822784706</ds:X509SerialNumber>
                            </ds:X509IssuerSerial>
                        </ds:X509Data>
                    </KeyInfo>
                    <e:CipherData>
                        <e:CipherValue>isG83fVk50fJRI...</e:CipherValue>
                    </e:CipherData>
                </e:EncryptedKey>
            </KeyInfo>
            <xenc:CipherData>
                <xenc:CipherValue>+b2o6HNxaxsse7rkB...</xenc:CipherValue>
            </xenc:CipherData>
        </xenc:EncryptedData>
    </EncryptedAssertion>
</samlp:Response>

HTTP-Redirect Binding

Configuration:-

webSSOProfileOptions.setBinding(SAMLConstants.SAML2_REDIRECT_BINDING_URI);

Using SAML2_REDIRECT_BINDING_URI binding, the signature will be removed before the message is delivered. The signature is then performed on the serialized request and sent as a GET parameter.

<?xml version="1.0" encoding="UTF-8"?>
<saml2p:AuthnRequest xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol"
                     AssertionConsumerServiceURL="https://server/my-app/saml/SSO"
                     Destination="https://adfs-server/adfs/ls/"
                     ForceAuthn="false"
                     ID="a4719398gd37jgg464505g70i40a49"
                     IsPassive="false"
                     IssueInstant="2016-02-18T15:24:59.036Z"
                     ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
                     Version="2.0">
    <saml2:Issuer xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">https://server/my-app/saml/metadata</saml2:Issuer>
</saml2p:AuthnRequest>

Rest Template: Could Not Extract Response – No Suitable HttpMessageConverter Found for Response Type [X] and Content Type [application/json;charset=UTF-8]

PROBLEM

When invoking a web service using RestTemplate:-

restTemplate.getForObject("http://server/api", MyBean[].class));

… the following exception occurs:-

Exception in thread "main" org.springframework.web.client.RestClientException: 
Could not extract response: no suitable HttpMessageConverter found for response 
type [class [LMyBean;] and content type [application/json;charset=UTF-8]
	at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:110)
	at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:572)
	at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:530)
	at org.springframework.web.client.RestTemplate.getForObject(RestTemplate.java:237)

SOLUTION

If the content type is JSON, add the following dependency:-

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.6.0</version>
</dependency>

Spring Data JPA: Requested bean is currently in creation: Is there an unresolvable circular reference?

PROBLEM

Let’s assume we have the following Spring Data JPA repository…

public interface ProjectRepository extends JpaRepository<Project, Long>, ProjectRepositoryCustom {
    Project findByName(String name);
}

… this repository has some custom implementation…

public interface ProjectRepositoryCustom {
    Project doCustom(String name);
}

… this custom implementation depends on the original repository to reuse existing APIs…

public final class ProjectRepositoryImpl implements ProjectRepositoryCustom {
    private final ProjectRepository projectRepository;

    @Autowired
    public ProjectRepositoryImpl(final ProjectRepository projectRepository) {
        this.projectRepository = projectRepository;
    }

    @Override
    public Project doCustom(final String name) {
        final Project project = projectRepository.findByName(name);
        return ...
    }
}

When we run the code, we get the following exception:-

Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: 
Error creating bean with name 'projectRepositoryImpl': Requested bean 
is currently in creation: Is there an unresolvable circular reference?
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:347)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:223)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:293)

SOLUTION

To fix the circular reference problem, instead of auto-wiring ProjectRepository using the constructor, auto-wire using the field or setter method:-

public final class ProjectRepositoryImpl implements ProjectRepositoryCustom {
    // this field cannot be `final` anymore
    private ProjectRepository projectRepository;

    @Autowired
    public void setProjectRepository(final ProjectRepository projectRepository) {
        this.projectRepository = projectRepository;
    }

    @Override
    public Project doCustom(final String name) {
        final Project project = projectRepository.findByName(name);
        return ...
    }
}