【问题标题】:Spring Security - separate configuration for REST API and other URLsSpring Security - REST API 和其他 URL 的单独配置
【发布时间】:2013-03-09 17:20:50
【问题描述】:

我有两组 URL - 一组是 REST API,第二组 - 是非常普通的网站。 我想为 REST API 应用不同的安全规则,以便偶尔调用 REST API 的用户/脚本将使用 401 代码(基本身份验证就可以)或仅 403 来回答。

所以我想允许访问 REST API:

  • 已登录的用户(对于调用 REST API 的站点页面上的 javascript,因此共享相同的会话)。
  • 一些使用 WWW-Authenticate 标头中的基本身份验证凭据调用 REST API 的脚本。

目前我正试图弄清楚什么配置会让 spring “理解”我想要的东西。 我想出了以下配置:

<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.1.xsd
                http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.1.xsd">
    <http pattern="/" security="none" />
    <http pattern="/static/**" security="none" />

    <!-- REST API -->
    <http pattern="/rest/*" use-expressions="true">
        <http-basic />
        <intercept-url pattern="/*" access="isAuthenticated()" />
    </http>

    <!-- Site -->
    <http access-denied-page="/WEB-INF/views/errors/403.jsp" use-expressions="true">
        <intercept-url pattern="/index.html" access="hasRole('ROLE_USER') or hasRole('ROLE_ANONYMOUS')" />
        <intercept-url pattern="/login.html" access="hasRole('ROLE_USER') or hasRole('ROLE_ANONYMOUS')" />
        <intercept-url pattern="/admin/*" access="hasRole('ROLE_ADMIN')" />
        <intercept-url pattern="/**" access="hasRole('ROLE_USER')" />
        <form-login login-page="/login.html"
                    default-target-url="/index.html"
                    authentication-failure-url="/login.html?error=1" />

        <logout logout-url="/logout.do" logout-success-url="/index.html" />

        <anonymous username="guest" granted-authority="ROLE_ANONYMOUS" />
        <remember-me />
    </http>

    <authentication-manager>
        <authentication-provider>
            <user-service>
                <user name="admin" password="2" authorities="ROLE_ADMIN,ROLE_USER" />
                <user name="alex" password="1" authorities="ROLE_USER" />
            </user-service>
        </authentication-provider>
    </authentication-manager>
</beans:beans>

不幸的是,不仅基本身份验证不适用于此配置,而且匿名用户发出的请求没有 403 响应 - 应用程序回答重定向 302 Found,我想禁止 REST API url。

我尝试为 REST API 添加自定义入口点:

<beans:bean id="ep403" class="org.springframework.security.web.authentication.Http403ForbiddenEntryPoint"/>


<!-- REST API -->
<http pattern="/rest/*" entry-point-ref="ep403" use-expressions="true">
    <intercept-url pattern="/*" access="hasRole('ROLE_USER')" />
    <http-basic />
</http>

但这也不起作用。

总结一下我想要得到的:

  • 允许授权用户访问 REST API(授权工具应考虑 cookie 中的用户会话)。
  • 如果脚本指定了正确的身份验证令牌并且未在 cookie 中指定会话,则允许脚本访问 REST API。

更新

在深入研究 Spring Security 内部之后,我发现了决定是否拒绝某些请求的代码。这就是所谓的“访问决策投票者”,基本上它们都适用于某个请求,如果链中的一个访问决策投票者投票拒绝访问某些资源,则该请求最终会被拒绝。

因此,可以通过引入特殊的访问决策投票器来解决原始问题,该投票器的行为方式如下:它尝试从会话中提取相关角色(如果请求中存在)并使用该角色继续进行授权步骤;如果没有会话存在,它会尝试根据 WWW-Authenticate 标头中的凭据对用户进行身份验证,然后使用与给定用户关联的角色继续进行授权步骤。

【问题讨论】:

    标签: spring spring-mvc spring-security


    【解决方案1】:

    作为 bluish@ 建议的解决方案重新发布 :) 希望这会对某人有所帮助。

    我找到了一个简单的解决方法。我只是将一个休息控制器映射到两组不同的 URL,并将每组分配给 spring 安全配置中的不同身份验证处理程序:

    <!-- Defines custom security policy for Stateful REST API -->
    <beans:bean id="nonRedirectingAccessDeniedHandler" class="org.springframework.security.web.access.AccessDeniedHandlerImpl"/>
    <beans:bean id="forbiddenEntryPoint" class="org.springframework.security.web.authentication.Http403ForbiddenEntryPoint"/>
    
    <!-- Stateful REST API -->
    <http pattern="/rest/stateful/**" use-expressions="true" entry-point-ref="forbiddenEntryPoint">
        <access-denied-handler ref="nonRedirectingAccessDeniedHandler"/>
        <intercept-url pattern="/rest/stateful/**" access="isAuthenticated()" />
    </http>
    
    <!-- Stateless REST API -->
    <http pattern="/rest/stateless/**" use-expressions="true" create-session="stateless">
        <http-basic/>
        <intercept-url pattern="/rest/stateless/**" access="isAuthenticated()" />
    </http>
    

    这看起来是解决该问题的好方法,因为将来您可能希望使用特定于用户或脚本用例的唯一 URL 来扩展“有状态”用户或“无状态”脚本 REST API。

    在我的情况下,当 UX 更改需要 REST API 提供一些旨在实现特定 UI 场景的新方法并且某些脚本场景可能需要添加一些 URL 来支持某些客户端脚本到服务器的交互时,可能会发生这种情况。

    【讨论】:

      【解决方案2】:

      您可以使用拦截器来检查具有/rest/ 模式的 URL。

      <mvc:interceptors>
              <mvc:interceptor>
                  <mvc:mapping path="/rest/**"/>
                  <bean class="com.your.class.name.Interceptor></bean>
              </mvc:interceptor>
      </mvc:interceptors>
      

      为此,在 XML 的标题部分添加以下行,即

      <beans xmlns="http://www.springframework.org/schema/beans"
          xmlns:mvc="http://www.springframework.org/schema/mvc"
      

      现在在 Interceptor 类中检查您需要的任何内容,并且该类应该实现 Spring HandlerInterceptor

      public class Interceptor implements HandlerInterceptor{
      
          @Override  
          public boolean preHandle(HttpServletRequest request,  
                                   HttpServletResponse response,  
                                   Object handler) throws Exception {
             //do what you need to check when the request arrives
             //do authentications here
             //return true if success
             //else false
          }
      
          @Override
          public void postHandle(HttpServletRequest request,
                                 HttpServletResponse response, 
                                 Object handler,
                                 ModelAndView modelAndView) throws Exception {
      
          }
      
          @Override
          public void afterCompletion(HttpServletRequest request,
                                      HttpServletResponse response, 
                                      Object handler, 
                                      Exception ex) throws Exception {    
          }
      
      }
      

      阅读this 了解有关这些方法的更多详细信息。 也可以看this

      【讨论】:

      • 我不确定我是否完全正确。如果我理解你的方法是正确的,这将要求我在提议的拦截器中实现所有必要的授权逻辑,因此安全配置将分布在 security-context.xml 和这样的自定义拦截器中。看起来有点吓人。好吧,如果这是唯一的解决方案,我想重新考虑我对 REST URL 进行身份验证和授权的方法。
      • 是的,你没看错..你现在是怎么做授权的??
      • 目前我正在考虑在负载均衡器端进行此操作。这是一个相当困难和脆弱的方法,但它确实有效。同时,我正在积极寻找足够的弹簧安全解决方案来解决这个问题。也许我会重新考虑我的方法,放弃基本身份验证,并为使用 REST API 的脚本包含单独的登录 URL。
      【解决方案3】:

      尝试将 REST API 的 pattern="/rest/*" 更改为 pattern="/rest/**"pattern="/*" 更改为 pattern="/**"

      <http pattern="/rest/**" use-expressions="true">
          <intercept-url pattern="/**" access="isAuthenticated()" />
          <http-basic />
      </http>
      

      【讨论】:

      • 谢谢,create-session 可以解决问题,但是尽管这使基本身份验证工作,但会话身份验证却被拒绝工作(换句话说,页面上的 javascript 将无法访问 REST API) .
      • 是的,这是真的。改变模式有帮助吗?
      • 该模式没有太大影响,因为现在我有一个非常简单的 REST API url 方案(比如 /rest/book?name=X - 目前我被身份验证负担困住了,我我不考虑扩展它)。当然,我的模式不适用于像 /rest/one/two 这样具有深层嵌套的 URL,感谢您指出这一点。 create-session="stateless" 使两种模式的基本身份验证工作,但它使 spring 忽略两种模式的现有授权会话。
      猜你喜欢
      • 2014-07-07
      • 2014-08-31
      • 2021-12-21
      • 2015-06-05
      • 2016-07-08
      • 2018-05-25
      • 2015-03-29
      • 2017-02-16
      相关资源
      最近更新 更多