16

I have a spring 3 application with the configurations given below. When any user tries to access a page and he/she isn't logged in, I get an Access is Denied exception with an ugly stack trace. How do I handle this exception and not let it dump out a stack trace. I implemented my own access-denied-handler but that doesn't get invoked.

Based on the type of the requested resource, I would like to show custom error messages or pages. Here is my spring configuration.

How do I get Spring to invoke my access-denied-handler . Here is my spring configuration

 <security:http auto-config='true'>
    <security:intercept-url pattern="/static/**" filters="none"/>
    <security:intercept-url pattern="/login" filters="none"/>

      <security:intercept-url pattern="/**" access="ROLE_USER" />

      <security:form-login login-page="/index"
            default-target-url="/home" always-use-default-target="true"
            authentication-success-handler-ref="AuthenticationSuccessHandler"        
            login-processing-url="/j_spring_security_check" 
            authentication-failure-url="/index?error=true"/>

       <security:remember-me key="myLongSecretCookieKey" token-validity-seconds="1296000" 
            data-source-ref="jdbcDataSource" user-service-ref="AppUserDetailsService" />

       <security:access-denied-handler ref="myAccessDeniedHandler" />   

    </security:http>

    <bean id="myAccessDeniedHandler"
         class="web.exceptions.handlers.AccessDeniedExceptionHandler">
      <property name="errorPage" value="/public/403.htm" />
    </bean>

The custom class for handling this exception is given below

public class AccessDeniedExceptionHandler implements AccessDeniedHandler
{

    private String errorPage;

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response,
            AccessDeniedException arg2) throws IOException, ServletException {
        response.sendRedirect(errorPage);
    }

       public void setErrorPage(String errorPage) {
       if ((errorPage != null) && !errorPage.startsWith("/")) {
            throw new IllegalArgumentException("errorPage must begin with '/'");
        }
        this.errorPage = errorPage;
    }

}

When I run this application, this is the error that I get. I am only pasting a part of the stacktrace and the Spring Debug logs.

20:39:46,173 DEBUG AffirmativeBased:53 - Voter: org.springframework.security.access.vote.RoleVoter@5b7da0d1, returned: -1
20:39:46,173 DEBUG AffirmativeBased:53 - Voter: org.springframework.security.access.vote.AuthenticatedVoter@14c92844, returned: 0
20:39:46,178 DEBUG ExceptionTranslationFilter:154 - Access is denied (user is anonymous); redirecting to authentication entry point
org.springframework.security.access.AccessDeniedException: Access is denied
    at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:71)
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:204)

How do I fix this problem? Firstly, I want to stop spring from Throwing that exception. If it still throws it, I want to handle it and not raise any flags.

Update: I have attached a part of my web.xml as well.

<!-- Hibernate filter configuration -->

<filter>
        <filter-name>HibernateFilter</filter-name>
        <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>HibernateFilter</filter-name> 
        <url-pattern>/*</url-pattern>       
        <dispatcher>FORWARD</dispatcher>
        <dispatcher>REQUEST</dispatcher>
    </filter-mapping>

<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>

    <!--Dispatcher Servlet -->

   <servlet>
     <servlet-name>rowz</servlet-name>
     <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
     <load-on-startup>1</load-on-startup>
   </servlet>
Ritesh M Nayak
  • 8,001
  • 14
  • 49
  • 78
  • I read in the spring documentation that AccessDeniedHandler is only invoked when a person is already logged in but does not have access to certain resources due to authorization levels. If so, then what should I use to handle this exception? – Ritesh M Nayak Aug 12 '11 at 07:31

7 Answers7

12

In your configuration You require the user to be always authenticated when entering any URL on Your site:

<security:intercept-url pattern="/**" access="ROLE_USER" />

I think You should allow the user to be unauthenticated when entering the login page:

<security:intercept-url pattern="/your-login-page-url" access="ROLE_ANONYMOUS" />
<security:intercept-url pattern="/your-login-process-url" access="ROLE_ANONYMOUS" />
<security:intercept-url pattern="/your-login-failure-url" access="ROLE_ANONYMOUS" />
<security:intercept-url pattern="/**" access="ROLE_USER" />

If You use URL's like: /login/start, /login/error and /login/failure You can have:

<security:intercept-url pattern="/login/**" access="ROLE_ANONYMOUS" />
<security:intercept-url pattern="/**" access="ROLE_USER" />

Update:

Having this configuration should make the framework to redirect all unauthenticated (anonymous) users to login page, and all authenticated to AccessDeniedHandler. The AccessDeniedException is one of the core parts of the framework and ignoring it is not a good idea. It's hard to help more if You only provide parts of Your Spring Security configuration.

Be sure to read the JavaDoc for ExceptionTranslationFilter for detailed explanation of what exceptions are thrown by the framework, why and how are the handled by default.

If possible, try removing as many custom parts You added, like AuthenticationSuccessHandler, RememberMeAuthenticationFilter and AccessDeniedHandler and see if the problem pesist? Try to get the minimal congiuration and add new features step by step to see where the error comes from.

One important thing that You don't mention in Your question is what is the result of this error message? Do You get HTTP 500? Or HTTP 403? Or do You get redirected to login page?

If, as You mentioned in the question, the user is unauthenticated and he/she gets redirected to login page, than that's how it's intended to work. It looks like You get the error message logged by ExceptionTranslationFilter:172 only because You have DEBUG level set to Spring Security classes. If so, than that's also how it's intended to work, and if You don't want the error logged, than simply rise the logging level for Spring Secyruty classes.

Update 2:

The patterns with filters="none" must match the login-page, login-processing-url and authentication-failure-ur attributes set in <security:form-login /> to skip all SpringSecurity checks on pages that display the login page and process the logging in.

<security:http auto-config='true'>
  <security:intercept-url pattern="/static/**" filters="none"/>
  <security:intercept-url pattern="/index" filters="none"/>
  <security:intercept-url pattern="/j_spring_security_check" filters="none"/>
  <security:intercept-url pattern="/**" access="ROLE_USER" />

  <security:form-login login-page="/index"
        default-target-url="/home" always-use-default-target="true"
        authentication-success-handler-ref="AuthenticationSuccessHandler"        
        login-processing-url="/j_spring_security_check" 
        authentication-failure-url="/index?error=true"/>

   <security:remember-me key="myLongSecretCookieKey" token-validity-seconds="1296000" 
        data-source-ref="jdbcDataSource" user-service-ref="AppUserDetailsService" />

   <security:access-denied-handler ref="myAccessDeniedHandler" />   

</security:http>
Roadrunner
  • 6,661
  • 1
  • 29
  • 38
  • There are URL patterns which have none as ther access privileges. And yes, login, logout etc are all part of that. The exception still gets thrown though. Isn't there a way to handle that exception? – Ritesh M Nayak Sep 08 '11 at 08:57
  • Sorry about the question not including the whole of Spring config. I don't get a 500 or a 403. I just get redirected to the Login page. And I have nothing added, most of parts like AuthSucccessHandler, Rememberme and AccessDeniedHandler are default settings. And, as I mentioned before- the login page, images, css, javascript and other things are not protected by role_user. They are mapped to 'none'. I don't know if I should map them to Role_anonymous, but I don't really see a difference there. – Ritesh M Nayak Sep 08 '11 at 17:55
  • I have updated my spring config and added the login page and the static handlers. As I mentioned, removing the AccessDeniedHandler bean declaration is not affecting anything. Spring docs also say that AccessDeniedHandler is invoked only when a higher permission level is required than the one who is requesting the resource. I am begining to think that none is treated differently compared to ROLE_ANONYMOUS. Maybe I do need to change the none mappings to ROLE_ANONYMOUS and try. – Ritesh M Nayak Sep 08 '11 at 18:00
  • 2
    Ofcourse `filters="none"` is diffrent to `access="ROLE_ANONYMOUS"`! When You set `filters="none"` You turn off **ALL** SpringSecurity filters, while setting `access="ROLE_ANONYMOUS"` only tells the `FilterSecurityInterceptor` to check the token `ROLE_ANONYMOUS`, wich evenually calls the `RoleVoter` and allows only the users with `GrantedAuthority.getAuthority() == "ROLE_ANONYMOUS"`. That's completely two diffrent things. Do read about [Security Filter Chain](http://static.springsource.org/spring-security/site/docs/3.0.x/reference/security-filter-chain.html) – Roadrunner Sep 11 '11 at 10:28
  • @Roadrunner I am getting a 500 status code instead of 403. Any Idea what can be the problem? – Sahil Chhabra Nov 24 '17 at 09:57
7

AccessDeniedHandler is invoked when user is logged in and there is no permissions to resource (source here). If you want to handle request for login page when user is not logged in, just configure in security-context:

<http ... entry-point-ref="customAuthenticationEntryPoint">

And define customAuthenticationEntryPoint:

<beans:bean id="customAuthenticationEntryPoint" class="pl.wsiadamy.webapp.controller.util.CustomAuthenticationEntryPoint">
</beans:bean>

TIP, don't try to fight with ExceptionTranslationFilter. I have tried to override org.springframework.security.web.access.ExceptionTranslationFilter, without effects:

<beans:bean id="exceptionTranslationFilter" class="org.springframework.security.web.access.ExceptionTranslationFilter">
  <beans:property name="authenticationEntryPoint"  ref="customAuthenticationEntryPoint"/>
  <beans:property name="accessDeniedHandler" ref="accessDeniedHandler"/>
</beans:bean>
<beans:bean id="accessDeniedHandler"
 class="org.springframework.security.web.access.AccessDeniedHandlerImpl">
  <beans:property name="errorPage" value="/accessDenied.htm"/>
</beans:bean>

The ref="customAuthenticationEntryPoint" just didn't invoked.

Athlan
  • 6,389
  • 4
  • 38
  • 56
3

I have added Spring Access denied page in follwing way: Spring Frame Work: 3.1 Spring Security: 3.1, Java 1.5+

Entry in *-security.xml:

<security:access-denied-handler error-page="/<My Any error page controller name>" />

Example:

<security:access-denied-handler error-page="/accessDeniedPage.htm" />

Error page will always start with "/"

Entry for controller:

@Controller
public class RedirectAccessDenied {

    @RequestMapping(value = "/accessDeniedPage.htm", method = RequestMethod.GET)
    public String redirectAccessDenied(Model model) throws IOException, ServletException {
        System.out.println("############### Redirect Access Denied Handler!");
        return "403";
    }
}

Here 403 is my JSP name.

athspk
  • 6,722
  • 7
  • 37
  • 51
2

Spring Security uses an AuthenticationEntryPoint object to decide what to do when a user requires authentication. You can create your own AuthenticationEntryPoint bean ( see javadoc ), and then set the entryPoint attribute in the http element:

<http entry-point-ref="entryPointBean" .... />

However, by default, the form-login element creates a LoginUrlAuthenticationEntryPoint which redirects all of your unauthenticated users to the login page, so you shouldn't have to do this yourself. In fact, the log you posted claims it is forwarding the user to the authentication entry point: "Access is denied (user is anonymous); redirecting to authentication entry point".

I wonder if the problem is that you turned off the filter chain for the login url. Instead of setting filters to none, which means spring security is bypassed entirely, try keeping the filters on but allowing unrestricted access like this:

<security:intercept-url pattern="/login" access="permitAll" />

If that still doesn't help, please post the rest of the log so we can see what happens after the request is transferred to the entry point.

MicGer
  • 305
  • 4
  • 8
1

Programmatically solution:

@Order(1)
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    //
    // ...
    //

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.exceptionHandling().accessDeniedHandler(new AccessDeniedHandlerImpl() {
            @Override
            public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
                super.handle(request, response, accessDeniedException);
                accessDeniedException.printStackTrace();
            }
        });

        //
        // ...
        //

    }

}
Marcelo C.
  • 3,822
  • 2
  • 22
  • 11
0

Can you check your web.xml is supporting forward request?

errorPage is a FORWARD request and mostly in web.xml we support REDIRECTS only. Just a thought else your code looks ok to me.

Edited

A different point of view and This is been taken from working code only. Have a look at Authenticated Voter class

Disable the annotations

<global-method-security pre-post-annotations="disabled"
    secured-annotations="disabled" access-decision-manager-ref="accessDecisionManager">
</global-method-security>

bypassing filters

<http auto-config="true" use-expressions="true"
    access-decision-manager-ref="accessDecisionManager"
    access-denied-page="/accessDenied">
    <intercept-url pattern="/appsecurity/login.jsp" filters="none" />
    <intercept-url pattern="/changePassword" filters="none" />
    <intercept-url pattern="/pageNotFound" filters="none" />
    <intercept-url pattern="/accessDenied" filters="none" />
    <intercept-url pattern="/forgotPassword" filters="none" />
    <intercept-url pattern="/**" filters="none" />


    <form-login login-processing-url="/j_spring_security_check"
        default-target-url="/home" login-page="/loginDetails"
        authentication-failure-handler-ref="authenticationExceptionHandler"
        authentication-failure-url="/?login_error=t" />
    <logout logout-url="/j_spring_security_logout"
        invalidate-session="true" logout-success-url="/" />
    <remember-me />
    <!-- Uncomment to limit the number of sessions a user can have -->
    <session-management invalid-session-url="/">
        <concurrency-control max-sessions="1"
            error-if-maximum-exceeded="true" />
    </session-management>
</http>

custom Decision Voter

<bean id="customVoter" class="xyz.appsecurity.helper.CustomDecisionVoter" />

Access Decision Manager

<!-- Define AccessDesisionManager as UnanimousBased -->
<bean id="accessDecisionManager" class="org.springframework.security.access.vote.UnanimousBased">
    <property name="decisionVoters">
        <list>
            <ref bean="customVoter" />
            <!-- <bean class="org.springframework.security.access.vote.RoleVoter" 
                /> -->
            <bean class="org.springframework.security.access.vote.AuthenticatedVoter" />
        </list>
    </property>
</bean>

Authentiation Exception Handler

<bean id="authenticationExceptionHandler"
    class="org.springframework.security.web.authentication.ExceptionMappingAuthenticationFailureHandler">
    <property name="exceptionMappings">
        <props>
            <!-- /error.jsp -->
            <prop
                key="org.springframework.security.authentication.BadCredentialsException">/?login_error=t</prop>
            <!-- /getnewpassword.jsp -->
            <prop
                key="org.springframework.security.authentication.CredentialsExpiredException">/changePassword</prop>
            <!-- /lockedoutpage.jsp -->
            <prop key="org.springframework.security.authentication.LockedException">/?login_error=t</prop>
            <!-- /unauthorizeduser.jsp -->
            <prop
                key="org.springframework.security.authentication.DisabledException">/?login_error=t</prop>
        </props>
    </property>
</bean>
Manish Singh
  • 3,463
  • 22
  • 21
0

It looks like spring tries to redirect users who have not logged in to the login page, which is "/index", but that itself is a protected url.

The other possibility is, it tries to display /public/403.html, but that is again protected by security configuration.

Can you add the following entries and try?

<security:intercept-url pattern="/login" filters="none" />
<security:intercept-url pattern="/public/**" filters="none" />
Raghuram
  • 51,854
  • 11
  • 110
  • 122