<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Spring Security 實戰(zhàn)干貨:從零手寫一個驗證碼登錄

          共 5718字,需瀏覽 12分鐘

           ·

          2020-07-29 02:32

          1. 前言

          前面關(guān)于Spring Security胖哥又寫了兩篇文章,分別圖文并茂地介紹了UsernamePasswordAuthenticationFilter和?AuthenticationManager。很多同學(xué)表示無法理解這兩個東西有什么用,能解決哪些實際問題?所以今天就對這兩篇理論進(jìn)行實戰(zhàn)運用,我們從零寫一個短信驗證碼登錄并適配到Spring Security體系中。如果你在閱讀中有什么疑問可以回頭看看這兩篇文章,能解決很多疑惑。

          當(dāng)然你可以修改成郵箱或者其它通訊設(shè)備的驗證碼登錄。

          2. 驗證碼生命周期

          驗證碼存在有效期,一般 5 分鐘。一般邏輯是用戶輸入手機號后去獲取驗證碼,服務(wù)端對驗證碼進(jìn)行緩存。在最大有效期內(nèi)用戶只能使用驗證碼驗證成功一次(避免驗證碼浪費);超過最大時間后失效。

          驗證碼的緩存生命周期:

          public?interface?CaptchaCacheStorage?{

          ????/**
          ?????*?驗證碼放入緩存.
          ?????*
          ?????*?@param?phone?the?phone
          ?????*?@return?the?string
          ?????*/

          ????String?put(String?phone);

          ????/**
          ?????*?從緩存取驗證碼.
          ?????*
          ?????*?@param?phone?the?phone
          ?????*?@return?the?string
          ?????*/

          ????String?get(String?phone);

          ????/**
          ?????*?驗證碼手動過期.
          ?????*
          ?????*?@param?phone?the?phone
          ?????*/

          ????void?expire(String?phone);
          }

          我們一般會借助于緩存中間件,比如Redis、Ehcache、Memcached等等來做這個事情。為了方便收看該教程的同學(xué)們所使用的不同的中間件。這里我結(jié)合Spring Cache特意抽象了驗證碼的緩存處理。

          private?static?final?String?SMS_CAPTCHA_CACHE?=?"captcha";
          @Bean
          CaptchaCacheStorage?captchaCacheStorage()?{
          ????return?new?CaptchaCacheStorage()?{

          ????????@CachePut(cacheNames?=?SMS_CAPTCHA_CACHE,?key?=?"#phone")
          ????????@Override
          ????????public?String?put(String?phone)?{
          ????????????return?RandomUtil.randomNumbers(5);
          ????????}

          ????????@Cacheable(cacheNames?=?SMS_CAPTCHA_CACHE,?key?=?"#phone")
          ????????@Override
          ????????public?String?get(String?phone)?{
          ????????????return?null;
          ????????}

          ????????@CacheEvict(cacheNames?=?SMS_CAPTCHA_CACHE,?key?=?"#phone")
          ????????@Override
          ????????public?void?expire(String?phone)?{

          ????????}
          ????};
          }

          務(wù)必保證緩存的可靠性,這與用戶的體驗息息相關(guān)。

          接著我們就來編寫和業(yè)務(wù)無關(guān)的驗證碼服務(wù)了,驗證碼服務(wù)的核心功能有兩個:發(fā)送驗證碼驗證碼校驗。其它的諸如統(tǒng)計、黑名單、歷史記錄可根據(jù)實際業(yè)務(wù)定制。這里只實現(xiàn)核心功能。

          /**
          ?*?驗證碼服務(wù).
          ?*?兩個功能:?發(fā)送和校驗.
          ?*
          ?*?@param?captchaCacheStorage?the?captcha?cache?storage
          ?*?@return?the?captcha?service
          ?*/

          @Bean
          public?CaptchaService?captchaService(CaptchaCacheStorage?captchaCacheStorage)?{
          ????return?new?CaptchaService()?{
          ????????@Override
          ????????public?boolean?sendCaptcha(String?phone)?{
          ????????????String?existed?=?captchaCacheStorage.get(phone);
          ????????????if?(StringUtils.hasText(existed))?{
          ????????????????//?節(jié)約成本的話如果緩存中有當(dāng)前手機可用的驗證碼?不再發(fā)新的驗證碼
          ????????????????return?true;
          ????????????}
          ????????????//?生成驗證碼并放入緩存
          ????????????String?captchaCode?=?captchaCacheStorage.put(phone);
          ????????????log.info("captcha:?{}",?captchaCode);

          ????????????//todo?這里自行完善調(diào)用第三方短信服務(wù)發(fā)送驗證碼
          ????????????return?true;
          ????????}

          ????????@Override
          ????????public?boolean?verifyCaptcha(String?phone,?String?code)?{
          ????????????String?cacheCode?=?captchaCacheStorage.get(phone);

          ????????????if?(Objects.equals(cacheCode,?code))?{
          ????????????????//?驗證通過手動過期
          ????????????????captchaCacheStorage.expire(phone);
          ????????????????return?true;
          ????????????}
          ????????????return?false;
          ????????}
          ????};
          }

          接下來就可以根據(jù)CaptchaService編寫短信發(fā)送接口/captcha/{phone}了。

          @RestController
          @RequestMapping("/captcha")
          public?class?CaptchaController?{

          ????@Resource
          ????CaptchaService?captchaService;


          ????/**
          ?????*?模擬手機號發(fā)送驗證碼.
          ?????*
          ?????*?@param?phone?the?mobile
          ?????*?@return?the?rest
          ?????*/

          ????@GetMapping("/{phone}")
          ????public?Rest?captchaByMobile(@PathVariable?String?phone)?{
          ????????//todo 手機號?正則自行驗證

          ????????if?(captchaService.sendCaptcha(phone)){
          ????????????return?RestBody.ok("驗證碼發(fā)送成功");
          ????????}
          ????????return?RestBody.failure(-999,"驗證碼發(fā)送失敗");
          ????}

          }

          3. 集成到 Spring Security

          下面的教程就必須用到前兩篇介紹的知識了。我們要實現(xiàn)驗證碼登錄就必須定義一個Servlet Filter進(jìn)行處理。它的作用這里再重復(fù)一下:

          • 攔截短信登錄接口。
          • 獲取登錄參數(shù)并封裝為Authentication憑據(jù)。
          • 交給AuthenticationManager認(rèn)證。

          我們需要先定制AuthenticationAuthenticationManager

          3.1 驗證碼憑據(jù)

          Authentication在我看來就是一個載體,在未得到認(rèn)證之前它用來攜帶登錄的關(guān)鍵參數(shù),比如用戶名和密碼、驗證碼;在認(rèn)證成功后它攜帶用戶的信息和角色集。所以模仿UsernamePasswordAuthenticationToken?來實現(xiàn)一個CaptchaAuthenticationToken,去掉不必要的功能,抄就完事兒了:

          package?cn.felord.spring.security.captcha;

          import?org.springframework.security.authentication.AbstractAuthenticationToken;
          import?org.springframework.security.core.GrantedAuthority;
          import?org.springframework.security.core.SpringSecurityCoreVersion;

          import?java.util.Collection;

          /**
          ?*?驗證碼認(rèn)證憑據(jù).
          ?*?@author?felord.cn
          ?*/

          public?class?CaptchaAuthenticationToken?extends?AbstractAuthenticationToken?{

          ????private?static?final?long?serialVersionUID?=?SpringSecurityCoreVersion.SERIAL_VERSION_UID;

          ????private?final?Object?principal;
          ????private?String?captcha;

          ????/**
          ?????*?此構(gòu)造函數(shù)用來初始化未授信憑據(jù).
          ?????*
          ?????*?@param?principal???the?principal
          ?????*?@param?captcha?the?captcha
          ?????*?@see?CaptchaAuthenticationToken#CaptchaAuthenticationToken(Object,?String,?Collection)
          ?????*/

          ????public?CaptchaAuthenticationToken(Object?principal,?String?captcha)?{
          ????????super(null);
          ????????this.principal?=??principal;
          ????????this.captcha?=?captcha;
          ????????setAuthenticated(false);
          ????}

          ????/**
          ?????*?此構(gòu)造函數(shù)用來初始化授信憑據(jù).
          ?????*
          ?????*?@param?principal???????the?principal
          ?????*?@param?captcha?????the?captcha
          ?????*?@param?authorities?the?authorities
          ?????*?@see?CaptchaAuthenticationToken#CaptchaAuthenticationToken(Object,?String)
          ?????*/

          ????public?CaptchaAuthenticationToken(Object?principal,?String?captcha,
          ??????????????????????????????????????Collection?authorities)
          ?
          {
          ????????super(authorities);
          ????????this.principal?=?principal;
          ????????this.captcha?=?captcha;
          ????????super.setAuthenticated(true);?//?must?use?super,?as?we?override
          ????}

          ????public?Object?getCredentials()?{
          ????????return?this.captcha;
          ????}

          ????public?Object?getPrincipal()?{
          ????????return?this.principal;
          ????}

          ????public?void?setAuthenticated(boolean?isAuthenticated)?throws?IllegalArgumentException?{
          ????????if?(isAuthenticated)?{
          ????????????throw?new?IllegalArgumentException(
          ????????????????????"Cannot?set?this?token?to?trusted?-?use?constructor?which?takes?a?GrantedAuthority?list?instead");
          ????????}

          ????????super.setAuthenticated(false);
          ????}

          ????@Override
          ????public?void?eraseCredentials()?{
          ????????super.eraseCredentials();
          ????????captcha?=?null;
          ????}

          3.2 驗證碼認(rèn)證管理器

          我們還需要定制一個AuthenticationManager來對上面定義的憑據(jù)CaptchaAuthenticationToken進(jìn)行認(rèn)證處理。下面這張圖有必要再拿出來看一下:

          ProviderManager

          定義AuthenticationManager只需要定義其實現(xiàn)ProviderManager。而ProviderManager又需要依賴AuthenticationProvider。

          所以我們要實現(xiàn)一個專門處理CaptchaAuthenticationTokenAuthenticationProvider。AuthenticationProvider的流程是:

          1. CaptchaAuthenticationToken拿到手機號、驗證碼。
          2. 利用手機號從數(shù)據(jù)庫查詢用戶信息,并判斷用戶是否是有效用戶,實際上就是實現(xiàn)UserDetailsService接口
          3. 驗證碼校驗。
          4. 校驗成功則封裝授信的憑據(jù)。
          5. 校驗失敗拋出認(rèn)證異常。

          根據(jù)這個流程實現(xiàn)如下:

          package?cn.felord.spring.security.captcha;

          import?lombok.extern.slf4j.Slf4j;
          import?org.springframework.beans.factory.InitializingBean;
          import?org.springframework.context.MessageSource;
          import?org.springframework.context.MessageSourceAware;
          import?org.springframework.context.support.MessageSourceAccessor;
          import?org.springframework.security.authentication.AuthenticationProvider;
          import?org.springframework.security.authentication.BadCredentialsException;
          import?org.springframework.security.core.Authentication;
          import?org.springframework.security.core.AuthenticationException;
          import?org.springframework.security.core.GrantedAuthority;
          import?org.springframework.security.core.SpringSecurityMessageSource;
          import?org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
          import?org.springframework.security.core.authority.mapping.NullAuthoritiesMapper;
          import?org.springframework.security.core.userdetails.UserDetails;
          import?org.springframework.security.core.userdetails.UserDetailsService;
          import?org.springframework.util.Assert;

          import?java.util.Collection;
          import?java.util.Objects;

          /**
          ?*?驗證碼認(rèn)證器.
          ?*?@author?felord.cn
          ?*/

          @Slf4j
          public?class?CaptchaAuthenticationProvider?implements?AuthenticationProvider,?InitializingBean,?MessageSourceAware?{
          ????private?final?GrantedAuthoritiesMapper?authoritiesMapper?=?new?NullAuthoritiesMapper();
          ????private?final?UserDetailsService?userDetailsService;
          ????private?final?CaptchaService?captchaService;
          ????private?MessageSourceAccessor?messages?=?SpringSecurityMessageSource.getAccessor();

          ????/**
          ?????*?Instantiates?a?new?Captcha?authentication?provider.
          ?????*
          ?????*?@param?userDetailsService?the?user?details?service
          ?????*?@param?captchaService?????the?captcha?service
          ?????*/

          ????public?CaptchaAuthenticationProvider(UserDetailsService?userDetailsService,?CaptchaService?captchaService)?{
          ????????this.userDetailsService?=?userDetailsService;
          ????????this.captchaService?=?captchaService;
          ????}

          ????@Override
          ????public?Authentication?authenticate(Authentication?authentication)?throws?AuthenticationException?{
          ????????Assert.isInstanceOf(CaptchaAuthenticationToken.class,?authentication,
          ????????????????()?->?messages.getMessage(
          ????????????????????????"CaptchaAuthenticationProvider.onlySupports",
          ????????????????????????"Only?CaptchaAuthenticationToken?is?supported"));

          ????????CaptchaAuthenticationToken?unAuthenticationToken?=?(CaptchaAuthenticationToken)?authentication;

          ????????String?phone?=?unAuthenticationToken.getName();
          ????????String?rawCode?=?(String)?unAuthenticationToken.getCredentials();

          ????????UserDetails?userDetails?=?userDetailsService.loadUserByUsername(phone);

          ????????//?此處省略對UserDetails?的可用性?是否過期??是否鎖定?是否失效的檢驗??建議根據(jù)實際情況添加??或者在?UserDetailsService?的實現(xiàn)中處理
          ????????if?(Objects.isNull(userDetails))?{
          ????????????throw?new?BadCredentialsException("Bad?credentials");
          ????????}

          ????????//?驗證碼校驗
          ????????if?(captchaService.verifyCaptcha(phone,?rawCode))?{
          ????????????return?createSuccessAuthentication(authentication,?userDetails);
          ????????}?else?{
          ????????????throw?new?BadCredentialsException("captcha?is?not?matched");
          ????????}

          ????}

          ????@Override
          ????public?boolean?supports(Class?authentication)?{
          ????????return?CaptchaAuthenticationToken.class.isAssignableFrom(authentication);
          ????}

          ????@Override
          ????public?void?afterPropertiesSet()?throws?Exception?{
          ????????Assert.notNull(userDetailsService,?"userDetailsService?must?not?be?null");
          ????????Assert.notNull(captchaService,?"captchaService?must?not?be?null");
          ????}

          ????@Override
          ????public?void?setMessageSource(MessageSource?messageSource)?{
          ????????this.messages?=?new?MessageSourceAccessor(messageSource);
          ????}

          ????/**
          ?????*?認(rèn)證成功將非授信憑據(jù)轉(zhuǎn)為授信憑據(jù).
          ?????*?封裝用戶信息?角色信息。
          ?????*
          ?????*?@param?authentication?the?authentication
          ?????*?@param?user???????????the?user
          ?????*?@return?the?authentication
          ?????*/

          ????protected?Authentication?createSuccessAuthentication(Authentication?authentication,?UserDetails?user)?{

          ????????Collection?authorities?=?authoritiesMapper.mapAuthorities(user.getAuthorities());
          ????????CaptchaAuthenticationToken?authenticationToken?=?new?CaptchaAuthenticationToken(user,?null,?authorities);
          ????????authenticationToken.setDetails(authentication.getDetails());

          ????????return?authenticationToken;
          ????}

          }

          然后就可以組裝ProviderManager了:

          ProviderManager?providerManager?=?new?ProviderManager(Collections.singletonList(captchaAuthenticationProvider));

          經(jīng)過3.13.2的準(zhǔn)備,我們的準(zhǔn)備工作就完成了。

          3.3 驗證碼認(rèn)證過濾器

          定制好驗證碼憑據(jù)和驗證碼認(rèn)證管理器后我們就可以定義驗證碼認(rèn)證過濾器了。修改一下UsernamePasswordAuthenticationFilter就能滿足需求:

          package?cn.felord.spring.security.captcha;

          import?org.springframework.lang.Nullable;
          import?org.springframework.security.authentication.AuthenticationServiceException;
          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.http.HttpServletRequest;
          import?javax.servlet.http.HttpServletResponse;

          public?class?CaptchaAuthenticationFilter?extends?AbstractAuthenticationProcessingFilter?{


          ????public?static?final?String?SPRING_SECURITY_FORM_PHONE_KEY?=?"phone";
          ????public?static?final?String?SPRING_SECURITY_FORM_CAPTCHA_KEY?=?"captcha";


          ????public?CaptchaAuthenticationFilter()?{
          ????????super(new?AntPathRequestMatcher("/clogin",?"POST"));
          ????}

          ????public?Authentication?attemptAuthentication(HttpServletRequest?request,
          ????????????????????????????????????????????????HttpServletResponse?response)
          ?throws?AuthenticationException?
          {

          ????????if?(!request.getMethod().equals("POST"))?{
          ????????????throw?new?AuthenticationServiceException(
          ????????????????????"Authentication?method?not?supported:?"?+?request.getMethod());
          ????????}

          ????????String?phone?=?obtainPhone(request);
          ????????String?captcha?=?obtainCaptcha(request);

          ????????if?(phone?==?null)?{
          ????????????phone?=?"";
          ????????}

          ????????if?(captcha?==?null)?{
          ????????????captcha?=?"";
          ????????}

          ????????phone?=?phone.trim();

          ????????CaptchaAuthenticationToken?authRequest?=?new?CaptchaAuthenticationToken(
          ????????????????phone,?captcha);

          ????????//?Allow?subclasses?to?set?the?"details"?property
          ????????setDetails(request,?authRequest);

          ????????return?this.getAuthenticationManager().authenticate(authRequest);
          ????}

          ????@Nullable
          ????protected?String?obtainCaptcha(HttpServletRequest?request)?{
          ????????return?request.getParameter(SPRING_SECURITY_FORM_CAPTCHA_KEY);
          ????}

          ????@Nullable
          ????protected?String?obtainPhone(HttpServletRequest?request)?{
          ????????return?request.getParameter(SPRING_SECURITY_FORM_PHONE_KEY);
          ????}

          ????protected?void?setDetails(HttpServletRequest?request,
          ??????????????????????????????CaptchaAuthenticationToken?authRequest)
          ?
          {
          ????????authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
          ????}

          }

          這里我們指定了攔截驗證碼登陸的請求為:

          POST /clogin?phone=手機號&captcha=驗證碼 HTTP/1.1
          Host: localhost:8082

          接下來就是配置了。

          3.4 配置

          我把所有的驗證碼認(rèn)證的相關(guān)配置集中了起來,并加上了注釋。

          package?cn.felord.spring.security.captcha;

          import?cn.hutool.core.util.RandomUtil;
          import?lombok.extern.slf4j.Slf4j;
          import?org.springframework.beans.factory.annotation.Qualifier;
          import?org.springframework.cache.annotation.CacheEvict;
          import?org.springframework.cache.annotation.CachePut;
          import?org.springframework.cache.annotation.Cacheable;
          import?org.springframework.context.annotation.Bean;
          import?org.springframework.context.annotation.Configuration;
          import?org.springframework.security.authentication.ProviderManager;
          import?org.springframework.security.core.authority.AuthorityUtils;
          import?org.springframework.security.core.userdetails.User;
          import?org.springframework.security.core.userdetails.UserDetailsService;
          import?org.springframework.security.web.authentication.AuthenticationFailureHandler;
          import?org.springframework.security.web.authentication.AuthenticationSuccessHandler;
          import?org.springframework.util.StringUtils;

          import?java.util.Collections;
          import?java.util.Objects;

          /**
          ?*?驗證碼認(rèn)證配置.
          ?*
          ?*?@author?felord.cn
          ?*?@since?13?:23
          ?*/

          @Slf4j
          @Configuration
          public?class?CaptchaAuthenticationConfiguration?{
          ????private?static?final?String?SMS_CAPTCHA_CACHE?=?"captcha";

          ????/**
          ?????*?spring?cache?管理驗證碼的生命周期.
          ?????*
          ?????*?@return?the?captcha?cache?storage
          ?????*/

          ????@Bean
          ????CaptchaCacheStorage?captchaCacheStorage()?{
          ????????return?new?CaptchaCacheStorage()?{

          ????????????@CachePut(cacheNames?=?SMS_CAPTCHA_CACHE,?key?=?"#phone")
          ????????????@Override
          ????????????public?String?put(String?phone)?{
          ????????????????return?RandomUtil.randomNumbers(5);
          ????????????}

          ????????????@Cacheable(cacheNames?=?SMS_CAPTCHA_CACHE,?key?=?"#phone")
          ????????????@Override
          ????????????public?String?get(String?phone)?{
          ????????????????return?null;
          ????????????}

          ????????????@CacheEvict(cacheNames?=?SMS_CAPTCHA_CACHE,?key?=?"#phone")
          ????????????@Override
          ????????????public?void?expire(String?phone)?{

          ????????????}
          ????????};
          ????}

          ????/**
          ?????*?驗證碼服務(wù).
          ?????*?兩個功能:?發(fā)送和校驗.
          ?????*
          ?????*?@param?captchaCacheStorage?the?captcha?cache?storage
          ?????*?@return?the?captcha?service
          ?????*/

          ????@Bean
          ????public?CaptchaService?captchaService(CaptchaCacheStorage?captchaCacheStorage)?{
          ????????return?new?CaptchaService()?{
          ????????????@Override
          ????????????public?boolean?sendCaptcha(String?phone)?{
          ????????????????String?existed?=?captchaCacheStorage.get(phone);
          ????????????????if?(StringUtils.hasText(existed))?{
          ????????????????????//?節(jié)約成本的話如果緩存存在可用的驗證碼?不再發(fā)新的驗證碼
          ????????????????????log.warn("captcha?code?【?{}?】?is?available?now",?existed);
          ????????????????????return?false;
          ????????????????}
          ????????????????//?生成驗證碼并放入緩存
          ????????????????String?captchaCode?=?captchaCacheStorage.put(phone);
          ????????????????log.info("captcha:?{}",?captchaCode);

          ????????????????//todo?這里自行完善調(diào)用第三方短信服務(wù)
          ????????????????return?true;
          ????????????}

          ????????????@Override
          ????????????public?boolean?verifyCaptcha(String?phone,?String?code)?{
          ????????????????String?cacheCode?=?captchaCacheStorage.get(phone);

          ????????????????if?(Objects.equals(cacheCode,?code))?{
          ????????????????????//?驗證通過手動過期
          ????????????????????captchaCacheStorage.expire(phone);
          ????????????????????return?true;
          ????????????????}
          ????????????????return?false;
          ????????????}
          ????????};
          ????}

          ????/**
          ?????*?自行實現(xiàn)根據(jù)手機號查詢可用的用戶,這里簡單舉例.
          ?????*?注意該接口可能出現(xiàn)多態(tài)。所以最好加上注解@Qualifier
          ?????*
          ?????*?@return?the?user?details?service
          ?????*/

          ????@Bean
          ????@Qualifier("captchaUserDetailsService")
          ????public?UserDetailsService?captchaUserDetailsService()?{
          ????????//?驗證碼登陸后密碼無意義了但是需要填充一下
          ????????return?username?->?User.withUsername(username).password("TEMP")
          ????????????????//todo??這里權(quán)限?你需要自己注入
          ????????????????.authorities(AuthorityUtils.createAuthorityList("ROLE_ADMIN",?"ROLE_APP")).build();
          ????}

          ????/**
          ?????*?驗證碼認(rèn)證器.
          ?????*
          ?????*?@param?captchaService?????the?captcha?service
          ?????*?@param?userDetailsService?the?user?details?service
          ?????*?@return?the?captcha?authentication?provider
          ?????*/

          ????@Bean
          ????public?CaptchaAuthenticationProvider?captchaAuthenticationProvider(CaptchaService?captchaService,
          ???????????????????????????????????????????????????????????????????????@Qualifier("captchaUserDetailsService")

          ???????????????????????????????????????????????????????????????????????????????UserDetailsService?userDetailsService)?
          {
          ????????return?new?CaptchaAuthenticationProvider(userDetailsService,?captchaService);
          ????}


          ????/**
          ?????*?驗證碼認(rèn)證過濾器.
          ?????*
          ?????*?@param?authenticationSuccessHandler??the?authentication?success?handler
          ?????*?@param?authenticationFailureHandler??the?authentication?failure?handler
          ?????*?@param?captchaAuthenticationProvider?the?captcha?authentication?provider
          ?????*?@return?the?captcha?authentication?filter
          ?????*/

          ????@Bean
          ????public?CaptchaAuthenticationFilter?captchaAuthenticationFilter(AuthenticationSuccessHandler?authenticationSuccessHandler,
          ???????????????????????????????????????????????????????????????????AuthenticationFailureHandler?authenticationFailureHandler,
          ???????????????????????????????????????????????????????????????????CaptchaAuthenticationProvider?captchaAuthenticationProvider)
          ?
          {
          ????????CaptchaAuthenticationFilter?captchaAuthenticationFilter?=?new?CaptchaAuthenticationFilter();
          ????????//?配置?authenticationManager
          ????????ProviderManager?providerManager?=?new?ProviderManager(Collections.singletonList(captchaAuthenticationProvider));
          ????????captchaAuthenticationFilter.setAuthenticationManager(providerManager);
          ????????//?成功處理器
          ????????captchaAuthenticationFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler);
          ????????//?失敗處理器
          ????????captchaAuthenticationFilter.setAuthenticationFailureHandler(authenticationFailureHandler);

          ????????return?captchaAuthenticationFilter;
          ????}
          }

          然而這并沒有完,你需要將CaptchaAuthenticationFilter配置到整個Spring Security的過濾器鏈中,這種看了胖哥教程的同學(xué)應(yīng)該非常熟悉了。

          配置驗證碼認(rèn)證過濾器到WebSecurityConfigurerAdapter中

          **請?zhí)貏e注意:**務(wù)必保證登錄接口和驗證碼接口可以匿名訪問,如果是動態(tài)權(quán)限可以給接口添加?ROLE_ANONYMOUS?角色。

          大功告成,測試如下:

          模擬驗證碼登錄

          而且原先的登錄方式不受影響,它們可以并存。

          4. 總結(jié)

          通過對UsernamePasswordAuthenticationFilter和?AuthenticationManager的系統(tǒng)學(xué)習(xí),我們了解了Spring Security認(rèn)證的整個流程,本文是對這兩篇的一個實際運用。相信看到這一篇后你就不會對前幾篇的圖解懵逼了,這也是理論到實踐的一次嘗試。

          本文DEMO 可以通過關(guān)注下方公眾號:碼農(nóng)小胖哥?

          回復(fù)captcha?獲取,如果有用還請關(guān)注、點贊、轉(zhuǎn)發(fā)給胖哥一個創(chuàng)作的動力。

          往期推薦

          為什么國內(nèi)流行的 MyBatis ,國外 Java 工程師卻不愿意使用?

          盤點開發(fā)中那些常用的MySQL優(yōu)化

          Spring 中 @Component、@Service 等注解如何被解析?

          Redis 的內(nèi)存淘汰策略問題

          Redis 6.0 除了多線程,別忘了這個牛逼特性!

          JDK 14 里的調(diào)試神器了解一下?


          瀏覽 52
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  青青草成人免费自拍视频 | 婷婷色亚洲| 人人干天天做 | 亚洲欧美国产精品久久久久久久 | 躁躁躁日日躁 |