Search This Blog

Wednesday 16 October 2013

Spring Security - The Login Flow

In the last post we saw how Spring security can be used to secure web applications. I used both the form login and the basic login to get the user authentication done.
While basic login is pretty much client system dependent, the form login can be tweaked and customized. I decided to try out the options.

I decided to stick to basic configurations in web.xml:
  1. The Listener to load my configuration and set up the Spring Container:
      <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
      </listener>
    
      <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
          /WEB-INF/spring-security.xml
        </param-value>
      </context-param>
    
  2. The filter to bring in the security behavior:
      <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
      </filter>
    
      <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
      </filter-mapping>
    
  3. A welcome file to make life easier:
      <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
      </welcome-file-list>
    
Below is the project structure:
I have some pages in the dynamic folder that should be accessed only by a logged in user. Rest of the files in the WebContent folder should be visible to all users. If I were to use the default settings as before, Then my spring configuration would be:
<http>
    <intercept-url pattern="/dynamic/**" access="ROLE_USER" />
    <intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY" />
    <form-login />
  </http>
The flow is as below:
  1.  User try to access a page of the form http://<server>/dynamic/*.
  2. Spring Security checks to see if user is authorized.
    2013-07-12 14:16:55 DEBUG AntPathRequestMatcher:116 - Checking match of request :
     '/dynamic/account.jsp'; against '/dynamic/**'
    2013-07-12 14:16:55 DEBUG FilterSecurityInterceptor:194 - Secure object: 
    FilterInvocation: URL: /dynamic/account.jsp; Attributes: [ROLE_USER]
    ...
    2013-07-12 14:16:55 DEBUG ExceptionTranslationFilter:165 - Access is denied
     (user is anonymous); redirecting to authentication entry point
    ...
    2013-07-12 14:16:55 DEBUG HttpSessionRequestCache:41 - DefaultSavedRequest 
    added to Session: DefaultSavedRequest[http://localhost:8080/FormLogin/dynamic/account.jsp]
    2013-07-12 14:16:55 DEBUG DefaultRedirectStrategy:36 - Redirecting to
     'http://localhost:8080/FormLogin/spring_security_login;jsessionid=D3F72EC17AD8FC2213DE49260FB73266'
    
  3. As seen Spring security detected the lack of authorization. It also created a http session and added the requested page as an attribute to the session. It will then redirect the request to a login page auto generated by spring.
The page generated is a simple form page.Spring does not force us to use this page. We can always create our own custom login page (maybe with styling similar to our site) and add it to the Spring security flow.
Accordingly I updated the spring configuration file:
<http>
    <intercept-url pattern="/dynamic/**" access="ROLE_USER" />
    <intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY" />
    <form-login login-page="/login.jsp"/>
  </http>
The login page is a simple jsp:
<!doctype html>
<html>
  <head>
  <title>FormSecure - Login</title>
  </head>
  <body>
    <h2>Please Login to Proceed</h2>
    <p>
      <b>Security Settings : </b>IS_AUTHENTICATED_ANONYMOUSLY (All Allowed
      here with this role)
    </p>
    <form method="POST"
      action="${pageContext.request.contextPath}/j_spring_security_check">
      <table style="border: 1 px grey;">
        <tr>
          <td>User name</td>
          <td><input type="text" name="j_username"></td>
        </tr>
        <tr>
          <td>Password</td>
          <td><input type="password" name="j_password"></td>
        </tr>
      </table>
      <input type="submit" value="Login!">
    </form>
  </body>
</html>
The page resembles very much the login page provided to Containers for form authentication. The one major difference is the action URL. For Container managed authentication it would be 'j_security_check'. Spring uses the url 'j_spring_security_check'. The form fields use the same names as before.
If we now try to access the secure page.
2013-07-12 14:47:52 DEBUG ExceptionTranslationFilter:165 - Access is denied (user is anonymous);
 redirecting to authentication entry point
...
2013-07-12 14:47:52 DEBUG ExceptionTranslationFilter:185 - Calling Authentication entry point.

2013-07-12 14:47:52 DEBUG DefaultRedirectStrategy:36 - Redirecting to 
'http://localhost:8080/FormLogin/login.jsp;jsessionid=43BC455CE360AE04C6C75163D95B0333'
...
2013-07-12 14:47:52 DEBUG FilterSecurityInterceptor:194 - Secure object: FilterInvocation: 
URL: /login.jsp; Attributes: [IS_AUTHENTICATED_ANONYMOUSLY]
2013-07-12 14:47:52 DEBUG FilterSecurityInterceptor:310 - Previously Authenticated: 
org.springframework.security.authentication.AnonymousAuthenticationToken@6faa1b5a:
Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: 
org.springframework.security.web.authentication.WebAuthenticationDetails@ffff6a82:
RemoteIpAddress: 127.0.0.1; SessionId: 43BC455CE360AE04C6C75163D95B0333; Granted Authorities: ROLE_ANONYMOUS
...
2013-07-12 14:47:52 DEBUG FilterSecurityInterceptor:215 - Authorization successful
As login.jsp is allowed for users with role IS_AUTHENTICATED_ANONYMOUSLY, the page can be rendered without authentication. The authentication provider that I have used here is a local authentication provider.
On providing the credentials:
DEBUG UsernamePasswordAuthenticationFilter:189 - Request is to process authentication
DEBUG ProviderManager:152 - Authentication attempt using org.springframework.security
.authentication.dao.DaoAuthenticationProvider
TRACE XmlWebApplicationContext:332 - Publishing event in Root WebApplicationContext:
org.springframework.security.authentication.event.AuthenticationSuccessEvent[
source=org.springframework.security.authentication.
UsernamePasswordAuthenticationToken@bd980118: Principal: org.springframework.security.core.userdetails.User@67a69aa:
Username: robin; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; 
credentialsNonExpired: true; AccountNonLocked: true;Granted Authorities: ROLE_USER; 
Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.
WebAuthenticationDetails@ffff6a82: RemoteIpAddress: 127.0.0.1; 
SessionId: 43BC455CE360AE04C6C75163D95B0333; Granted Authorities: ROLE_USER]
DEBUG SessionFixationProtectionStrategy:88 - Invalidating session with Id 
'43BC455CE360AE04C6C75163D95B0333' and migrating attributes.
DEBUG SessionFixationProtectionStrategy:98 - Started new session: F50A51FAF695FFC1E81F6E2DF5A2DCD5
DEBUG UsernamePasswordAuthenticationFilter:317 - Authentication success.
DEBUG DefaultRedirectStrategy:36 - Redirecting to 'http://localhost:8080/FormLogin/dynamic/account.jsp'
  1. Spring verified the credentials. 
  2. On authenticating the user successfully, it discarded the previous session. 
  3. It associated a new session with the request, added the authentication details to it, before redirecting the user to the requested page. 
  4. Before redirecting there was an authorization check performed to verify that the user has sufficient privileges to view the account.jsp page.
    2013-07-12 14:53:15 DEBUG FilterSecurityInterceptor:194 - Secure object: FilterInvocation: 
    URL: /dynamic/account.jsp; Attributes: [ROLE_USER]
    2013-07-12 14:53:15 DEBUG AffirmativeBased:65 - Voter: 
    org.springframework.security.access.vote.RoleVoter@1ab2f62, returned: 1
    2013-07-12 14:53:15 DEBUG FilterSecurityInterceptor:215 - Authorization successful
    

3 comments:

  1. This is information verry nice and detail, thank you ..

    ReplyDelete
  2. This comment has been removed by the author.

    ReplyDelete
    Replies
    1. This is example is good but when used IS_AUTHENTICATED_ANONYMOUSLY not working returns returns run-time exception.
      this works.

      Delete