【问题标题】:Spring 3 Security: AccessDeniedHandler is not being invokedSpring 3 Security:没有调用 AccessDeniedHandler
【发布时间】:2011-10-24 04:48:33
【问题描述】:

我有一个 spring 3 应用程序,其配置如下。当任何用户尝试访问页面并且他/她没有登录时,我会收到一个 Access is Denied 异常,并带有丑陋的堆栈跟踪。我该如何处理这个异常,而不是让它转储出堆栈跟踪。我实现了自己的拒绝访问处理程序,但没有被调用。

根据所请求资源的类型,我想显示自定义错误消息或页面。这是我的弹簧配置。

如何让 Spring 调用我的 access-denied-handler 。这是我的弹簧配置

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

下面给出了处理这个异常的自定义类

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

}

当我运行这个应用程序时,这是我得到的错误。我只粘贴了堆栈跟踪和 Spring Debug 日志的一部分。

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)

我该如何解决这个问题?首先,我想阻止 spring 抛出异常。如果它仍然抛出它,我想处理它而不是举起任何标志。

更新:我也附上了我的 web.xml 的一部分。

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

【问题讨论】:

  • 我在 spring 文档中读到 AccessDeniedHandler 仅在一个人已经登录但由于授权级别而无权访问某些资源时才被调用。如果是这样,那我应该用什么来处理这个异常?

标签: java spring-mvc spring-security spring-3


【解决方案1】:

在您的配置中,您要求用户在您的网站上输入任何 URL时始终经过身份验证:

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

我认为您应该允许用户在进入登录页面未经身份验证

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

如果您使用 URL 之类的:/login/start/login/error/login/failure,您可以:

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

更新:

具有此配置应该使框架将所有未经身份验证(匿名)的用户重定向到登录页面,并且所有身份验证到AccessDeniedHandlerAccessDeniedException 是框架的核心部分之一,忽略它不是一个好主意。如果您只提供部分 Spring Security 配置,则很难提供更多帮助。

请务必阅读 ExceptionTranslationFilter 的 JavaDoc,详细了解框架抛出哪些异常、默认情况下处理的原因和方式。

如果可能,请尝试删除您添加的尽可能多的自定义部分,例如 AuthenticationSuccessHandlerRememberMeAuthenticationFilterAccessDeniedHandler,看看问题是否仍然存在?尝试获得最小的配置并逐步添加新功能,以查看错误来自何处。

您在您的问题中没有提到的一件重要事情是此错误消息的结果是什么?你收到HTTP 500 吗?还是HTTP 403?或者你会被重定向到登录页面?

如果正如您在问题中提到的那样,用户未经身份验证并且他/她被重定向到登录页面,那么这就是它的预期工作方式。看起来您收到ExceptionTranslationFilter:172 记录的错误消息只是因为您将DEBUG 级别设置为Spring Security 类。如果是这样,那么这也是它的工作方式,如果您不想记录错误,那么只需提高 Spring Secyruty 类的日志记录级别。

更新 2:

带有filters="none" 的模式必须与&lt;security:form-login /&gt; 中设置的login-pagelogin-processing-urlauthentication-failure-ur 属性相匹配,以跳过显示登录页面并处理登录的页面上的所有 SpringSecurity 检查。

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

【讨论】:

  • 有些 URL 模式没有任何访问权限。是的,登录、注销等都是其中的一部分。但是仍然会抛出异常。没有办法处理这个异常吗?
  • 很抱歉这个问题不包括整个 Spring 配置。我没有收到 500 或 403。我只是被重定向到登录页面。而且我没有添加任何内容,AuthSuccccessHandler、Rememberme 和 AccessDeniedHandler 等大多数部分都是默认设置。而且,正如我之前提到的——登录页面、图像、css、javascript 和其他东西不受 role_user 保护。它们被映射到“无”。我不知道我是否应该将它们映射到 Role_anonymous,但我真的看不出有什么不同。
  • 我已经更新了我的 spring 配置并添加了登录页面和静态处理程序。正如我所提到的,删除 AccessDeniedHandler bean 声明不会影响任何事情。 Spring 文档还说,只有在需要比请求资源的权限级别更高的权限级别时才会调用 AccessDeniedHandler。我开始认为与 ROLE_ANONYMOUS 相比,没有人受到不同的对待。也许我确实需要将无映射更改为 ROLE_ANONYMOUS 并尝试。
  • 当然filters="none"access="ROLE_ANONYMOUS" 不同!当你设置filters="none" 你关闭ALL SpringSecurity 过滤器,而设置access="ROLE_ANONYMOUS" 只告诉FilterSecurityInterceptor 检查令牌ROLE_ANONYMOUS,最终调用RoleVoter 并且只允许GrantedAuthority.getAuthority() == "ROLE_ANONYMOUS" 的用户。这完全是两件不同的事情。请阅读Security Filter Chain
  • @Roadrunner 我得到的是 500 状态码而不是 403。知道可能是什么问题吗?
【解决方案2】:

AccessDeniedHandler 在用户登录 并且没有资源权限 (source here) 时调用。如果你想处理用户未登录时的登录页面请求,只需在security-context中配置:

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

并定义customAuthenticationEntryPoint:

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

提示不要试图与ExceptionTranslationFilter打架。 我试图覆盖org.springframework.security.web.access.ExceptionTranslationFilter,但没有效果:

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

ref="customAuthenticationEntryPoint" 只是没有被调用。

【讨论】:

    【解决方案3】:

    我以下列方式添加了 Spring Access denied 页面: 弹簧框架工作:3.1 Spring Security:3.1、Java 1.5+

    进入*-security.xml:

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

    例子:

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

    错误页面总是以“/”开头

    控制器入口:

    @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";
        }
    }
    

    这里的 403 是我的 JSP 名称。

    【讨论】:

      【解决方案4】:

      Spring Security 使用 AuthenticationEntryPoint 对象来决定当用户需要身份验证时要做什么。您可以创建自己的 AuthenticationEntryPoint bean (see javadoc),然后在 http 元素中设置 entryPoint 属性:

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

      但是,默认情况下,form-login 元素会创建一个 LoginUrlAuthenticationEntryPoint,它将所有未经身份验证的用户重定向到登录页面,因此您不必自己执行此操作。事实上,您发布的日志声称它正在将用户转发到身份验证入口点:“访问被拒绝(用户是匿名的);重定向到身份验证入口点”。

      我想知道问题是否在于您关闭了登录 url 的过滤器链。不要将过滤器设置为无,这意味着完全绕过弹簧安全性,请尝试保持过滤器打开但允许不受限制的访问,如下所示:

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

      如果仍然没有帮助,请发布日志的其余部分,以便我们查看请求转移到入口点后会发生什么。

      【讨论】:

        【解决方案5】:

        以编程方式解决:

        @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】:

          您能检查一下您的 web.xml 是否支持转发请求?

          errorPage 是一个 FORWARD 请求,主要在 web.xml 中,我们只支持 REDIRECTS。只是一个想法,否则您的代码对我来说看起来不错。

          编辑

          不同的观点,这仅取自工作代码。 看看Authenticated Voter class

          禁用注释

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

          绕过过滤器

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

          自定义决策投票器

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

          访问决策管理器

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

          身份验证异常处理程序

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

          【讨论】:

          • 我已启用转发和重定向到错误页面。我认为这不是 web.xml 问题。
          • 已添加部分web.xml
          • 没有得到这部分 FORWARD, REQUEST ?这个处理什么?
          【解决方案7】:

          看起来spring试图将尚未登录的用户重定向到登录页面,即“/index”,但它本身是一个受保护的url。

          另一种可能性是,它尝试显示 /public/403.html,但这又受到安全配置的保护。

          您可以添加以下条目并尝试吗?

          <security:intercept-url pattern="/login" filters="none" />
          <security:intercept-url pattern="/public/**" filters="none" />
          

          【讨论】:

            猜你喜欢
            • 2018-12-10
            • 2018-08-31
            • 2019-01-02
            • 2012-07-27
            • 2012-12-10
            • 2017-08-12
            • 1970-01-01
            • 2015-09-13
            • 2020-06-17
            相关资源
            最近更新 更多