Category Archives: Security

JEE Security: Disabling HTTP OPTIONS method

PROBLEM

HTTP OPTIONS method is used to provide a list of methods that are supported by the web server.

For example, the following shows both GET and HEAD are allowed on the given link:-

➜  ~ curl -i -k -X OPTIONS https://localhost:8443/
HTTP/1.1 200
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
Strict-Transport-Security: max-age=31536000 ; includeSubDomains
X-Frame-Options: DENY
X-Application-Context: application:local:8443
Allow: GET,HEAD
Content-Length: 0
Date: Thu, 31 Aug 2017 14:07:21 GMT

Enabling OPTIONS may increase the risk of cross-site tracing (XST)… see OWASP’s Test HTTP Methods (OTG-CONFIG-006).

SOLUTION

There are several ways to disable OPTIONS method.

Solution 1: Using web.xml

If your app has web.xml, you may add the following snippet:-

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
		 http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         metadata-complete="true" version="3.1">

    <security-constraint>
        <web-resource-collection>
            <web-resource-name>restricted methods</web-resource-name>
            <url-pattern>/*</url-pattern>
            <http-method>OPTIONS</http-method>
        </web-resource-collection>
        <auth-constraint/>
    </security-constraint>
	
    <!-- Other configurations -->
</web-app>

Solution 2: Using Spring Boot

If you are using Spring Boot, there isn’t any option to mimic the above configuration programmatically.

However, you still can use web.xml in conjunction with Spring Boot by setting metadata-complete to false and use servlet version 3.0 or higher:-

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
		 http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         metadata-complete="false" version="3.1">

    <security-constraint>
        <web-resource-collection>
            <web-resource-name>restricted methods</web-resource-name>
            <url-pattern>/*</url-pattern>
            <http-method>OPTIONS</http-method>
        </web-resource-collection>
        <auth-constraint/>
    </security-constraint>
</web-app>

Solution 3: Using Spring Security

If you don’t want to use web.xml, you may configure Spring Security to disable OPTIONS method on all URIs:-

@Configuration
@EnableWebSecurity
class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(final HttpSecurity http) throws Exception {
        http.authorizeRequests().
                antMatchers(HttpMethod.OPTIONS, '/**').denyAll().
                antMatchers('/**').permitAll()
    }
}

Now, when trying to hit the same link with OPTIONS method, the app will return 403 Forbidden:-

➜  ~ curl -i -k -X OPTIONS https://localhost:8443/
HTTP/1.1 403
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
Strict-Transport-Security: max-age=31536000 ; includeSubDomains
X-Frame-Options: DENY
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Thu, 31 Aug 2017 14:26:51 GMT

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>

Java + HTTPS: Unable to Find Valid Certification Path to Requested Target

PROBLEM

When invoking a HTTPS URL from Java, for example…

final RestTemplate restTemplate = new RestTemplate();
restTemplate.execute("https://www.google.com/some-api", HttpMethod.GET, null, ...);

…the following exception is thrown…

org.springframework.web.client.ResourceAccessException: I/O error on GET 
request for "https://www.google.com":sun.security.validator.ValidatorException: 
PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: 
unable to find valid certification path to requested target; nested exception 
is javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: 
PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: 
unable to find valid certification path to requested target
	at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:607)
	at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:557)

SOLUTION 1: Disable SSL Validation – NOT RECOMMENDED

One way is to simply disable the SSL validation by configuring SSLContext to trust all X.509 certificates before invoking the intended HTTPS URL.

@Configuration
class MyAppConfig {
    @Bean
    public Boolean disableSSLValidation() throws Exception {
        final SSLContext sslContext = SSLContext.getInstance("TLS");

        sslContext.init(null, new TrustManager[]{new X509TrustManager() {
            @Override
            public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
            }

            @Override
            public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
            }

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
            }
        }}, null);

        HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());
        HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        });

        return true;
    }
}

Unless you are writing test cases or implementing non-production code, this approach is highly discouraged because it doesn’t prevent the man-in-the-middle attacks.

SOLUTION 2: Import Certificate into Java Keystore – RECOMMENDED

The reason we see this exception is because the certificate used by the HTTPS URL doesn’t exist in the Java Keystore. Hence, the SSL validation fails.

To fix this, we can download the website’s certificate using a browser to be added into Java Keystore.

Access the website (in this example, I use https://www.google.com) and click on the pad lock.

Find the button that allows you to view and export the certificate as a PEM file. In this example, I named that file google.cer.

The content of the downloaded PEM file should look like this:-

-----BEGIN CERTIFICATE-----
MIIEgDCCA2igAwIBAgIITq9JKLrGf5EwDQYJKoZIhvcNAQELBQAwSTELMAkGA1UE
BhMCVVMxEzARBgNVBAoTCkdvb2dsZSBJbmMxJTAjBgNVBAMTHEdvb2dsZSBJbnRl
eoV23mHmV6/0mOwocVYt/Th96WNGGmhANkFW//HCphRWnhaOqIG6yFRQ/jxArTvZ
QJEGI5AiYHzQn7LdUM8mH1o3ifR+lX+QiAwyeU9oegdlRslI2KMoPOuOFj329NFx
Bw+XVQXMsRJITPg8pnegPmLCOjpz8y7pBxbxGnfaI66I8X4dArsaXX4r5mkfhk2e
mm7fxQ8qUaW9mKoW0XvwGxU0AwKI8OopuXHoD97vr2GSK0QNZ19A96mtTWnQ2cu2
i9qjGw==
-----END CERTIFICATE-----

Go to the Terminal.

If you have multiple versions of java, make sure you choose the correct version first:-

export JAVA_HOME="`/usr/libexec/java_home -v '1.7*'`"

Import the certificate into Java Keystore:-

sudo keytool -import -trustcacerts -file /path/to/google.cer -alias google -keystore $JAVA_HOME/jre/lib/security/cacerts -storepass changeit

Now, you should be able to invoke the HTTPS URL without any exception.

If for some reason you need to remove the imported certificate from Java Keystore, you can refer to that certificate using the alias and run this command:-

sudo keytool -delete -alias google -keystore $JAVA_HOME/jre/lib/security/cacerts

Spring Security: Invalid CSRF Token ‘null’ was found on the request parameter ‘_csrf’ or header ‘X-CSRF-TOKEN’

PROBLEM

With Spring Security 4.x, the CSRF protection is enabled by default. You may disable it, but to be more aligned with OWASP and the industry security standard, it’s best to leave this setting the way it is. Learn more about CSRF attack…

To prevent this attack, Spring Security 4.x requires you to attach a server-side generated CSRF token on any POST, PUT or DELETE calls… basically, actions that may modify the request state. Their argument for not attaching this token on GET is to prevent this token value from leaking out.

Further, you will require to call POST /login and POST /logout now. In the past, you can call GET /j_spring_security_logout without problem.

If you invoke POST, PUT or DELETE without this CSRF token, you will get a 403 error with this message: "Invalid CSRF Token 'null' was found on the request parameter '_csrf' or header 'X-CSRF-TOKEN'.".

SOLUTION

To obtain this CSRF token, add this Spring Security custom tag to the JSP file:-

<!DOCTYPE html>
<html>
	<head>
	    <sec:csrfMetaTags/>
	</head>
	<body>
	</body>
</html>

The rendered HTML looks like this:-

<!DOCTYPE html>
<html class="no-js">
	<head>
	    <meta name="_csrf_parameter" content="_csrf" />
	    <meta name="_csrf_header" content="X-CSRF-TOKEN" />
	    <meta name="_csrf" content="e62835df-f1a0-49ea-bce7-bf96f998119c" />
	</head>
	<body>
	</body>
</html>

Finally, set the request header before making the AJAX call:-

var header = $("meta[name='_csrf_header']").attr("content");
var token = $("meta[name='_csrf']").attr("content");

$.ajax({
    url: '/test',
    type: 'POST',
    beforeSend: function(xhr){
        xhr.setRequestHeader(header, token);
    },
    success: function(data) {
        console.log(data);
    },
    error: function (xhr, ajaxOptions, thrownError) {
        console.log(xhr.status + ": " + thrownError);
    }
});

Spring Security: Forcing URLs to use HTTPS

PROBLEM

Your web application supports both HTTP and HTTPS. You want to force all URLs to use HTTPS.

SOLUTION

Spring Security has a simple configuration that allows us to redirect all HTTP-based URLs to HTTPS. All we have to do is to set requires-channel="https" on <security:intercept-url/> tag.

For example:-

<security:http auto-config="true">
	<security:form-login .../>
	<security:logout .../>
	
	<security:intercept-url pattern="/reports" access="ROLE_ADMIN" requires-channel="https"/>
	<security:intercept-url pattern="/login" access="IS_AUTHENTICATED_ANONYMOUSLY" requires-channel="https"/>
	<security:intercept-url pattern="/**" access="ROLE_USER" requires-channel="https"/>
</security:http>

With this configuration, when the user hits http://server/app, it will be redirected to https://server/app.

If we are seeing this “redirect loop” error…

… and the server log went bananas…

"GET /app HTTP/1.1" 302 0 
"GET /app/ HTTP/1.1" 302 0 
"GET /app/app/ HTTP/1.1" 302 0 
"GET /app/app/app/ HTTP/1.1" 302 0 
"GET /app/app/app/app/ HTTP/1.1" 302 0 
"GET /app/app/app/app/app/ HTTP/1.1" 302 0 
"GET /app/app/app/app/app/app/ HTTP/1.1" 302 0 
"GET /app/app/app/app/app/app/app/ HTTP/1.1" 302 0 
"GET /app/app/app/app/app/app/app/app/ HTTP/1.1" 302 0 
"GET /app/app/app/app/app/app/app/app/app/ HTTP/1.1" 302 0 

… then, chances are we are not using the default HTTP port (80) and HTTPS port (443). To fix this, we have to specify the custom port mappings in the Spring Security configuration:-

<security:http auto-config="true">
	<security:form-login .../>
	<security:logout .../>
	
	<security:intercept-url pattern="/reports" access="ROLE_ADMIN" requires-channel="https"/>
	<security:intercept-url pattern="/login" access="IS_AUTHENTICATED_ANONYMOUSLY" requires-channel="https"/>
	<security:intercept-url pattern="/**" access="ROLE_USER" requires-channel="https"/>
	
	<security:port-mappings>
		<!-- Default ports -->
		<security:port-mapping http="80" https="443"/>
		<!-- Websphere default ports -->
		<security:port-mapping http="9080" https="9443"/>
		<!-- Tomcat default ports -->
		<security:port-mapping http="8080" https="8443"/>
		<!-- Jetty custom ports -->
		<security:port-mapping http="7777" https="7443"/>
	</security:port-mappings>
</security:http>

Now, when the user hits http://localhost:7777/app, it will be redirected to https://localhost:7443/app.