如何在 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 功能来做到这一点。步骤是:

    实现自定义WebAuthenticationDetailsSourceWebAuthenticationDetailsWebAuthenticationDetails 将捕获您要验证的额外表单字段。

    注意:在 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 中捕获大量 Jackson 异常?

Spring 框架——利用HandlerExceptionResolver实现全局异常捕获

java spring boot在启动之前从db中获取值

Spring Boot - 如何在不使用 spring 注释的情况下在运行时获取端口

GraphQL - 如何捕获“内部”Apollo/GraphQL 错误 - Java Spring Boot