通过Spring进行RESTful身份验证

Chr*_*ell 253 java rest spring spring-mvc spring-security

问题:
我们有一个基于Spring MVC的RESTful API,它包含敏感信息.应该保护API,但是不希望向每个请求发送用户的凭证(用户/通过组合).根据REST准则(和内部业务要求),服务器必须保持无状态.API将由mashup风格的方法由另一台服务器使用.

要求:

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

  • 客户端对各种(受保护的)URL进行后续请求,将先前获得的令牌附加为查询参数(或者不太希望的是HTTP请求头).

  • 不能指望客户存储cookie.

  • 由于我们已经使用Spring,因此该解决方案应该使用Spring Security.

我们一直在试图让这项工作碰壁,所以希望那里的人已经解决了这个问题.

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

Chr*_*ell 182

我们设法完全按照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>
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,我们已经创建了一个自定义AuthenticationEntryPoint,401 Unauthorized如果请求未在我们的过滤器链中进行身份验证,则基本上只返回一个AuthenticationTokenProcessingFilter.

CustomAuthenticationEntryPoint:

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." );
    }
}
Run Code Online (Sandbox Code Playgroud)

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

显然,TokenUtils包含一些私有(和特定于案例的)代码,不能轻易共享.这是它的界面:

public interface TokenUtils {
    String getToken(UserDetails userDetails);
    String getToken(UserDetails userDetails, Long expiration);
    boolean validate(String token);
    UserDetails getUserFromToken(String token);
}
Run Code Online (Sandbox Code Playgroud)

这应该让你有一个良好的开端.快乐的编码.:)

  • AuthenticationManager里面有什么? (3认同)
  • 很棒的提示.@ChrisCashwell - 我找不到的部分是你在哪里验证用户凭据并发送回令牌?我猜它应该是/ authenticate端点的impl中的某个地方.我对吗 ?如果不是/ authenticate的目标是什么? (2认同)

Tim*_*ote 24

您可以考虑摘要访问身份验证.本质上协议如下:

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

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

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


Lei*_*ohn 6

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

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

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