如何在 Spring 获取之前捕获 Spring Security 登录表单?
Posted
技术标签:
【中文标题】如何在 Spring 获取之前捕获 Spring Security 登录表单?【英文标题】:How to catch Spring Security login form before Spring gets it? 【发布时间】:2012-02-20 03:52:01 【问题描述】:我有一个带有用户名和密码的 Spring 登录表单,它指向 /myapp/j_spring_security_check。
我希望在 Spring 收到请求之前有人提交登录表单时拦截表单提交。
基本上,我希望能够审查用户输入,看看它是否满足某些要求。如果没有,应用程序会将用户带回登录表单。如果用户输入满足要求,则流程转到 Spring 认证。
我该怎么做?以一种有效的方式?
谢谢!
【问题讨论】:
这个链接可能对tejakantamneni.wordpress.com/2008/08/23/…有帮助 【参考方案1】:您可以使用常规的 Spring Security 功能来做到这一点。步骤是:
实现自定义WebAuthenticationDetailsSource
和WebAuthenticationDetails
。 WebAuthenticationDetails
将捕获您要验证的额外表单字段。
注意:在 Spring 3.0 中,您需要将 use a BeanPostProcessor to configure WebAuthenticationDetailsSource
转换为 UsernamePasswordAuthenticationFilter
。在 Spring 3.1 中,您可以直接在 <form-login>
命名空间配置中执行此操作。
实现自定义AuthenticationProvider
并在authenticate()
中检查WebAuthenticationDetails
,如果验证失败则抛出AuthenticationException
。在您的登录页面中检查此异常。
或者,您可以创建一个 Filter
来进行验证并将其添加到 Spring Security 过滤器之前。
【讨论】:
ericacm,非常感谢您的意见。我的意图是使用过滤器之类的东西。当请求到达 authenticate() 方法时,我没有有效的方法在字段下显示特定的错误消息。我可以从 authenticate() 中抛出异常,但这还不够具体。基本上,我想将登录表单视为其他 Spring-MVC,如果出现验证错误,则会在该字段下方显示一条消息。我尝试了过滤器方法,但似乎我无法将错误消息放在请求范围内。有什么想法吗?谢谢! 如果您使用第一种方法(步骤 1 和 2),那么您可以在登录页面中使用((AuthenticationException) session.getAttribute(AbstractAuthenticationProcessingFilter.SPRING_SECURITY_LAST_EXCEPTION_KEY)).getMessage()
收到错误消息。
当然你可以用Filter
做同样的事情,把异常放在会话中。
在我的测试中,AbstractAuthenticationProcessingFilter.SPRING_SECURITY_LAST_EXCEPTION_KEY 在会话中不存在。实际上,AbstractAuthenticationProcessingFilter.SPRING_SECURITY_LAST_EXCEPTION_KEY 的值是 SPRING_SECURITY_LAST_EXCEPTION,我通过 $sessionScope["SPRING_SECURITY_LAST_EXCEPTION"].message 从中获取会话消息。但是,此消息是一般消息,我没有优雅的方法可以知道哪个字段产生了身份验证错误。我错过了什么?非常感谢!
这个想法是您创建 AuthenticationExeception 的自定义子类,并在该异常中添加您想要的任何数据,例如导致验证失败的字段。然后,您可以在将异常拉出会话时获取该数据。【参考方案2】:
使用上面的答案作为指导,这是我创建的后处理器的工作示例,可让您指定要提供给身份验证器的登录表单变量,以及检查中的 terms_of_service 复选框值的示例自定义身份验证器登录表单。
在 Spring 配置中:
<bean id="authFormDetailsPostProcessor" class="com.sefaira.authauth.AuthFormDetailsPostProcessor">
<property name="formVarNames" value="terms_of_service_accepted"/>
</bean>
AuthFormDetailsPostProcessor.java:
public class AuthFormDetailsPostProcessor implements BeanPostProcessor
private String [] formVarNames;
public void setFormVarNames (String formVarNames)
this.formVarNames = formVarNames.split (",");
public static class Details extends WebAuthenticationDetails
private Map<String, String> map;
public Details (HttpServletRequest request, String [] parameters)
super (request);
this.map = new HashMap<String, String>();
for (String parameter : parameters)
this.map.put (parameter.trim(), request.getParameter (parameter.trim()));
public String get (String name)
return map.get(name);
public Object postProcessAfterInitialization(Object bean, String name)
if (bean instanceof UsernamePasswordAuthenticationFilter)
((UsernamePasswordAuthenticationFilter)bean).setAuthenticationDetailsSource(
new AuthenticationDetailsSource()
public Object buildDetails(Object context)
if (formVarNames == null)
throw new RuntimeException ("AuthFormDetailsPostProcessor bean requires a formVarNames property, specifying a comma-delimited list of form vars to provide in the details object.");
return new Details ((HttpServletRequest) context, formVarNames);
);
return bean;
public Object postProcessBeforeInitialization(Object bean, String name)
return bean;
这是使用它的自定义身份验证器:
public class AuthServiceAuthenticator implements AuthenticationProvider
@Override
public Authentication authenticate (Authentication authentication) throws AuthenticationException
String email = (String) authentication.getPrincipal();
String password = (String) authentication.getCredentials();
AuthFormDetailsPostProcessor.Details details = (AuthFormDetailsPostProcessor.Details) authentication.getDetails();
// see if they checked the terms_of_service checkbox
String termsOfServiceVar = details.get ("terms_of_service_accepted");
boolean termsOfServiceAccepted = (termsOfServiceVar != null && termsOfServiceVar.equals ("on"));
// ... do your custom authentication ...
return authentication; // or a new authentication object
@Override
public boolean supports(Class<? extends Object> authentication)
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
【讨论】:
以上是关于如何在 Spring 获取之前捕获 Spring Security 登录表单?的主要内容,如果未能解决你的问题,请参考以下文章
如何从 Validated Spring RestController 获取多个缺失请求参数的详细信息?
Spring 框架——利用HandlerExceptionResolver实现全局异常捕获