问题: 我们有一个基于 Spring MVC 的 RESTful API,其中包含敏感信息。API 应该是安全的,但是在每个请求中都发送用户的凭据(用户/密码组合)是不可取的。根据 REST 准则(和内部业务要求),服务器必须保持无状态。该 API 将由另一台服务器以 mashup 风格的方式使用。
要求:
.../authenticate客户端使用凭据向(不受保护的 URL)发出请求;服务器返回一个安全令牌,其中包含足够的信息供服务器验证未来的请求并保持无状态。这可能包含与 Spring Security 的Remember-Me Token相同的信息。
.../authenticate
客户端向各种(受保护的)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>
如您所见,我们创建了一个 custom AuthenticationEntryPoint,401 Unauthorized如果请求没有在过滤器链中被我们的AuthenticationTokenProcessingFilter.
AuthenticationEntryPoint
401 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 包含一些私密(并且非常特定于案例)代码并且不能轻易共享。这是它的界面:
TokenUtils
public interface TokenUtils { String getToken(UserDetails userDetails); String getToken(UserDetails userDetails, Long expiration); boolean validate(String token); UserDetails getUserFromToken(String token); }
这应该让你有一个好的开始。