Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I allow a user override with Spring Security?

In my Spring MVC web application, there are certain areas accessible only to users with sufficient privileges. Rather than just have a "access denied" message, I need to be able to allow users to log in as a different user in order to use these pages (sort of like an override).

How can I do this with Spring Security?

Here's the flow I am looking to have, with a bit more detail:

  1. User A comes in to page X from external application and is authenticated via headers
  2. User A does not have permission to use page X, and so is taken to the login screen with a message indicating that they must log in as a user with sufficient privilages to use this page
  3. User B logs in, and has sufficient privilages, and is taken to page X.

Note: Page X has a big, long query string that needs to be preserved.

How can I do this with Spring Security?


Here's my spring security config file:

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
    xmlns:beans="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
            http://www.springframework.org/schema/security 
            http://www.springframework.org/schema/security/spring-security-3.1.xsd">

    <debug />

    <global-method-security pre-post-annotations="enabled">
        <!-- AspectJ pointcut expression that locates our "post" method and applies 
            security that way <protect-pointcut expression="execution(* bigbank.*Service.post*(..))" 
            access="ROLE_TELLER"/> -->
    </global-method-security>

    <!-- Allow anyone to get the static resources and the login page by not applying the security filter chain -->
    <http pattern="/resources/**" security="none" />
    <http pattern="/css/**" security="none" />
    <http pattern="/img/**" security="none" />
    <http pattern="/js/**" security="none" />

    <!-- Lock everything down -->
    <http 
        auto-config="true"
        use-expressions="true" 
        disable-url-rewriting="true">

        <!-- Define the URL access rules -->
        <intercept-url pattern="/login" access="permitAll" />
        <intercept-url pattern="/about/**" access="permitAll and !hasRole('blocked')" />
        <intercept-url pattern="/users/**" access="hasRole('user')" />
        <intercept-url pattern="/reviews/new**" access="hasRole('reviewer')" />
        <intercept-url pattern="/**" access="hasRole('user')" />

        <form-login 
            login-page="/login" />

        <logout logout-url="/logout" /> 

        <access-denied-handler error-page="/login?reason=accessDenied"/>

        <!-- Limit the number of sessions a user can have to only 1 -->
        <session-management>
            <concurrency-control max-sessions="1" />
        </session-management>
    </http>

    <authentication-manager>
        <authentication-provider ref="adAuthenticationProvider" />
        <authentication-provider>
            <user-service>
                <user name="superadmin" password="superadminpassword" authorities="user" />
            </user-service>
        </authentication-provider>
    </authentication-manager>

    <beans:bean id="adAuthenticationProvider" class="[REDACTED Package].NestedGroupActiveDirectoryLdapAuthenticationProvider">
        <beans:constructor-arg value="[REDACTED FQDN]" />
        <beans:constructor-arg value="[REDACTED LDAP URL]" />
        <beans:property name="convertSubErrorCodesToExceptions" value="true" />
        <beans:property name="[REDACTED Group Sub-Tree DN]" />
        <beans:property name="userDetailsContextMapper" ref="peerReviewLdapUserDetailsMapper" />
    </beans:bean>

    <beans:bean id="peerReviewLdapUserDetailsMapper" class="[REDACTED Package].PeerReviewLdapUserDetailsMapper">
        <beans:constructor-arg ref="UserDAO" />
    </beans:bean>

</beans:beans>

I'm using a slightly modified version of the Spring Security 3.1 Active Directory connection capabilities. The modifications simply load all of a user's groups, including those reached by group nesting, rather than only the ones the user is directly a member of. I'm also using a custom user object that has my application's User object embedded in it, and a custom LDAP mapper that does the normal LDAP mapping, and then adds in my user.

There is a special authentication scenario that has not been implemented yet where the user is authenticated based on a username passed from an external application (or via Kerberos) in a Single-Sign-On fashion.

like image 379
cdeszaq Avatar asked Sep 26 '11 17:09

cdeszaq


1 Answers

How do you check for roles ?

If you define them in your security context like this:

<intercept-url pattern="/adminStuff.html**" access="hasRole('ROLE_ADMIN')" />

You can set the defaultFailureUrl in your SimpleUrlAuthenticationFailureHandler and when a user with lesser privileges tries to access a secured URL the FaliureHandler should redirect you to the defaultFailureUrl which could be your login page.

You can inject a FaliureHandler in the filter at the FORM_LOGIN_FILTER position.

<bean id="myFaliureHandler" 
    class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
    <property name="defaultFailureUrl" value="http://yourdomain.com/your-login.html"/>
</bean>

<bean id="myFilter"
   class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
   <property name="authenticationFailureHandler" ref="myFaliureHandler"/>
</bean>    

<http>
  <custom-filter position="FORM_LOGIN_FILTER" ref="myFilter" />
</http>

Answering 1) in the comment.

This would be a little more work than I thought given your namespace configuration.

What you need to do is remove the <form-login> definition and instead of it add a 'custom' UsernamePasswordAuthenticationFilter (this is the filter that handles the <form-login> element).

You also need to remove the <access-denied-handler>.

So your configuration would look something like:

<bean id="myFaliureHandler" 
    class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
    <property name="defaultFailureUrl" value="http://yourdomain.com/your-login.html"/>
</bean>

<bean id="myFilter"
   class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
   <property name="authenticationFailureHandler" ref="myFaliureHandler"/>
   <!-- there are more required properties, but you can read about them in the docs -->
</bean>

<bean id="loginUrlAuthenticationEntryPoint"
   class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
   <property name="loginFormUrl" value="/login"/>
</bean>

<http entry-point-ref="authenticationEntryPoint" auto-config="false">

  <!-- your other http config goes here, just omit the form-login element and the access denied handler -->

  <custom-filter position="FORM_LOGIN_FILTER" ref="myFilter" />
</http>

Generally also have a look at the spring docs on custom filters, if you haven't already. We currently use this config in my current company forcing users to relogin if the don't have required privileges on a page.

like image 58
Simeon Avatar answered Oct 04 '22 22:10

Simeon