VerifyCodeFilter.java

package cn.home1.oss.lib.security.internal;

import static cn.home1.oss.lib.security.internal.preauth.PreAuthTokenFilter.ATTR_PRINCIPAL;
import static cn.home1.oss.lib.security.starter.FormAuthConfiguration.FORM_AUTHENTICATION_ENTRYPOINT;
import static org.apache.commons.lang3.StringUtils.isBlank;

import cn.home1.oss.lib.security.api.GenericUser;
import cn.home1.oss.lib.security.api.Security;
import cn.home1.oss.lib.security.api.VerifyCodeProvider;
import cn.home1.oss.lib.webmvc.api.RequestResolver;

import lombok.NonNull;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.env.Environment;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.web.filter.GenericFilterBean;

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * TODO Note: Not a bean, avoid auto pick-up.
 * before UsernamePasswordAuthenticationFilter.
 *
 * <p>
 * Created by zhanghaolun on 16/7/14.
 * </p>
 */
@Setter
@Slf4j
public class VerifyCodeFilter extends GenericFilterBean {

  /**
   * will be invoked when authentication fails. Typically an instance of {@link
   * org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint}.
   */
  @Qualifier(FORM_AUTHENTICATION_ENTRYPOINT)
  @Autowired
  @NonNull
  private AuthenticationEntryPoint formAuthenticationEntryPoint;

  @Autowired(required = false)
  @NonNull
  private VerifyCodeProvider codeVerifyProvider;

  @Autowired
  @NonNull
  private RequestResolver requestResolver;

  @Override
  public void doFilter( //
    final ServletRequest req, final ServletResponse res, final FilterChain chain //
  ) throws IOException, ServletException {
    final HttpServletRequest request = (HttpServletRequest) req;
    final HttpServletResponse response = (HttpServletResponse) res;

    try {
      if (this.authenticationIsRequired(request)) {
        final GenericUser principal = (GenericUser) request.getAttribute(ATTR_PRINCIPAL);
        this.authenticate(request, principal);
      }
    } catch (final AuthenticationException failed) {
      SecurityContextHolder.clearContext();

      if (log.isTraceEnabled()) {
        log.trace("AUTH authentication failed. not login.", failed);
      }

      this.formAuthenticationEntryPoint.commence(request, response, failed);
      return;
    }

    chain.doFilter(request, response);
  }

  private boolean authenticationIsRequired(final HttpServletRequest request) {
    // TODO fix this, 有context-path的情况下可能不对
    return Security.authenticationIsRequired() && this.requestResolver.isLoginRequest(request);
  }

  private void authenticate( //
    final HttpServletRequest request, //
    final GenericUser principal //
  ) throws AuthenticationException {
    final String code = request.getParameter("verifycode");
    if (isBlank(code)) {
      throw new BadCredentialsException("verifycode is required");
    }
    final String uuid = principal.getUuid();
    final Boolean match = this.codeVerifyProvider.match(uuid, code);
    if (!match) {
      throw new BadCredentialsException("verifycode not match");
    }
  }

  public String getCodeUrl() {
    return this.codeVerifyProvider.getCodeUrl();
  }

  @Autowired
  @Override
  public void setEnvironment(final Environment environment) {
    super.setEnvironment(environment);
  }
}