Spring 3 Security: AccessDeniedHandler is not being invoked

若如初见. 提交于 2019-12-04 16:22:45

问题


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>

回答1:


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>



回答2:


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.




回答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.




回答4:


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.




回答5:


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();
            }
        });

        //
        // ...
        //

    }

}



回答6:


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>



回答7:


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


来源:https://stackoverflow.com/questions/7013197/spring-3-security-accessdeniedhandler-is-not-being-invoked

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!