Spring:如果 url 的安全性设置为无,则检查代码

Posted

技术标签:

【中文标题】Spring:如果 url 的安全性设置为无,则检查代码【英文标题】:Spring: Check in code if url has security set to none 【发布时间】:2016-12-25 03:01:15 【问题描述】:

可以检查 Spring Interceptor preHandle() 方法是否请求的 URL 受 Spring Security 保护(已设置 security="none")?

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception 
     if(isSecured(request) && !paymentRegistered())
        response.sendRedirect("/payment")
    return super.preHandle(request, response, handler);

private boolean isSecured(HttpServletRequest request)
//how to check if url has security=none

我的问题是成功登录后我想检查用户是否已支付服务费用。如果不是,我想重定向到付款页面。我的想法是编写自定义请求范围过滤器或拦截器,并检查用户是否在数据库中注册了付款。问题是我不想过滤不安全的 URL,例如资源、登录页面、错误页面等。付款页面(安全的)也应该可用。 也许更好的想法是编写自定义安全过滤器并将自定义标志添加到 Principal 对象,例如 servicePayed 以及其他安全标志:enabedaccountNonExipired 等。

【问题讨论】:

【参考方案1】:

我会写一个自定义的AuthenticationSuccessHandler,主要基于简单的实现SimpleUrlAuthenticationSuccessHandler。

在您的实现中,您应该覆盖 onAuthenticationSuccess 方法,并检查您是否应该将用户重定向到支付页面。

/**
     * Calls the parent class @code handle() method to forward or redirect to the target
     * URL, and then calls @code clearAuthenticationAttributes() to remove any leftover
     * session data.
     */
    public void onAuthenticationSuccess(HttpServletRequest request,
            HttpServletResponse response, Authentication authentication)
            throws IOException, ServletException 

        if(mustCompletePayment(authentication))
            handle(request, response, authentication);
            clearAuthenticationAttributes(request);
        

然后只需使用身份验证对象编写一种 mustCompletePayment ,您必须能够从中检查用户是否必须完成付款,或者您是否已经自定义 UserDetailsS​​ervice 在身份验证期间进行检查,只需检查该指标在您的身份验证对象中

编辑: 如果您真正想要做的是避免在登录用户未完成付款时对其采取任何行动,我将使用授予的权限进行管理。

如我所见,这里的关键是将用户尚未支付给授权层的事实转化为您可以利用的方式。

你已经实现了判断用户是否完成支付信息的逻辑,所以你可以自己写UserDetailsService,所以在

UserDetails loadUserByUsername(String username)throws UsernameNotFoundException

您可以检查一下,如果用户没有完成付款,只需从 UserDetails 中删除任何返回的 granthedAuthority 并只让一个声明用户必须完成付款,比如说 ROLE_USER_HAS_NOT_PAID。

然后,在安全 http 配置中(这是 xml 版本,但可能你使用的是 java 配置),进行这样的映射:

<security:http ...>
     ...
     <security:intercept-url pattern="/secured/payment/**" access="ROLE_USER,ROLE_USER_HAS_NOT_PAID" />
     <security:intercept-url pattern="/secured/**" access="ROLE_USER_HAS_PAID" />
     ...
</security:http>

使用此配置,任何用户都可以访问付款页面,无论用户是否已付款,而其他页面仅适用于已付款的用户。只是,请小心,因为一旦用户付费让他在每个页面都可用,您必须更新用户的授予权限。

这样,AuthenticationSuccessHandler 不应该评估用户授予的权限来决定将用户重定向到哪里。我已经多次通过基于有序地图构建 AuthenticationSuccessHandler 来实现这一点,在该地图中,我为每个需要自己的登陆页面的授权机构配置了登陆页面。

现在,如果用户未完成付款,则任何登录的用户操作都将被禁止,因此在尝试访问任何其他安全资源时会引发 HTTP 403。但是你不想仅仅阻止用户做任何其他事情,你想将它重定向到支付页面。在这里您需要一个AccessDeniedHandler,您可以在其中进行或多或少相同的检查:

public class CustomAuthenticationAccessDeniedHandler extends
        AccessDeniedHandlerImpl implements AccessDeniedHandler 

    private String errorPage = "/error/403";

    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    @Override
    public void handle(HttpServletRequest arg0, HttpServletResponse arg1,
            AccessDeniedException arg2) throws IOException, ServletException 
        SecurityContext context = SecurityContextHolder.getContext();
        if(context.getAuthentication() != null && context.getAuthentication().isAuthenticated())
            if(context.getAuthentication().getAuthorities().contains("ROLE_USER_HAS_NOT_PAID"))
                this.redirectStrategy.sendRedirect(arg0, arg1, "/secured/payment/pay");
                return;
            
        
        this.redirectStrategy.sendRedirect(arg0, arg1, this.getErrorPage());        
    

    public RedirectStrategy getRedirectStrategy() 
        return redirectStrategy;
    

    public void setRedirectStrategy(RedirectStrategy redirectStrategy) 
        this.redirectStrategy = redirectStrategy;
    

    @Override
    public void setErrorPage(String errorPage) 
        this.errorPage = errorPage;
    

    public String getErrorPage() 
        return errorPage;
    


通过这种方式,您会将仍然必须付款的用户重定向到您的付款页面,在任何其他情况下,您都会重定向到默认的 403 页面

【讨论】:

这是我的第一个想法。问题是您使用权限机制来评估您的业务逻辑。您拥有权限,并且由于业务逻辑而人为地删除了它。但考虑到所有其他可能的解决方案,它似乎是最好的 其实我不认为这是个问题。您只是根据到达的用户的个人资料决定将到达的用户重定向到哪里。一种常见的情况是根据用户授予的权限重定向到一个或另一个登录页面,我不得不多次这样做。 但是您在浏览器中输入 URL 的解决方案是什么? AuthenticationSuccessHandler 仅在登录过程中触发。它会重定向到付款,但是当用户在浏览器中输入另一个 URL 时它会起作用。我的想法是从除支付权限之外的所有授予权限中消失主体对象。你有更好的主意吗? 请看看我的编辑。我会按照你在上一条评论中所说的去做 几乎 :) 我在创建 Principal 对象期间删除了所有权限,但 PAYMENT_PERMISSION (如果用户有)。登录后,用户将根据权限重定向到所需页面。在 URL 栏中手动输入的所有其他页面返回 403。【参考方案2】:

也许你会找到一种方法来做到这一点,但恕我直言,你不应该这样做,因为它可能需要深入了解 Spring Security 内部。

如果您只想按照设计的方式使用 Spring Security,您可以实现自定义 AccessDecisionVoter。例如,如果只能对一个以 PAYMENT 开头的安全属性进行投票。您将该安全属性放在 spring 安全配置中:

<security:intercept-url pattern="/..." access="PAYMENT,ROLE_ADMIN"/>

限制对已付费用户或具有 ADMIN 角色的用户的访问权限

要声明自定义投票者,您必须替换默认访问决策管理器。首先你声明它:

<bean id="accessDecisionManager"
      class="org.springframework.security.access.vote.AffirmativeBased">
    <constructor-arg>
        <list>
            <bean class="org.springframework.security.access.vote.AuthenticatedVoter"/>
            <bean class="org.springframework.security.access.vote.RoleVoter"/>
            <bean class="your.package.PaymentVoter"/>
        </list>
    </constructor-arg>
</bean>

然后你将它插入到你的&lt;http&gt; 元素中:

<http access-decision-manager-ref="accessDecisionManager"/>

【讨论】:

好的,但是您的解决方案是什么?添加自定义选民,评估未支付服务,下一步是什么?安全性不会对用户进行身份验证,但用户必须通过身份验证才能为服务付费。【参考方案3】:

不知道是否有办法从 Spring Security 获取此类信息。但也许如果你没有很多不安全的 url,你可以这样做:

private boolean isSecured(HttpServletRequest request) 
    String requestUri = request.getRequestURI();
    return !(request.getMethod().equalsIgnoreCase("GET")
            && (requestUri.contains("error/")
            || requestUri.startsWith("resources/"))
    );

或者将那些不安全的资源移动到一些通用的开始路径,并使用上面代码中描述的想法。

【讨论】:

有两个以上的 URL。但这不是问题。您的解决方案的问题是您复制了与安全 URL 连接的逻辑。您必须在安全设置和过滤器中提供路径。也许您可以提出比过滤器更好的方法?

以上是关于Spring:如果 url 的安全性设置为无,则检查代码的主要内容,如果未能解决你的问题,请参考以下文章

php 如果设置为无,则设置默认特色图像

Spring安全性重定向到经过身份验证的URL的登录页面

如果我将默认设置为无,我可以省略 Optional 吗?

Spring 安全重定向到登录页面以获取经过身份验证的 url

Spring Boot - 具有安全性的 Angularjs $stateProvider

由 url spring security 保护