268

问题:
我们有一个基于 Spring MVC 的 RESTful API,其中包含敏感信息。API 应该是安全的,但是在每个请求中都发送用户的凭据(用户/密码组合)是不可取的。根据 REST 准则(和内部业务要求),服务器必须保持无状态。该 API 将由另一台服务器以 mashup 风格的方式使用。

要求:

  • .../authenticate客户端使用凭据向(不受保护的 URL)发出请求;服务器返回一个安全令牌,其中包含足够的信息供服务器验证未来的请求并保持无状态。这可能包含与 Spring Security 的Remember-Me Token相同的信息。

  • 客户端向各种(受保护的)URL 发出后续请求,将先前获得的令牌附加为查询参数(或者,不太理想的是,附加一个 HTTP 请求标头)。

  • 不能期望客户端存储 cookie。

  • 由于我们已经使用 Spring,所以解决方案应该使用 Spring Security。

我们一直在努力解决这个问题,所以希望有人已经解决了这个问题。

鉴于上述情况,您将如何解决这一特殊需求?

4

4 回答 4

192

我们设法让这个工作完全按照 OP 中的描述工作,希望其他人可以使用该解决方案。这是我们所做的:

像这样设置安全上下文:

<security:http realm="Protected API" use-expressions="true" auto-config="false" create-session="stateless" entry-point-ref="CustomAuthenticationEntryPoint">
    <security:custom-filter ref="authenticationTokenProcessingFilter" position="FORM_LOGIN_FILTER" />
    <security:intercept-url pattern="/authenticate" access="permitAll"/>
    <security:intercept-url pattern="/**" access="isAuthenticated()" />
</security:http>

<bean id="CustomAuthenticationEntryPoint"
    class="com.demo.api.support.spring.CustomAuthenticationEntryPoint" />

<bean id="authenticationTokenProcessingFilter"
    class="com.demo.api.support.spring.AuthenticationTokenProcessingFilter" >
    <constructor-arg ref="authenticationManager" />
</bean>

如您所见,我们创建了一个 custom AuthenticationEntryPoint401 Unauthorized如果请求没有在过滤器链中被我们的AuthenticationTokenProcessingFilter.

自定义身份验证入口点

public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response,
            AuthenticationException authException) throws IOException, ServletException {
        response.sendError( HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized: Authentication token was either missing or invalid." );
    }
}

AuthenticationTokenProcessingFilter

public class AuthenticationTokenProcessingFilter extends GenericFilterBean {

    @Autowired UserService userService;
    @Autowired TokenUtils tokenUtils;
    AuthenticationManager authManager;
    
    public AuthenticationTokenProcessingFilter(AuthenticationManager authManager) {
        this.authManager = authManager;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        @SuppressWarnings("unchecked")
        Map<String, String[]> parms = request.getParameterMap();

        if(parms.containsKey("token")) {
            String token = parms.get("token")[0]; // grab the first "token" parameter
            
            // validate the token
            if (tokenUtils.validate(token)) {
                // determine the user based on the (already validated) token
                UserDetails userDetails = tokenUtils.getUserFromToken(token);
                // build an Authentication object with the user's info
                UsernamePasswordAuthenticationToken authentication = 
                        new UsernamePasswordAuthenticationToken(userDetails.getUsername(), userDetails.getPassword());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails((HttpServletRequest) request));
                // set the authentication into the SecurityContext
                SecurityContextHolder.getContext().setAuthentication(authManager.authenticate(authentication));         
            }
        }
        // continue thru the filter chain
        chain.doFilter(request, response);
    }
}

显然,TokenUtils包含一些私密(并且非常特定于案例)代码并且不能轻易共享。这是它的界面:

public interface TokenUtils {
    String getToken(UserDetails userDetails);
    String getToken(UserDetails userDetails, Long expiration);
    boolean validate(String token);
    UserDetails getUserFromToken(String token);
}

这应该让你有一个好的开始。

于 2012-06-02T16:33:25.613 回答
27

您可能会考虑Digest Access Authentication。基本上协议如下:

  1. 请求来自客户端
  2. 服务器以唯一的 nonce 字符串响应
  3. 客户端提供了一个用户名和密码(以及一些其他值)md5 与随机数散列;这个哈希被称为 HA1
  4. 服务器然后能够验证客户的身份并提供所请求的材料
  5. 与 nonce 的通信可以继续,直到服务器提供新的 nonce(计数器用于消除重放攻击)

所有这些通信都是通过标头进行的,正如 jmort253 指出的那样,通常比在 url 参数中传达敏感材料更安全。

Spring Security支持摘要访问身份验证。请注意,尽管文档说您必须有权访问客户端的纯文本密码,但如果您拥有客户端的 HA1 哈希,则可以成功进行身份验证。

于 2012-05-31T02:43:21.943 回答
7

关于携带信息的令牌,JSON Web Tokens ( http://jwt.io ) 是一项出色的技术。主要概念是将信息元素(声明)嵌入到令牌中,然后对整个令牌进行签名,以便验证端可以验证声明确实可信。

我使用这个 Java 实现:https ://bitbucket.org/b_c/jose4j/wiki/Home

还有一个 Spring 模块(spring-security-jwt),但我还没有研究它支持什么。

于 2016-03-28T10:01:28.897 回答
3

为什么不开始使用带有 JSON WebTokens 的 OAuth

http://projects.spring.io/spring-security-oauth/

OAuth2 是一种标准化的授权协议/框架。根据官方 OAuth2规范

你可以在这里找到更多信息

于 2017-07-24T04:43:04.313 回答