通过Spring进行RESTful认证

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

要求:

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

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

  • 客户不能期望存储cookie。

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

  • 我们一直在试图做这项工作,我们的头撞墙,所以希望有人已经解决了这个问题。

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


    我们设法完全按照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>
    

    正如你所看到的,我们已经创建了一个自定义的AuthenticationEntryPoint ,如果请求没有被我们的AuthenticationTokenProcessingFilter过滤器链AuthenticationEntryPoint ,它基本上只返回401 Unauthorized

    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." );
        }
    }
    

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

    这应该让你有一个好的开始。 快乐的编码。 :)


    您可能会考虑摘要访问身份验证。 基本上协议如下:

  • 请求来自客户端
  • 服务器使用唯一的随机数字符串进行响应
  • 客户端提供用户名和密码(以及其他一些值)md5与随机数散列; 这个哈希被称为HA1
  • 然后服务器能够验证客户的身份并提供请求的材料
  • 与nonce的通信可以继续,直到服务器提供新的nonce(计数器用于消除重播攻击)
  • 所有这些通信都是通过标题完成的,正如jmort253指出的那样,通常比在url参数中传递敏感资料更安全。

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


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

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

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

    链接地址: http://www.djcxy.com/p/3803.html

    上一篇: RESTful Authentication via Spring

    下一篇: REST API Token