English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

spring securityでカスタム認証ログインの全過程を記録

spring securityの使用方法カテゴリ:

spring securityの使用方法については、百度で検索したことがある人なら誰でも知っていると思います、全ての使用方法は以下の4つで、シンプルから複雑に並んでいます:

1、データベースを使用せず、すべてのデータを設定ファイルに書きます、これは公式ドキュメントのデモです;

2、データベースを使用し、spring securityのデフォルトの実装コードに基づいてデータベースを設計します、つまりデータベースは固定されています、この方法は柔軟性に欠け、非常にシンプルなデザインで、実用性が低いです;

3、spring securityとAcegiとは異なり、デフォルトのフィルタを修正することはできませんが、フィルタを挿入することをサポートしていますので、これに基づいて自分のフィルタを挿入して柔軟に使用することができます;

4、強制手段、ソースコードの修正、先ほどのデフォルトフィルタの修正は設定ファイルを変更してフィルタを置き換えるだけで、これはソースコードを直接修正するものであり、OO設計原則に反し、実際には使えません。

本文ではspring securityのカスタム認証ログインに関する内容を紹介し、皆様の参考と学習のために共有します。それでは、詳細な紹介に移りましょう。

1.概要

1.1.概要

spring securityはSpring AOPとServletフィルタの安全フレームワークで、これを使用して権限認証などを管理します。

1.2.spring securityのカスタム認証プロセス

1)認証プロセス

未認証のAuthenticationTokenの生成                 

 ↑(情報取得)  (AuthenticationTokenに基づいてproviderを割り当てます)     
 AuthenticationFilter -> AuthenticationManager -> AuthenticationProvider
        ↓(認証)
       UserDetails(一般的にはデータベースから取得)
        ↓(通過)
        認証成功のAuthenticationTokenの生成
         ↓(保存)
        SecurityContextHolder

2)AuthenticationFilterをsecurityフィルタリングチェーン(リソースサーバー内の設定)に追加します、例えば:

http.addFilterBefore(AuthenticationFilter, AbstractPreAuthenticatedProcessingFilter.class)

または:

http.addFilterAfter(AuthenticationFilter, UsernamePasswordAuthenticationFilter.class)

2.以手机号短信登录为例

2.1.开发环境

  • SpringBoot
  • Spring security
  • Redis

2.2.核心代码分析

2.2.1.自定义登录认证流程

2.2.1.1.自定义认证登录Token

/**
 * 手机登录Token
 *
 * @author : CatalpaFlat
 */
public class MobileLoginAuthenticationToken extends AbstractAuthenticationToken {
 private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
 private static final Logger logger = LoggerFactory.getLogger(MobileLoginAuthenticationToken.class.getName());
 private final Object principal;
 public MobileLoginAuthenticationToken(String mobile) {
 super(null);
 this.principal = mobile;
 this.setAuthenticated(false);
 logger.info("MobileLoginAuthenticationToken setAuthenticated ->false loading ...
 }
 public MobileLoginAuthenticationToken(Object principal,
      Collection<? extends GrantedAuthority> authorities) {
 super(authorities);
 this.principal = principal;
 // must use super, as we override
 super.setAuthenticated(true);
 logger.info("MobileLoginAuthenticationToken setAuthenticated ->true loading ...
 }
 @Override
 public void setAuthenticated(boolean authenticated) {}}
 if (authenticated) {
  throw new IllegalArgumentException(
   "このトークンを信頼できるに設定することはできません" - use constructor which takes a GrantedAuthority list instead");
 }
 super.setAuthenticated(false);
 }
 @Override
 public Object getCredentials() {
 return null;
 }
 @Override
 public Object getPrincipal() {
 return this.principal;
 }
 @Override
 public void eraseCredentials() {
 super.eraseCredentials();
 }
}

注:

setAuthenticated():認証済みかどうかを判断

  • フィルタで、認証未済みのAuthenticationTokenが生成されます。この時、カスタムトークンのsetAuthenticated()が呼び出されます。この時、falseに設定されます。 -> 認証未済み
  • プロバイダーで、認証済みのAuthenticationTokenが生成されます。この時、親クラスのsetAuthenticated()が呼び出されます。この時、trueに設定されます。 -> 認証済み

2.2.1.1.カスタム認証ログインフィルタ

/**
 * 携帯電話短信ログインフィルタ
 *
 * @author : CatalpaFlat
 */
public class MobileLoginAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
 private boolean postOnly = true;
 private static final Logger logger = LoggerFactory.getLogger(MobileLoginAuthenticationFilter.class.getName());
 @Getter
 @Setter
 private String mobileParameterName;
 public MobileLoginAuthenticationFilter(String mobileLoginUrl, String mobileParameterName,
      String httpMethod) {}}
 super(new AntPathRequestMatcher(mobileLoginUrl, httpMethod));
 this.mobileParameterName = mobileParameterName;
 logger.info("MobileLoginAuthenticationFilterのロード中...");
 }
 @Override
 public Authentication attemptAuthentication(HttpServletRequest request,      HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
 if (postOnly && !request.getMethod().equals(HttpMethod.POST.name())) {
  throw new AuthenticationServiceException("認証メソッドがサポートされていません: " + request.getMethod());
 }
 //モバイルを取得します
 String mobile = obtainMobile(request);
 //トークンを構築します
 MobileLoginAuthenticationToken authRequest = new MobileLoginAuthenticationToken(mobile);
 // サブクラスに「details」プロパティを設定させることを許可します
 setDetails(request, authRequest);
 return this.getAuthenticationManager().authenticate(authRequest);
 }
 /**
 * 認証の詳細情報を設定します
 */
 private void setDetails(HttpServletRequest request, MobileLoginAuthenticationToken authRequest) {
 authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
 }
 /**
 * 携帯電話番号を取得する
 */
 private String obtainMobile(HttpServletRequest request) {
 return request.getParameter(mobileParameterName);
 }
 public void setPostOnly(boolean postOnly) {
 this.postOnly = postOnly;
 }
}

注:attemptAuthentication()メソッド:

  • 指定されたURL、httpMethodをフィルタリングする
  • 必要なリクエストパラメータデータをパッケージ化して未認証のAuthenticationTokenを生成する
  • AuthenticationManagerに認証を渡す

2.2.1.1.カスタム認証ログインプロバイダ

/**
 * 携帯電話短信ログイン認証プロバイダ
 *
 * @author : CatalpaFlat
 */
public class MobileLoginAuthenticationProvider implements AuthenticationProvider {
 private static final Logger logger = LoggerFactory.getLogger(MobileLoginAuthenticationProvider.class.getName());
 @Getter
 @Setter
 private UserDetailsService customUserDetailsService;
 public MobileLoginAuthenticationProvider() {
 logger.info("MobileLoginAuthenticationProvider loading ...");
 }
 /**
 * 認証
 */
 @Override
 public Authentication authenticate(Authentication authentication) throws AuthenticationException {
 //フィルターレイヤーが包装したtoken情報を取得
 MobileLoginAuthenticationToken authenticationToken = (MobileLoginAuthenticationToken) authentication;
 //ユーザー情報(データベース認証)を取得する
 UserDetails userDetails = customUserDetailsService.loadUserByUsername((String) authenticationToken.getPrincipal());
 //通過しません
 if (userDetails == null) {
  throw new InternalAuthenticationServiceException("ユーザー情報の取得ができません");
 }
 //通過
 MobileLoginAuthenticationToken authenticationResult = new MobileLoginAuthenticationToken(userDetails, userDetails.getAuthorities());
 authenticationResult.setDetails(authenticationToken.getDetails());
 return authenticationResult;
 }
 /**
 * tokenのタイプに応じて、どのProviderを使用するかを判断します
 */
 @Override
 public boolean supports(Class<?> authentication) {
 return MobileLoginAuthenticationToken.class.isAssignableFrom(authentication);
 }
}

注:authenticate()メソッド

  • フィルターレイヤーが包装したtoken情報を取得
  • UserDetailsServiceを呼び出してユーザー情報を取得(データベース認証)->通過かどうかの判断
  • 通過すれば新しいAuthenticationTokenを包装し、返却します

2.2.1.1.カスタム認証ログイン認証設定

@Configuration(SpringBeanNameConstant.DEFAULT_CUSTOM_MOBILE_LOGIN_AUTHENTICATION_SECURITY_CONFIG_BN)
public class MobileLoginAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
 private static final Logger logger = LoggerFactory.getLogger(MobileLoginAuthenticationSecurityConfig.class.getName());
 @Value("${login.mobile.url}")
 private String defaultMobileLoginUrl;
 @Value("${login.mobile.parameter}")
 private String defaultMobileLoginParameter;
 @Value("${login.mobile.httpMethod}")
 private String defaultMobileLoginHttpMethod;
 @Autowired
 private CustomYmlConfig customYmlConfig;
 @Autowired
 private UserDetailsService customUserDetailsService;
 @Autowired
 private AuthenticationSuccessHandler customAuthenticationSuccessHandler;
 @Autowired
 private AuthenticationFailureHandler customAuthenticationFailureHandler;
 public MobileLoginAuthenticationSecurityConfig() {
 logger.info("MobileLoginAuthenticationSecurityConfig loading ...");
 }
 @Override
 public void configure(HttpSecurity http) throws Exception {
 MobilePOJO mobile = customYmlConfig.getLogins().getMobile();
 String url = mobile.getUrl();
 String parameter = mobile.getParameter().getMobile();
 String httpMethod = mobile.getHttpMethod();
 MobileLoginAuthenticationFilter mobileLoginAuthenticationFilter = new MobileLoginAuthenticationFilter(StringUtils.isBlank(url) ? defaultMobileLoginUrl : url,
  StringUtils.isBlank(parameter) ? defaultMobileLoginUrl : parameter, StringUtils.isBlank(httpMethod) ? defaultMobileLoginHttpMethod : httpMethod); mobileLoginAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class)); mobileLoginAuthenticationFilter.setAuthenticationSuccessHandler(customAuthenticationSuccessHandler); mobileLoginAuthenticationFilter.setAuthenticationFailureHandler(customAuthenticationFailureHandler);
 MobileLoginAuthenticationProvider mobileLoginAuthenticationProvider = new MobileLoginAuthenticationProvider(); mobileLoginAuthenticationProvider.setCustomUserDetailsService(customUserDetailsService);
 http.authenticationProvider(mobileLoginAuthenticationProvider)
  .addFilterAfter(mobileLoginAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
 }
}

注:configure()メソッド

AuthenticationFilterおよびAuthenticationProviderをインスタンス化します。

AuthenticationFilterおよびAuthenticationProviderをSpring Securityに追加します。

2.2.2.Redisに基づくカスタム验证码確認

2.2.2.1.Redisに基づくカスタム验证码フィルタ

/**
 * 验证码过滤器
 *
 * @author : CatalpaFlat
 */
@Component(SpringBeanNameConstant.DEFAULT_VALIDATE_CODE_FILTER_BN)
public class ValidateCodeFilter extends OncePerRequestFilter implements InitializingBean {
 private static final Logger logger = LoggerFactory.getLogger(ValidateCodeFilter.class.getName());
 @Autowired
 private CustomYmlConfig customYmlConfig;
 @Autowired
 private RedisTemplate<Object, Object> redisTemplate;
 /**
  * URLと設定のURLが一致するか確認するユーティリティクラス
  */
 private AntPathMatcher pathMatcher = new AntPathMatcher();
 public ValidateCodeFilter() {
  logger.info("Loading ValidateCodeFilter...");
 }
 @Override
 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
         FilterChain filterChain) throws ServletException, IOException {
  String url = customYmlConfig.getLogins().getMobile().getUrl();
  if (pathMatcher.match(url, request.getRequestURI())) {}}
   String deviceId = request.getHeader("deviceId");
   if (StringUtils.isBlank(deviceId)) {
    throw new CustomException(HttpStatus.NOT_ACCEPTABLE.value(), "リクエストのヘッダーにdeviceIdが含まれていません");
   }
   String codeParamName = customYmlConfig.getLogins().getMobile().getParameter().getCode();
   String code = request.getParameter(codeParamName);
   if (StringUtils.isBlank(code)) {
    throw new CustomException(HttpStatus.NOT_ACCEPTABLE.value(), "リクエストのパラメータにコードが含まれていません");
   }
   String key = SystemConstant.DEFAULT_MOBILE_KEY_PIX + deviceId;
   SmsCodePO smsCodePo = (SmsCodePO) redisTemplate.opsForValue().get(key);
   if (smsCodePo.isExpried()){
    throw new CustomException(HttpStatus.BAD_REQUEST.value(), "認証コードが有効期限切れです");
   }
   String smsCode = smsCodePo.getCode();
   if (StringUtils.isBlank(smsCode)) {
    throw new CustomException(HttpStatus.BAD_REQUEST.value(), "認証コードが存在しません");
   }
   if (StringUtils.equals(code, smsCode)) {}}
    redisTemplate.delete(key);
    //let it go
    filterChain.doFilter(request, response);
   } else {
    throw new CustomException(HttpStatus.BAD_REQUEST.value(), "Validation code is incorrect");
   }
  } else {
   //let it go
   filterChain.doFilter(request, response);
  }
 }
}

注:doFilterInternal()

カスタム認証コードフィルタの確認

2.2.2.2.カスタム認証コードフィルタをspring security フィルターチェーンに追加します

http.addFilterBefore(validateCodeFilter, AbstractPreAuthenticatedProcessingFilter.class)

注:認証プレプロセッションレイヤーに追加する前に

3.テスト効果

最後にソースコードのアドレスを付記します:https://gitee.com/CatalpaFlat/springSecurity.git  (ローカルダウンロード

まとめ

これでこの記事の全ての内容が終わりました。本文の内容が皆様の学習や仕事に参考になることを願っています。何かご不明な点があれば、コメントを残してください。皆様の「呐喊教程」へのサポートに感謝します。

声明:本文の内容はインターネットから収集され、著作権者に帰属します。インターネットユーザーが自発的に貢献し、アップロードした内容であり、本サイトは所有権を持ちません。また、人間による編集は行われていません。著作権侵害の疑いがある場合は、メールで notice#w までお知らせください。3codebox.com(メール送信時は、#を@に変更してください。不正行為の通報を行い、関連する証拠を提供してください。一旦確認がとりたいとされると、当サイトは直ちに侵害される内容を削除します。)

おすすめ