通过 Spring 进行 RESTful 身份验证 [英] RESTful Authentication via Spring

查看:36
本文介绍了通过 Spring 进行 RESTful 身份验证的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Problem:
We have a Spring MVC-based RESTful API which contains sensitive information. The API should be secured, however sending the user's credentials (user/pass combo) with each request is not desirable. Per REST guidelines (and internal business requirements), the server must remain stateless. The API will be consumed by another server in a mashup-style approach.

要求:

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

  • Client makes a request to .../authenticate (unprotected URL) with credentials; server returns a secure token which contains enough information for the server to validate future requests and remain stateless. This would likely consist of the same information as Spring Security's Remember-Me Token.

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

Client makes subsequent requests to various (protected) URLs, appending the previously obtained token as a query parameter (or, less desirably, an HTTP request header).

不能期望客户端存储 cookie.

Client cannot be expected to store cookies.

既然我们已经在使用 Spring,那么解决方案应该使用 Spring Security.

Since we use Spring already, the solution should make use of Spring Security.

我们一直在努力使这项工作成功,所以希望有人已经解决了这个问题.

We've been banging our heads against the wall trying to make this work, so hopefully someone out there has already solved this problem.

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

Given the above scenario, how might you solve this particular need?

推荐答案

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

We managed to get this working exactly as described in the OP, and hopefully someone else can make use of the solution. Here's what we did:

像这样设置安全上下文:

Set up the security context like so:

<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,如果我们的过滤器链中的请求未通过身份验证,它基本上只会返回一个 401 UnauthorizedAuthenticationTokenProcessingFilter.

As you can see, we've created a custom AuthenticationEntryPoint, which basically just returns a 401 Unauthorized if the request wasn't authenticated in the filter chain by our 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." );
    }
}

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 包含一些隐私(并且非常特定于案例)代码并且不能轻易共享.这是它的界面:

Obviously, TokenUtils contains some privy (and very case-specific) code and can't be readily shared. Here's its interface:

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

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

That ought to get you off to a good start.

这篇关于通过 Spring 进行 RESTful 身份验证的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆