问题:
我们有一个基于Spring MVC的RESTful API,它包含敏感信息。应该保护API,但是不希望向每个请求发送用户的凭证(用户/通过组合)。根据REST准则(和内部业务要求),服务器必须保持无状态。该API将由mashup风格的方法由另一台服务器使用。
要求:
客户端使用凭据向.../authenticate
(未受保护的URL)发出请求;服务器返回一个安全令牌,其中包含足够的信息,供服务器验证将来的请求并保持无状态。这可能包含与Spring Security的Remember-Me Token相同的信息。
客户端对各种(受保护的)URL进行后续请求,将先前获取的令牌附加为查询参数(或者,不太希望的是HTTP请求头)。
不能指望客户存储Cookie。
由于我们已经使用Spring,因此该解决方案应该使用Spring Security。
我们一直在试图让这项工作碰壁,所以希望那里的人已经解决了这个问题。
鉴于上述情况,您如何解决这一特殊需求?
答案 0 :(得分:177)
我们设法完全按照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
,如果我们的401 Unauthorized
未在过滤器链中对请求进行身份验证,则基本上只返回{{1}}。 / p>
<强> CustomAuthenticationEntryPoint 强>:
AuthenticationTokenProcessingFilter
<强> 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." );
}
}
显然, 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
这应该让你有一个良好的开端。快乐的编码。 :)
答案 1 :(得分:22)
您可以考虑Digest Access Authentication。基本上协议如下:
所有这些通信都是通过标头进行的,正如jmort253所指出的那样,它通常比在url参数中传递敏感材料更安全。
Spring Security支持摘要访问身份验证。请注意,虽然文档说您必须能够访问客户端的纯文本密码,但您可以为您的客户successfully authenticate if you have the HA1 hash。
答案 2 :(得分:4)
关于携带信息的令牌,JSON Web令牌(http://jwt.io)是一项出色的技术。主要概念是将信息元素(声明)嵌入到令牌中,然后对整个令牌进行签名,以便验证端可以验证声明确实值得信任。
我使用这个Java实现:https://bitbucket.org/b_c/jose4j/wiki/Home
还有一个Spring模块(spring-security-jwt),但我没有研究它支持的内容。
答案 3 :(得分:1)
为什么不开始使用带有JSON WebTokens的OAuth
http://projects.spring.io/spring-security-oauth/
OAuth2是标准化的授权协议/框架。根据官方OAuth2 Specification:
您可以找到更多信息here