我有一个 Spring MVC 应用程序,它使用 Spring Security 和基于表单的登录进行授权/身份验证。
现在我想添加一个特殊的 URL,其中包含一个无需其他信息即可访问的令牌,因为该令牌对用户来说是唯一的:
http://myserver.com/special/5f6be0c0-87d7-11e2-9e96-0800200c9a66/text.pdf
如何配置 Spring Security 以使用该令牌进行用户身份验证?
我有一个 Spring MVC 应用程序,它使用 Spring Security 和基于表单的登录进行授权/身份验证。
现在我想添加一个特殊的 URL,其中包含一个无需其他信息即可访问的令牌,因为该令牌对用户来说是唯一的:
http://myserver.com/special/5f6be0c0-87d7-11e2-9e96-0800200c9a66/text.pdf
如何配置 Spring Security 以使用该令牌进行用户身份验证?
您可以提供自定义 PreAuthenticatedProcessingFilter 和 PreAuthenticatedAuthenticationProvider。有关详细信息,请参阅预身份验证方案一章。
您需要定义您的自定义预授权过滤器。
在http
标签内的安全应用上下文中:
<custom-filter position="PRE_AUTH_FILTER" ref="preAuthTokenFilter" />
然后定义您的过滤器bean(及其适当的属性):
<beans:bean class="com.yourcompany.PreAuthTokenFilter"
id="preAuthTokenFilter">
<beans:property name="authenticationDetailsSource" ref="authenticationDetailsSource" />
<beans:property name="authenticationManager" ref="authenticationManager" />
<beans:property name="authenticationEntryPoint" ref="authenticationEntryPoint"/>
</beans:bean>
创建从 GenericFilterBean 扩展的自定义过滤器
public class PreAuthTokenFilter extends GenericFilterBean {
private AuthenticationEntryPoint authenticationEntryPoint;
private AuthenticationManager authenticationManager;
private AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource();
@Override
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
String token = getTokenFromHeader(request);//your method
if (StringUtils.isNotEmpty(token)) {
/* get user entity from DB by token, retrieve its username and password*/
if (isUserTokenValid(/* some args */)) {
try {
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
Authentication authResult = this.authenticationManager.authenticate(authRequest);
SecurityContextHolder.getContext().setAuthentication(authResult);
} catch (AuthenticationException e) {
}
}
}
chain.doFilter(request, response);
}
/*
other methods
*/
If you don't want or cannot retrieve a password, you need to create your own AbstractAuthenticationToken
which will receive only username as param (principal) and use it instead of UsernamePasswordAuthenticationToken
:
public class PreAuthToken extends AbstractAuthenticationToken {
private final Object principal;
public PreAuthToken(Object principal) {
super(null);
super.setAuthenticated(true);
this.principal = principal;
}
@Override
public Object getCredentials() {
return "";
}
@Override
public Object getPrincipal() {
return principal;
}
}
我遇到了这个问题,并使用 Spring Security RembereMe Service 基础架构的自定义实现解决了它。这是您需要做的。
定义自己的 Authentication 对象
公共类 LinkAuthentication 扩展 AbstractAuthenticationToken { @Override public Object getCredentials() { return null; }
@Override
public Object getPrincipal()
{
return the prncipal that that is passed in via the constructor
}
}
定义
public class LinkRememberMeService implements RememberMeServices, LogoutHandler
{
/**
* It might appear that once this method is called and returns an authentication object, that authentication should be finished and the
* request should proceed. However, spring security does not work that way.
*
* Once this method returns a non null authentication object, spring security still wants to run it through its authentication provider
* which, is totally brain dead on the part of Spring this, is why there is also a
* LinkAuthenticationProvider
*
*/
@Override
public Authentication autoLogin(HttpServletRequest request, HttpServletResponse response)
{
String accessUrl = ServletUtils.getApplicationUrl(request, "/special/");
String requestUrl = request.getRequestURL().toString();
if (requestUrl.startsWith(accessUrl))
{
// take appart the url extract the token, find the user details object
// and return it.
LinkAuthentication linkAuthentication = new LinkAuthentication(userDetailsInstance);
return linkAuthentication;
} else
{
return null;
}
}
@Override
public void loginFail(HttpServletRequest request, HttpServletResponse response)
{
}
@Override
public void loginSuccess(HttpServletRequest request, HttpServletResponse response, Authentication successfulAuthentication)
{
}
@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
{
}
}
public class LinkAuthenticationProvider implements AuthenticationProvider
{
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException
{
// Spring Security is totally brain dead and over engineered
return authentication;
}
@Override
public boolean supports(Class<?> authentication)
{
return LinkAuthentication.class.isAssignableFrom(authentication);
}
}
破解其余的 spring security xml 以定义自定义身份验证提供程序和自定义记住我服务。
PS,如果您在 URL 中对 GUID 进行 base64 编码,它将缩短几个字符。您可以使用 Apache commons codec base64 二进制编码器/解码器来做更安全的 url 链接。
public static String toBase64Url(UUID uuid)
{
return Base64.encodeBase64URLSafeString(toBytes(uuid));
}