Spring Security実戦乾物:カスタムログインをプレイする


1.はじめに
前のSpring Securityに関する記事は予熱にすぎない.次のより良い実戦のために、もしあなたが逃したらSpring Security実戦シリーズから始めてください.セキュリティ・アクセスの最初のステップは認証(Authentication)、認証の最初のステップはログインです.今日はSpring Securityのカスタマイズにより、拡張性と伸縮性のあるformログイン機能を設計します.
2.formログインの流れ
次はformログインの基本手順です.
formログインであれば基本的に上のフローに変換できます.次にSpring Securityがどのように処理されているかを見てみましょう.
3.Spring Securityでのログイン
昨日Spring Security実戦乾物:カスタム構成クラスエントリWebSecurityConfigurerAdapterでは、通常のカスタムアクセス制御は主にHttpSecurityによって構築されていると述べています.デフォルトでは、次の3つのログイン方法があります.
  • formLogin()一般フォーム登録
  • oauth2Login() OAuth2.0 openidLogin()認証/認証プロトコル
  • に基づく.
  • OpenID AbstractAuthenticationFilterConfigurerアイデンティティ認証仕様
  • に基づく.
    以上の3つの方式はすべてHttpSecurityで実現され、
    4.HttpSecurityでのformフォームログイン
    フォーム登録を有効にするには、2つの方法があります.1つは、apply(C configurer)AbstractAuthenticationFilterConfigurer方法によって、比較的高度な遊び方であるHttpSecurityの実装を独自に構築することです.もう1つは、formLogin()FormLoginConfigurerメソッドを使用してloginPage(String loginPage)をカスタマイズすることです.まず普通の2つ目をやってみましょう.
    4.1 FormLoginConfigurer
    このクラスはformフォームログインの構成クラスです.一般的な構成方法をいくつか提供します.
  • /login:インタフェースではなくページにログインし、前後の分離モードを変更する必要があります.デフォルトはloginProcessingUrl(String loginProcessingUrl) です.
  • Action実際のテーブルは、ユーザ情報のUsernamePasswordAuthenticationFilterを一方的にバックグラウンドに送信し、フィルタActionによってブロック処理され、このusernameParameter(String usernameParameter)は実際には論理を処理しない.
  • usernameユーザーパラメータ名をカスタマイズするために使用されます.デフォルトはpasswordParameter(String passwordParameter)です.
  • passwordユーザーパスワード名をカスタマイズするために使用されます.デフォルトはfailureUrl(String authenticationFailureUrl)
  • です.
  • failureForwardUrl(String forwardUrl)ログインに失敗すると、このパスにリダイレクトされ、一般的に前後の分離では使用されません.
  • Controllerログインに失敗すると、これに転送され、一般的には前後に分離して使用されます.戻り値を処理するためにRequestMethod(コントローラ)を定義できますが、defaultSuccessUrl(String defaultSuccessUrl, boolean alwaysUse)に注意してください.
  • alwaysUseデフォルトログイン成功後にここにジャンプし、truefalseであれば認証プロセスが成功すれば、ずっとここにジャンプします.一般推奨デフォルト値successForwardUrl(String forwardUrl)
  • defaultSuccessUrlの効果は、上のalwaysUsetrueと同等ですが、RequestMethodに注意してください.
  • successHandler(AuthenticationSuccessHandler successHandler)カスタム認証成功プロセッサは、上記のすべてのsuccess方式
  • に代わることができる.
  • failureHandler(AuthenticationFailureHandler authenticationFailureHandler)カスタム失敗成功プロセッサは、上記のすべてのsuccess方式
  • に代わることができる.
  • permitAll(boolean permitAll) formフォーム登録
  • を開放するかどうか
    これらを知ったら、カスタマイズされたログインをすることができます.
    5.Spring Security集約登録実戦
    次は私たちが最も感動的な実戦ログイン操作です.Spring実戦の一連の予熱文章を真剣に読むことができるのは疑問だ.
    5.1シンプルなニーズ
    我々のインタフェースアクセスは認証に合格し,ログインエラー後にエラー情報(json)を返し,成功後フロントで対応するデータベースユーザ情報(json)を取得することができる(実戦では脱敏を覚えている).
    成功に失敗したコントローラを定義します.
     @RestController
     @RequestMapping("/login")
     public class LoginController {
         @Resource
         private SysUserService sysUserService;
     
         /**
          *        401       .
          *
          * @return the rest
          */
         @PostMapping("/failure")
         public Rest loginFailure() {
     
             return RestBody.failure(HttpStatus.UNAUTHORIZED.value(), "     ,  ");
         }
     
         /**
          *            .
          *
          * @return the rest
          */
         @PostMapping("/success")
         public Rest loginSuccess() {
               //              UserDetails             SecurityContextHolder  
             User principal = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
             String username = principal.getUsername();
             SysUser sysUser = sysUserService.queryByUsername(username);
             //   
             sysUser.setEncodePassword("[PROTECT]");
             return RestBody.okData(sysUser,"    ");
         }
     }

    次に、カスタム構成上書きvoid configure(HttpSecurity http)メソッドを使用して、crsfを無効にする必要があります.
     @Configuration
     @ConditionalOnClass(WebSecurityConfigurerAdapter.class)
     @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
     public class CustomSpringBootWebSecurityConfiguration {
     
         @Configuration
         @Order(SecurityProperties.BASIC_AUTH_ORDER)
         static class DefaultConfigurerAdapter extends WebSecurityConfigurerAdapter {
             @Override
             protected void configure(AuthenticationManagerBuilder auth) throws Exception {
                 super.configure(auth);
             }
     
             @Override
             public void configure(WebSecurity web) throws Exception {
                 super.configure(web);
             }
     
             @Override
             protected void configure(HttpSecurity http) throws Exception {
                 http.csrf().disable()
                         .cors()
                         .and()
                         .authorizeRequests().anyRequest().authenticated()
                         .and()
                         .formLogin()
                         .loginProcessingUrl("/process")
                         .successForwardUrl("/login/success").
                         failureForwardUrl("/login/failure");
     
             }
         }
     }

    Postmanまたは他のツールを使用してPost方式のフォームを発行すると、http://localhost:8080/process?username=Felordcn&password=12345がユーザー情報を返します.
     {
         "httpStatus": 200,
         "data": {
             "userId": 1,
             "username": "Felordcn",
             "encodePassword": "[PROTECT]",
             "age": 18
         },
         "msg": "    ",
         "identifier": ""
     }

    パスワードを別の値に変更して認証を再要求できませんでした.
      {
          "httpStatus": 401,
          "data": null,
          "msg": "     ,  ",
          "identifier": "-9999"
      }

    6.多種の登録方式の簡単な実現
    これで終わりですか.現在は登録の種類が多い.通常はメール、メールボックス、スキャンコードがありますが、第三者は後で私が話したいのは今日の範囲内ではありません.アイデアの多いプロダクトマネージャにどう対応しますか?さまざまなポーズを拡張できるログイン方法を作りましょう.私たちは上の2.formログインのプロセスの中のユーザーと判定の間にアダプタを追加して適応すればいいです.この判定とはUsernamePasswordAuthenticationFilterであることを知っています.uriが上記に構成された/processであり、getParameter(String name)を介してユーザ名とパスワードを取得できることを保証するだけでよい.DelegatingPasswordEncoderのやり方を真似て、レジストリを維持して異なる処理戦略を実行できると思います.もちろん、GenericFilterBeanUsernamePasswordAuthenticationFilterの前に実行されることを実現します.同時にログインのポリシーを設定します.
    6.1ログイン方式の定義
    ログイン方式列挙``を定義します.
    
      public enum LoginTypeEnum {
      
          /**
           *       .
           */
          FORM,
          /**
           * Json   .
           */
          JSON,
          /**
           *    .
           */
          CAPTCHA
      
      }

    6.2プリプロセッサインタフェースの定義
    プリアンブルプロセッサインタフェースを定義して、受信した様々な特色のあるログインパラメータを処理し、具体的な論理を処理します.この言い訳は実は少し勝手で、重要なのはあなたが考えをマスターすることです.デフォルトのform' RequestBody json`の2つの方法を実現しましたが、ここでは紙幅制限は示しません.具体的なDEMOは下部を参照してください.
       public interface LoginPostProcessor {
       
       
       
           /**
            *        
            *
            * @return the type
            */
           LoginTypeEnum getLoginTypeEnum();
       
           /**
            *      
            *
            * @param request the request
            * @return the string
            */
           String obtainUsername(ServletRequest request);
       
           /**
            *     
            *
            * @param request the request
            * @return the string
            */
           String obtainPassword(ServletRequest request);
       
       }

    6.3登録前処理フィルタの実現
    このフィルタは、LoginPostProcessorマッピングテーブルを維持します.ログイン方式によるポリシー上の前処理は、フロントエンドで判定され、最終的にはUsernamePasswordAuthenticationFilterに渡される.HttpSecurityaddFilterBefore(preLoginFilter, UsernamePasswordAuthenticationFilter.class)法により前置を行った.
     package cn.felord.spring.security.filter;
     
     import cn.felord.spring.security.enumation.LoginTypeEnum;
     import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
     import org.springframework.security.web.util.matcher.RequestMatcher;
     import org.springframework.util.Assert;
     import org.springframework.util.CollectionUtils;
     import org.springframework.web.filter.GenericFilterBean;
     
     import javax.servlet.FilterChain;
     import javax.servlet.ServletException;
     import javax.servlet.ServletRequest;
     import javax.servlet.ServletResponse;
     import javax.servlet.http.HttpServletRequest;
     import java.io.IOException;
     import java.util.Collection;
     import java.util.HashMap;
     import java.util.Map;
     
     import static org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_PASSWORD_KEY;
     import static org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter.SPRING_SECURITY_FORM_USERNAME_KEY;
     
     /**
      *       
      *
      * @author Felordcn
      * @since 16 :21 2019/10/17
      */
     public class PreLoginFilter extends GenericFilterBean {
     
     
         private static final String LOGIN_TYPE_KEY = "login_type";
     
     
         private RequestMatcher requiresAuthenticationRequestMatcher;
         private Map processors = new HashMap<>();
     
     
         public PreLoginFilter(String loginProcessingUrl, Collection loginPostProcessors) {
             Assert.notNull(loginProcessingUrl, "loginProcessingUrl must not be null");
             requiresAuthenticationRequestMatcher = new AntPathRequestMatcher(loginProcessingUrl, "POST");
             LoginPostProcessor loginPostProcessor = defaultLoginPostProcessor();
             processors.put(loginPostProcessor.getLoginTypeEnum(), loginPostProcessor);
     
             if (!CollectionUtils.isEmpty(loginPostProcessors)) {
                 loginPostProcessors.forEach(element -> processors.put(element.getLoginTypeEnum(), element));
             }
     
         }
     
     
         private LoginTypeEnum getTypeFromReq(ServletRequest request) {
             String parameter = request.getParameter(LOGIN_TYPE_KEY);
     
             int i = Integer.parseInt(parameter);
             LoginTypeEnum[] values = LoginTypeEnum.values();
             return values[i];
         }
     
     
         /**
          *     Form .
          *
          * @return the login post processor
          */
         private LoginPostProcessor defaultLoginPostProcessor() {
             return new LoginPostProcessor() {
     
     
                 @Override
                 public LoginTypeEnum getLoginTypeEnum() {
     
                     return LoginTypeEnum.FORM;
                 }
     
                 @Override
                 public String obtainUsername(ServletRequest request) {
                     return request.getParameter(SPRING_SECURITY_FORM_USERNAME_KEY);
                 }
     
                 @Override
                 public String obtainPassword(ServletRequest request) {
                     return request.getParameter(SPRING_SECURITY_FORM_PASSWORD_KEY);
                 }
             };
         }
     
     
         @Override
         public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
             ParameterRequestWrapper parameterRequestWrapper = new ParameterRequestWrapper((HttpServletRequest) request);
             if (requiresAuthenticationRequestMatcher.matches((HttpServletRequest) request)) {
     
                 LoginTypeEnum typeFromReq = getTypeFromReq(request);
     
                 LoginPostProcessor loginPostProcessor = processors.get(typeFromReq);
     
     
                 String username = loginPostProcessor.obtainUsername(request);
     
                 String password = loginPostProcessor.obtainPassword(request);
     
     
                 parameterRequestWrapper.setAttribute(SPRING_SECURITY_FORM_USERNAME_KEY, username);
                 parameterRequestWrapper.setAttribute(SPRING_SECURITY_FORM_PASSWORD_KEY, password);
     
             }
     
             chain.doFilter(parameterRequestWrapper, response);
     
     
         }
     }

    6.4検証POSTフォーム送信方式http://localhost:8080/process?username=Felordcn&password=12345&login_type=0により、成功を要求することができる.または、次の方法でコミットできます.
    より多くの方法は、インタフェースLoginPostProcessor注入PreLoginFilterを実現するだけである.
    7.まとめ
    今日,我々は各種技術の運用により,単純登録から動的拡張まで多様な方式が併存する実戦運用を実現した.あなたにとって大きな収穫があると信じています.今回のコードDEMOは、公衆番号に注目することで、Felordcnからss03に返信することができます.後はもっと素晴らしいです. :Felordcn
    個人ブログ:https://felord.cn