通过弹簧进行 RESTful 身份验证

2022-08-31 05:26:02

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

要求:

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

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

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

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

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

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


答案 1

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

如您所见,我们已经创建了一个 自定义 ,如果请求未由我们的.AuthenticationEntryPoint401 UnauthorizedAuthenticationTokenProcessingFilter

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

这应该能让你有一个良好的开端。


答案 2

您可以考虑摘要式访问身份验证。从本质上讲,协议如下:

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

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

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