问题:
我们有一个包含敏感信息的基于Spring MVC的REST的API。 该API应该被固定,但是与每个请求发送用户的凭证(用户名/密码组合)是不可取的。 每REST原则(和内部业务需求),服务器必须保持无状态。 该API将通过在混搭风格的方法在另一台服务器消耗。
要求:
客户端发出的请求.../authenticate
与证书(无保护的URL); 服务器返回一个包含服务器来验证未来的请求,并保持无状态足够的信息安全令牌。 这可能包括相同的信息Spring Security的记住,我的令牌 。
客户端发出后续请求到各种(被保护)的URL,并附加先前获得的令牌作为查询参数(或不太优选的HTTP请求报头)。
客户不能指望存储cookie。
由于我们使用Spring已经,解决方案应利用春季安全。
我们一直在敲打我的头靠在墙上努力使这项工作,所以希望有人在那里已经解决了这个问题。
鉴于上述情况,你会如何解决这个特定的需求?
我们成功地得到这个工作完全一样在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
,如果请求没有在我们的过滤器链认证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
包含一些厕所(和非常特定的病例)码,并且不能被容易地共享。 下面是它的界面:
public interface TokenUtils {
String getToken(UserDetails userDetails);
String getToken(UserDetails userDetails, Long expiration);
boolean validate(String token);
UserDetails getUserFromToken(String token);
}
这应该让你有一个良好的开端。 编码愉快。 :)
你可能会考虑HTTP摘要认证 。 本质上,方案如下:
- 请求从客户端进行
- 服务器具有独特的随机数串做出响应
- 客户端提供用户名和密码(和一些其它值)MD5与随机数散列; 该散列被称为HA1
- 服务器就能够验证客户的身份和服务于所要求的材料
- 与现时的通信可以继续,直到该服务器提供了一个新的随机数(计数器是用来消除重放攻击)
所有这些通信是通过报头,其中,因为jmort253指出的那样,通常比在url参数传输敏感材料更安全制成。
HTTP摘要认证被支持的Spring Security 。 请注意,虽然文档说,你必须获得客户的明文密码,你可以,如果你有HA1哈希成功地验证了您的客户端。
关于携带信息的令牌,JSON网络令牌( http://jwt.io )是一个辉煌的技术。 的主要概念是嵌入的信息元素(权利要求)到令牌,然后签署全令牌,以便验证端可以验证该权利要求书的确值得信赖。
我用这个Java实现: https://bitbucket.org/b_c/jose4j/wiki/Home
还有一个弹簧组件(弹簧安全JWT),但我还没有研究什么支持。
你为什么不开始使用OAuth使用JSON WebTokens
http://projects.spring.io/spring-security-oauth/
OAuth2用户是一个标准化的授权协议/框架。 按照官方的OAuth2 规格 :
你可以找到更多的信息在这里