spring security 4検証コードを追加するコード例


spring securityは大きなモジュールであり、本明細書ではカスタムパラメータの認証のみに関するものである。spring securityのデフォルトの検証パラメータはusernameとpasswordだけで、一般的には足りないです。時間が長すぎて、忘れ物があります。もういいです。
spring及びspring securityの構成はjavaConfigを採用し、バージョンは順次4.2.5、4.0.4である。
全般的な考え方:カスタムEntryPoint、カスタムパラメータ拡張Authentication TokenおよびAuthentication Providerを追加して検証します。
まずEntryPointを定義します。

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class MyAuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint {
  public MyAuthenticationEntryPoint(String loginFormUrl) {
    super(loginFormUrl);
  }
  @Override
  public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
    super.commence(request, response, authException);
  }
}

次にtokenです。validCodeは検証コードパラメータです。

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
public class MyUsernamePasswordAuthenticationToken extends UsernamePasswordAuthenticationToken {
  private String validCode;
  public MyUsernamePasswordAuthenticationToken(String principal, String credentials, String validCode) {
    super(principal, credentials);
    this.validCode = validCode;
  }
  public String getValidCode() {
    return validCode;
  }
  public void setValidCode(String validCode) {
    this.validCode = validCode;
  }
}

引き続きProcessing Filter、

import com.core.shared.ValidateCodeHandle;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
public class MyValidCodeProcessingFilter extends AbstractAuthenticationProcessingFilter {
  private String usernameParam = "username";
  private String passwordParam = "password";
  private String validCodeParam = "validateCode";
  public MyValidCodeProcessingFilter() {
    super(new AntPathRequestMatcher("/user/login", "POST"));
  }

  @Override
  public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
    String username = request.getParameter(usernameParam);
    String password = request.getParameter(passwordParam);
    String validCode = request.getParameter(validCodeParam);
    valid(validCode, request.getSession());
    MyUsernamePasswordAuthenticationToken token = new MyUsernamePasswordAuthenticationToken(username, password, validCode);
    return this.getAuthenticationManager().authenticate(token);
  }

  public void valid(String validCode, HttpSession session) {
    if (validCode == null) {
      throw new ValidCodeErrorException("     !");
    }
    if (!ValidateCodeHandle.matchCode(session.getId(), validCode)) {
      throw new ValidCodeErrorException("     !");
    }
  }
}
それぞれ3つのパラメータを定義し、loginフォームからのパラメータを受信するために、構成方法はloginのurlを与え、post方式を必要とする。
続いて認証します。ここではまだ認証ユーザ名とパスワードがない場合、認証コードだけを認証しました。
以下はValidate CodeHandleツールクラスとValidCodeError Exceptionです。

import java.util.concurrent.ConcurrentHashMap;
public class ValidateCodeHandle {
  private static ConcurrentHashMap validateCode = new ConcurrentHashMap<>();
  public static ConcurrentHashMap getCode() {
    return validateCode;
  }

  public static void save(String sessionId, String code) {
    validateCode.put(sessionId, code);
  }

  public static String getValidateCode(String sessionId) {
    Object obj = validateCode.get(sessionId);
    if (obj != null) {
      return String.valueOf(obj);
    }
    return null;
  }

  public static boolean matchCode(String sessionId, String inputCode) {
    String saveCode = getValidateCode(sessionId);
    if (saveCode.equals(inputCode)) {
      return true;
    }
    return false;
  }
}
ここではAuthentication Exceptionを継承してsecurityの認証が失敗したことを示す必要があります。これで後続の失敗プロセスが進みます。

import org.springframework.security.core.AuthenticationException;
public class ValidCodeErrorException extends AuthenticationException {

  public ValidCodeErrorException(String msg) {
    super(msg);
  }
  public ValidCodeErrorException(String msg, Throwable t) {
    super(msg, t);
  }
}
次はProviderです。

import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
public class MyAuthenticationProvider extends DaoAuthenticationProvider {
  @Override
  public boolean supports(Class<?> authentication) {
    return MyUsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
  }

  @Override
  protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
    Object salt = null;
    if (getSaltSource() != null) {
      salt = getSaltSource().getSalt(userDetails);
    }
    if (authentication.getCredentials() == null) {
      logger.debug("Authentication failed: no credentials provided");
      throw new BadCredentialsException("        !");
    }
    String presentedPassword = authentication.getCredentials().toString();
    if (!this.getPasswordEncoder().isPasswordValid(userDetails.getPassword(), presentedPassword, salt)) {
      logger.debug("Authentication failed: password does not match stored value");

      throw new BadCredentialsException("        !");
    }

  }
}
この中でsupportsメソッドの指定はカスタムtokenを使って、additional Authentication Checks方法は親の論理と同じです。異常な戻り情報を変更しただけです。
次は認証の成功と認証の失敗を処理したhandlerです。

import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class FrontAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
  public FrontAuthenticationSuccessHandler(String defaultTargetUrl) {
    super(defaultTargetUrl);
  }

  @Override
  public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
    super.onAuthenticationSuccess(request, response, authentication);
  }
}

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class FrontAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
  public FrontAuthenticationFailureHandler(String defaultFailureUrl) {
    super(defaultFailureUrl);
  }

  @Override
  public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
    super.onAuthenticationFailure(request, response, exception);
  }
}
最後に最も重要なsecurity configです。

import com.service.user.CustomerService;
import com.web.filter.SiteMeshFilter;
import com.web.mySecurity.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.password.StandardPasswordEncoder;
import org.springframework.security.web.access.ExceptionTranslationFilter;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
import org.springframework.web.filter.CharacterEncodingFilter;
import javax.servlet.DispatcherType;
import javax.servlet.FilterRegistration;
import javax.servlet.ServletContext;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends AbstractSecurityWebApplicationInitializer {

  @Bean
  public PasswordEncoder passwordEncoder() {
    return new StandardPasswordEncoder("MD5");
  }

  @Autowired
  private CustomerService customerService;

  @Configuration
  @Order(1)
  public static class FrontendWebSecurityConfigureAdapter extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyValidCodeProcessingFilter myValidCodeProcessingFilter;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
      http.csrf().disable() 
          .authorizeRequests()
          .antMatchers("/user/login", "/user/logout").permitAll()
          .anyRequest().authenticated()
          .and()
          .addFilterBefore(myValidCodeProcessingFilter, UsernamePasswordAuthenticationFilter.class)
          .formLogin()
          .loginPage("/user/login")
          .and()
          .logout()
          .logoutUrl("/user/logout")
          .logoutSuccessUrl("/user/login");
    }

  }

  @Bean(name = "frontAuthenticationProvider")
  public MyAuthenticationProvider frontAuthenticationProvider() {
    MyAuthenticationProvider myAuthenticationProvider = new MyAuthenticationProvider();
    myAuthenticationProvider.setUserDetailsService(customerService);
    myAuthenticationProvider.setPasswordEncoder(passwordEncoder());
    return myAuthenticationProvider;
  }

  @Bean
  public AuthenticationManager authenticationManager() {
    List<AuthenticationProvider> list = new ArrayList<>();
    list.add(frontAuthenticationProvider());
    AuthenticationManager authenticationManager = new ProviderManager(list);
    return authenticationManager;
  }

  @Bean
  public MyValidCodeProcessingFilter myValidCodeProcessingFilter(AuthenticationManager authenticationManager) {
    MyValidCodeProcessingFilter filter = new MyValidCodeProcessingFilter();
    filter.setAuthenticationManager(authenticationManager);
    filter.setAuthenticationSuccessHandler(frontAuthenticationSuccessHandler());
    filter.setAuthenticationFailureHandler(frontAuthenticationFailureHandler());
    return filter;
  }

  @Bean
  public FrontAuthenticationFailureHandler frontAuthenticationFailureHandler() {
    return new FrontAuthenticationFailureHandler("/user/login");
  }

  @Bean
  public FrontAuthenticationSuccessHandler frontAuthenticationSuccessHandler() {
    return new FrontAuthenticationSuccessHandler("/front/test");
  }

  @Bean
  public MyAuthenticationEntryPoint myAuthenticationEntryPoint() {
    return new MyAuthenticationEntryPoint("/user/login");
  }
}
まず、暗号化クラスのbeanです。customerServiceは簡単な検索ユーザーです。

@Service("customerService")
public class CustomerServiceImpl implements CustomerService {

  @Autowired
  private UserDao userDao;

  @Override
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    return userDao.findCustomerByUsername(username);
  }
}
 次はFrontendWebSecurityConfigreAdapterです。configure方法を書き直して、csrfを無効にして、ライセンス要求authorize Requestsを開けます。その中に「/user/logurt」、「/user/logout」は権限検証を逃します。他の要求は登録認証を行います。私がカスタマイズしたmyValidCodeProcessigFilterをsecurityデフォルトのUsernamePassworld Authentication Filterに追加する前に、つまり私のカスタムパラメータ認証を行い、その後form Login()、設定登録urlと登録urlをして、登録urlはすべてcontrolマッピングを行う必要があります。
続いてAuthentication Provider、Authentication Manager、Processing Filter、Authentication Failure Handler、Authentication Success Handler、EntyPointのbeanが声明を表示します。
以下はlogin.jspです

<body>
<div class="login_div">
  <form:form autocomplete="false" commandName="userDTO" method="post">
    <div>
      <span class="error_tips"><b>${SPRING_SECURITY_LAST_EXCEPTION.message}</b></span>
    </div>
    username:<form:input path="username" cssClass="form-control"/><br/>
    password:<form:password path="password" cssClass="form-control"/><br/>
    validateCode:<form:input path="validateCode" cssClass="form-control"/>
    <label>${validate_code}</label>
    <div class="checkbox">
      <label>
        <input type="checkbox" name="remember-me"/>   
      </label>
    </div>
    <input type="submit" class="btn btn-primary" value="submit"/>
  </form:form>
</div>
</body>
 検証コードの検証に失敗した時に投げ出したのはValidCodeErrerExceptionです。Authentication Exceptionを継承していますので、securityは認証に失敗した時にAuthentication Exceptionに会ったらAuthentication Failure Handlerをトリガします。上記のbeanは認証失敗を登録urlにジャンプしたと宣言しています。SECURITY_ラスト.EXCEPTION.messageは私の認証を取得する時に異常情報を投げて、ユーザーに友好的にヒントを与えることができます。
カスタマイズされたsecurityの検証プロセスは終了しました。
以上が本文の全部です。皆さんの勉強に役に立つように、私たちを応援してください。