本文正在参加「Java主题月 - Java Debug笔记活动」,详情查看<活动链接>
如何通过Spring的RESTful身份验证
我们有一个基于Spring MVC的RESTful API,其中包含敏感信息。该API应该是安全的,但是不希望随每个请求一起发送用户凭证(用户/密码组合)。根据REST准则(和内部业务要求),服务器必须保持无状态。该API将由另一台服务器以mashup方式使用。
要求:
1,客户端发出请求以使用凭据对... /进行身份验证(不受保护的URL); 服务器返回一个安全令牌,该令牌包含足够的信息供服务器验证未来的请求并保持无状态。 这可能包含与Spring Security的Remember-Me令牌相同的信息。
2,客户端向各种(受保护的)URL发出后续请求,并将先前获得的令牌附加为查询参数(不太希望是HTTP请求头不能期望客户端存储cookie。)。
3,不希望客户端存储cookie。
4,由于我们已经使用了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>
复制代码
如您所见,我们创建了一个自定义AuthenticationEntryPoint,如果该请求未通过AuthenticationTokenProcessingFilter在过滤器链中进行身份验证,则它只会返回401 Unauthorized。
代码:
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包含一些私有的(并且非常区分大小写)代码,并且不容易共享。 这是它的接口:
public interface TokenUtils {
String getToken(UserDetails userDetails);
String getToken(UserDetails userDetails, Long expiration);
boolean validate(String token);
UserDetails getUserFromToken(String token);
}
复制代码