Tuesday, June 30, 2015

Upgrading Spring Security from 3 to 4

There are a few changes to the configuration defaults in the Spring Security 4 release that may initially break your application.

Mostly this applies to applications using XML-based configurations. In the 4.0 release, there was an effort to align the XML-based Configuration defaults with the more recent JavaConfig counterparts.

For starters, turning on DEBUG logging for org.springframework.security will give you some hints.

The first change was in the main Spring Security Filter Chain. The CSRF token filter is added to the filter chain and turned on by default. This may cause your login form submit URL to fail the CSRF check and you will see the following in the log:

06/22/2015 18:26:19 DEBUG org.springframework.security.web.FilterChainProxy (FilterChainProxy.java:324) - /doLogin at position 5 of 13 in additional filter chain; firing Filter: 'CsrfFilter'
06/22/2015 18:26:19 DEBUG org.springframework.security.web.csrf.CsrfFilter (CsrfFilter.java:106) - Invalid CSRF token found for http://localhost/unity/doUserLogin

The change is described in SEC-2347. To restore the previous functionality, you can turn off CSRF by adding a disabled <csrf> element to your <http> configuration. Keep in mind that if you don't have another mechanism for CSRF protection in your app, you really should consider enabling Spring's CSRF.

  <http use-expressions="true">
    <csrf disabled="true"/>
    <intercept-url pattern="/app/login.do"  access="permitAll()" />
    <intercept-url pattern="/app/logout.do" access="permitAll()" />
    <intercept-url pattern="/app/**"        access="isAuthenticated()" /> 
    <intercept-url pattern="/**"            access="permitAll()" />
    <form-login login-processing-url="/doLogin"
      . . .
    />
  </http>



Another change that you may hit involves changes to the j_username/j_password parameters. Several of the default parameter names in the XML Configuration were changed to match the JavaConfig versions. If you were using "j_username" for your username parameter (which was historically rather common to match Java container managed security), starting with 4.0 you will no longer receive the username value in the authentication request.

This will likely result in the authentication manager complaining that there is no suitable authentication provider configured, and you will see the following in the log:

06/30/2015 18:43:08 DEBUG org.springframework.security.web.util.matcher.AntPathRequestMatcher (AntPathRequestMatcher.java:151) - Checking match of request : '/dologin'; against '/dologin'
06/30/2015 18:43:08 DEBUG org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter (AbstractAuthenticationProcessingFilter.java:211) - Request is to process authentication
06/30/2015 18:43:08 DEBUG org.springframework.security.authentication.ProviderManager (ProviderManager.java:162) - Authentication attempt using com.joshlandin.client.webapp.security.AuthenticationProvider
06/30/2015 18:43:08 DEBUG org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter (AbstractAuthenticationProcessingFilter.java:350) - Authentication request failed: org.springframework.security.authentication.ProviderNotFoundException: No AuthenticationProvider found for org.springframework.security.authentication.UsernamePasswordAuthenticationToken

The change is described in SEC-2783. Beyond j_username and j_password, there are about 8 other parameters that are affected as well. To fix your application, you can either change the HTML input names in your login form to the new defaults ("username" and "password"), or adjust the Spring configuration as follows:

  <http use-expressions="true">
    . . .
    <form-login login-processing-url="/doLogin"
                username-parameter="j_username"
                password-parameter="j_password"
                . . . 
    />
    . . .
  </http>

The next change has to do with Role names. By default, the Spring Security Filter Chain wraps the HttpRequest so that calls to things like isUserInRole will look in the proper Spring Security Context. Prior to version 4.0, the roles did not have a default prefix. After upgrading, if you find that you're role checks always return false, it's likely because your AuthenticationProvider does not prefix the roles with "ROLE_". This change in 4.0 is in SecurityContextHolderAwareRequestFilter where a default rolePrefix has been added. Unfortunately, I'm not sure if there was a JIRA item for this change (I couldn't find one). One fix would be to add  a "ROLE_" prefix to each of your org.springframework.security.core.GrantedAuthority instances when building your Principal instance. Keep in mind, the prefix should not be added to any calling parameters, for example request.isUserInRole, etc.

Finally, the default setting for security headers was also changed so that they are now included in the response. This had no impact on the application I upgraded, but it's worth noting. The change is described in SEC-2348 which includes a description of how to turn off the headers if needed.

No comments:

Post a Comment