欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Spring Security 實現(xiàn)多種登錄方式(常規(guī)方式外的郵件、手機驗證碼登錄)

 更新時間:2022年01月10日 11:37:39   作者:寧在春  
本文主要介紹了Spring Security 實現(xiàn)多種登錄方式(常規(guī)方式外的郵件、手機驗證碼登錄),文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下

 不知道, 你在用Spring Security的時候,有沒有想過,用它實現(xiàn)多種登錄方式勒,這次我的小伙伴就給我提了一些登錄方面的需求,需要在原有賬號密碼登錄的基礎(chǔ)上,另外實現(xiàn)電話驗證碼以及郵件驗證碼登錄,以及在實現(xiàn)之后,讓我能夠做到實現(xiàn)第三方登錄,如gitee、github等。

本文主要是講解Security在實現(xiàn)賬號密碼的基礎(chǔ)上,并且不改變原有業(yè)務(wù)情況下,實現(xiàn)郵件、電話驗證碼登錄。

前言:

上一篇文章我寫了 Security登錄詳細流程詳解有源碼有分析。掌握這個登錄流程,我們才能更好的做Security的定制操作。

我在寫這篇文章之前,也看過很多博主的文章,寫的非常好,有對源碼方面的解析,也有對一些相關(guān)設(shè)計理念的理解的文章。

這對于已經(jīng)學過一段時間,并且對Security已經(jīng)有了解的小伙伴來說,還是比較合適的,但是對于我以及其他一些急于解決當下問題的小白,并不是那么友善。??

一、??‍♂?理論知識

我們先思考一下這個流程大致是如何的?

  • 填寫郵件號碼,獲取驗證碼
  • 輸入獲取到的驗證碼進行登錄(登錄的接口:/email/login,這里不能使用默認的/login,因為我們是擴展)
  • 在自定義的過濾器 EmailCodeAuthenticationFilter 中獲取發(fā)送過來的郵件號碼及驗證碼,判斷驗證碼是否正確,郵件賬號是否為空等
  • 封裝成一個需要認證的 Authentication ,此處我們自定義實現(xiàn)為 EmailCodeAuthenticationToken。
  • 將 Authentiction 傳給 AuthenticationManager 接口中 authenticate 方法進行認證處理
  • AuthenticationManager 默認是實現(xiàn)類為 ProviderManager ,ProviderManager 又委托給 AuthenticationProvider 進行處理
  • 我們自定義一個 EmailCodeAuthenticationProvider 實現(xiàn) AuthenticationProvider ,實現(xiàn)身份驗證。
  • 自定義的 EmailCodeAuthenticationFilter 繼承了 AbstractAuthenticationProcessingFilter 抽象類, AbstractAuthenticationProcessingFilter 在 successfulAuthentication 方法中對登錄成功進行了處理,通過 SecurityContextHolder.getContext().setAuthentication() 方法將 Authentication 認證信息對象綁定到 SecurityContext即安全上下文中。
  • 其實對于身份驗證通過后的處理,有兩種方案,一種是直接在過濾器重寫successfulAuthentication,另外一種就是實現(xiàn)AuthenticationSuccessHandler來處理身份驗證通過。
  • 身份驗證失敗也是一樣,可重寫unsuccessfulAuthentication方法,也可以實現(xiàn) AuthenticationFailureHandler來對身份驗證失敗進行處理。

大致流程就是如此。從這個流程中我們可以知道,需要重寫的組件有以下幾個:

  • EmailCodeAuthenticationFilter:郵件驗證登錄過濾器
  • EmailCodeAuthenticationToken:身份驗證令牌
  • EmailCodeAuthenticationProvider:郵件身份認證處理
  • AuthenticationSuccessHandler:處理登錄成功操作
  • AuthenticationFailureHandler:處理登錄失敗操作

接下來,我是模仿著源碼寫出我的代碼,建議大家可以在使用的時候,多去看看,我這里去除了一些不是和這個相關(guān)的代碼。

來吧!!

二、EmailCodeAuthenticationFilter

我們需要重寫的 EmailCodeAuthenticationFilter,實際繼承了AbstractAuthenticationProcessingFilter抽象類,我們不會寫,可以先看看它的默認實現(xiàn)UsernamePasswordAuthenticationFilter是怎么樣的嗎,抄作業(yè)這是大家的強項的哈。

public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
?? ?
?? ?public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";

?? ?public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";

?? ?private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/login",
?? ??? ??? ?"POST");
?? ?//從前臺傳過來的參數(shù)
?? ?private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY;

?? ?private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY;

?? ?private boolean postOnly = true;
?? ?
? ? // ?初始化一個用戶密碼 認證過濾器 ?默認的登錄uri 是 /login 請求方式是POST
?? ?public UsernamePasswordAuthenticationFilter() {
?? ??? ?super(DEFAULT_ANT_PATH_REQUEST_MATCHER);
?? ?}

?? ?public UsernamePasswordAuthenticationFilter(AuthenticationManager authenticationManager) {
?? ??? ?super(DEFAULT_ANT_PATH_REQUEST_MATCHER, authenticationManager);
?? ?}

? ? /**
? ? 執(zhí)行實際身份驗證。實現(xiàn)應(yīng)執(zhí)行以下操作之一:
?? ?1、為經(jīng)過身份驗證的用戶返回填充的身份驗證令牌,表示身份驗證成功
?? ?2、返回null,表示認證過程還在進行中。 在返回之前,實現(xiàn)應(yīng)該執(zhí)行完成流程所需的任何額外工作。
?? ?3、如果身份驗證過程失敗,則拋出AuthenticationException
? ? */
?? ?@Override
?? ?public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
?? ??? ??? ?throws AuthenticationException {
?? ??? ?if (this.postOnly && !request.getMethod().equals("POST")) {
?? ??? ??? ?throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
?? ??? ?}
?? ??? ?String username = obtainUsername(request);
?? ??? ?username = (username != null) ? username : "";
?? ??? ?username = username.trim();
?? ??? ?String password = obtainPassword(request);
?? ??? ?password = (password != null) ? password : "";
? ? ? ? //生成 UsernamePasswordAuthenticationToken 稍后交由AuthenticationManager中的authenticate進行認證
?? ??? ?UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
?? ??? ?// 可以放一些其他信息進去
?? ??? ?setDetails(request, authRequest);
?? ??? ?return this.getAuthenticationManager().authenticate(authRequest);
?? ?}

?? ?@Nullable
?? ?protected String obtainPassword(HttpServletRequest request) {
?? ??? ?return request.getParameter(this.passwordParameter);
?? ?}

?? ?@Nullable
?? ?protected String obtainUsername(HttpServletRequest request) {
?? ??? ?return request.getParameter(this.usernameParameter);
?? ?}

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

?? ?//set、get方法
}

接下來我們就抄個作業(yè)哈:

package com.crush.security.auth.email_code;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
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 org.springframework.security.web.util.matcher.RequestMatcher;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.ArrayList;

/**
 * @Author: crush
 * @Date: 2021-09-08 21:13
 * version 1.0
 */
public class EmailCodeAuthenticationFilter  extends AbstractAuthenticationProcessingFilter {
    /**
     * 前端傳來的 參數(shù)名 - 用于request.getParameter 獲取
     */
    private final String DEFAULT_EMAIL_NAME="email";

    private final String DEFAULT_EMAIL_CODE="e_code";

    @Autowired
    @Override
    public void setAuthenticationManager(AuthenticationManager authenticationManager) {
        super.setAuthenticationManager(authenticationManager);
    }
    /**
     * 是否 僅僅post方式
     */
    private boolean postOnly = true;

    /**
     * 通過 傳入的 參數(shù) 創(chuàng)建 匹配器
     * 即 Filter過濾的url
     */
    public EmailCodeAuthenticationFilter() {
        super(new AntPathRequestMatcher("/email/login","POST"));
    }


    /**
     * filter 獲得 用戶名(郵箱) 和 密碼(驗證碼) 裝配到 token 上 ,
     * 然后把token 交給 provider 進行授權(quán)
     */
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
        if(postOnly && !request.getMethod().equals("POST") ){
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }else{
            String email = getEmail(request);
            if(email == null){
                email = "";
            }
            email = email.trim();
            //如果 驗證碼不相等 故意讓token出錯 然后走springsecurity 錯誤的流程
            boolean flag = checkCode(request);
            //封裝 token
            EmailCodeAuthenticationToken token = new EmailCodeAuthenticationToken(email,new ArrayList<>());
            this.setDetails(request,token);
            //交給 manager 發(fā)證
            return this.getAuthenticationManager().authenticate(token);
        }
    }

    /**
     * 獲取 頭部信息 讓合適的provider 來驗證他
     */
    public void setDetails(HttpServletRequest request , EmailCodeAuthenticationToken token ){
        token.setDetails(this.authenticationDetailsSource.buildDetails(request));
    }

    /**
     * 獲取 傳來 的Email信息
     */
    public String getEmail(HttpServletRequest request ){
        String result=  request.getParameter(DEFAULT_EMAIL_NAME);
        return result;
    }

    /**
     * 判斷 傳來的 驗證碼信息 以及 session 中的驗證碼信息
     */
    public boolean checkCode(HttpServletRequest request ){
        String code1 = request.getParameter(DEFAULT_EMAIL_CODE);
        System.out.println("code1**********"+code1);
        // TODO 另外再寫一個鏈接 生成 驗證碼 那個驗證碼 在生成的時候  存進redis 中去
        //TODO  這里的驗證碼 寫在Redis中, 到時候取出來判斷即可 驗證之后 刪除驗證碼
        if(code1.equals("123456")){
            return true;
        }
        return false;
    }
	// set、get方法...
}

三、EmailCodeAuthenticationToken

我們EmailCodeAuthenticationToken是繼承AbstractAuthenticationToken的,按照同樣的方式,我們接著去看看AbstractAuthenticationToken的默認實現(xiàn)是什么樣的就行了。

/**

?*/
public class UsernamePasswordAuthenticationToken extends AbstractAuthenticationToken {

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

? ? // 這里指的賬號密碼哈
?? ?private final Object principal;

?? ?private Object credentials;

?? ?/**
?? ?沒經(jīng)過身份驗證時,初始化權(quán)限為空,setAuthenticated(false)設(shè)置為不可信令牌
?? ? */
?? ?public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
?? ??? ?super(null);
?? ??? ?this.principal = principal;
?? ??? ?this.credentials = credentials;
?? ??? ?setAuthenticated(false);
?? ?}

?? ?/**
?? ?經(jīng)過身份驗證后,將權(quán)限放進去,setAuthenticated(true)設(shè)置為可信令牌
?? ? */
?? ?public UsernamePasswordAuthenticationToken(Object principal, Object credentials,
?? ??? ??? ?Collection<? extends GrantedAuthority> authorities) {
?? ??? ?super(authorities);
?? ??? ?this.principal = principal;
?? ??? ?this.credentials = credentials;
?? ??? ?super.setAuthenticated(true); // must use super, as we override
?? ?}

?? ?@Override
?? ?public Object getCredentials() {
?? ??? ?return this.credentials;
?? ?}

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

?? ?@Override
?? ?public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
?? ??? ?Assert.isTrue(!isAuthenticated,
?? ??? ??? ??? ?"Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
?? ??? ?super.setAuthenticated(false);
?? ?}

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

}

日常抄作業(yè)哈:

/**
?* @Author: crush
?* @Date: 2021-09-08 21:13
?* version 1.0
?*/
public class EmailCodeAuthenticationToken extends AbstractAuthenticationToken {


? ? /**
? ? ?* 這里的 principal 指的是 email 地址(未認證的時候)
? ? ?*/
? ? private final Object principal;

? ? public EmailCodeAuthenticationToken(Object principal) {
? ? ? ? super((Collection) null);
? ? ? ? this.principal = principal;
? ? ? ? setAuthenticated(false);
? ? }

? ? public EmailCodeAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
? ? ? ? super(authorities);
? ? ? ? this.principal = principal;
? ? ? ? super.setAuthenticated(true);
? ? }

? ? @Override
? ? public Object getCredentials() {
? ? ? ? return null;
? ? }

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

? ? @Override
? ? 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");
? ? ? ? } else {
? ? ? ? ? ? super.setAuthenticated(false);
? ? ? ? }
? ? }

}

這個很簡單的哈

四、EmailCodeAuthenticationProvider

自定義的EmailCodeAuthenticationProvider是實現(xiàn)了AuthenticationProvider接口,抄作業(yè)就得學會看看源碼。我們接著來。

4.1、先看看AbstractUserDetailsAuthenticationProvider,我們再來模仿

AuthenticationProvider 接口有很多實現(xiàn)類,不一一說明了,直接看我們需要看的AbstractUserDetailsAuthenticationProvider, 該類旨在響應(yīng) UsernamePasswordAuthenticationToken 身份驗證請求。但是它是一個抽象類,但其實就一個步驟在它的實現(xiàn)類中實現(xiàn)的,很簡單,稍后會講到。

在這個源碼中我把和檢查相關(guān)的一些操作都給刪除,只留下幾個重點,我們一起來看一看哈。

//該類旨在響應(yīng)UsernamePasswordAuthenticationToken身份驗證請求。
public abstract class AbstractUserDetailsAuthenticationProvider
?? ??? ?implements AuthenticationProvider, InitializingBean, MessageSourceAware {

?? ?protected final Log logger = LogFactory.getLog(getClass());

?? ?private UserCache userCache = new NullUserCache();

?? ?@Override
?? ?public Authentication authenticate(Authentication authentication) throws AuthenticationException {
?? ??? ?Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
?? ??? ??? ??? ?() -> this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports",
?? ??? ??? ??? ??? ??? ?"Only UsernamePasswordAuthenticationToken is supported"));
? ? ? ? //獲取用戶名
?? ??? ?String username = determineUsername(authentication);
?? ??? ?//判斷緩存中是否存在
? ? ? ? boolean cacheWasUsed = true;
?? ??? ?UserDetails user = this.userCache.getUserFromCache(username);
?? ??? ?if (user == null) {
?? ??? ??? ?cacheWasUsed = false;
?? ??? ??? ?try {
? ? ? ? ? ? ? ? // 緩存中沒有 通過字類實現(xiàn)的retrieveUser 從數(shù)據(jù)庫進行檢索,返回一個 UserDetails 對象
?? ??? ??? ??? ?user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
?? ??? ??? ?}
?? ??? ??? ?catch (UsernameNotFoundException ex) {
?? ??? ??? ??? ?this.logger.debug("Failed to find user '" + username + "'");
?? ??? ??? ??? ?if (!this.hideUserNotFoundExceptions) {
?? ??? ??? ??? ??? ?throw ex;
?? ??? ??? ??? ?}
?? ??? ??? ??? ?throw new BadCredentialsException(this.messages
?? ??? ??? ??? ??? ??? ?.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
?? ??? ??? ?}
?? ??? ??? ?Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
?? ??? ?}
?? ??? ?try {
? ? ? ? ? ? //進行相關(guān)檢查 ?因為可能是從緩存中取出來的 并非是最新的
?? ??? ??? ?this.preAuthenticationChecks.check(user);
?? ??? ??? ?additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
?? ??? ?}
?? ??? ?catch (AuthenticationException ex) {
?? ??? ??? ?if (!cacheWasUsed) {
?? ??? ??? ??? ?throw ex;
?? ??? ??? ?}
? ? ? ? ? ? // 沒有通過檢查, 重新檢索最新的數(shù)據(jù)
?? ??? ??? ?cacheWasUsed = false;
?? ??? ??? ?user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
?? ??? ??? ?this.preAuthenticationChecks.check(user);
?? ??? ??? ?additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
?? ??? ?}
? ? ? ? // 再次進行檢查
?? ??? ?this.postAuthenticationChecks.check(user);
?? ??? ?// 存進緩存中去
? ? ? ? if (!cacheWasUsed) {
?? ??? ??? ?this.userCache.putUserInCache(user);
?? ??? ?}
?? ??? ?Object principalToReturn = user;
?? ??? ?if (this.forcePrincipalAsString) {
?? ??? ??? ?principalToReturn = user.getUsername();
?? ??? ?}
? ? ? ? //創(chuàng)建一個可信的身份令牌返回
?? ??? ?return createSuccessAuthentication(principalToReturn, authentication, user);
?? ?}

?? ?private String determineUsername(Authentication authentication) {
?? ??? ?return (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName();
?? ?}

?? ?/**
簡而言之就是創(chuàng)建了一個通過身份驗證的UsernamePasswordAuthenticationToken
?? ? */
?? ?protected Authentication createSuccessAuthentication(Object principal, Authentication authentication,
?? ??? ??? ?UserDetails user) {
?? ??? ?UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal,
?? ??? ??? ??? ?authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(user.getAuthorities()));
?? ??? ?result.setDetails(authentication.getDetails());
?? ??? ?this.logger.debug("Authenticated user");
?? ??? ?return result;
?? ?}


?? ?/**
允許子類從特定于實現(xiàn)的位置實際檢索UserDetails ,如果提供的憑據(jù)不正確,則可以選擇立即拋出AuthenticationException (如果需要以用戶身份綁定到資源以獲得或生成一個UserDetails )
?? ? */
?? ?protected abstract UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
?? ??? ??? ?throws AuthenticationException;
?? ?//...
?? ?

? ? //簡而言之:當然有時候我們有多個不同的 `AuthenticationProvider`,它們分別支持不同的 `Authentication`對象,那么當一個具體的 `AuthenticationProvier`傳進入 `ProviderManager`的內(nèi)部時,就會在 `AuthenticationProvider`列表中挑選其對應(yīng)支持的provider對相應(yīng)的 Authentication對象進行驗證
?? ?@Override
?? ?public boolean supports(Class<?> authentication) {
?? ??? ?return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
?? ?}

}

關(guān)于 protected abstract UserDetails retrieveUser 的實現(xiàn),AbstractUserDetailsAuthenticationProvider實現(xiàn)是DaoAuthenticationProvider.

DaoAuthenticationProvider主要操作是兩個,第一個是從數(shù)據(jù)庫中檢索出相關(guān)信息,第二個是給檢索出的用戶信息進行密碼的加密操作。

public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {

?? ?private UserDetailsService userDetailsService;
? ??
?? ?@Override
?? ?protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
?? ??? ??? ?throws AuthenticationException {
?? ??? ?prepareTimingAttackProtection();
?? ??? ?try {
? ? ? ? ? ? // 檢索用戶,一般我們都會實現(xiàn) UserDetailsService接口,改為從數(shù)據(jù)庫中檢索用戶信息 返回安全核心類 UserDetails
?? ??? ??? ?UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
?? ??? ??? ?if (loadedUser == null) {
?? ??? ??? ??? ?throw new InternalAuthenticationServiceException(
?? ??? ??? ??? ??? ??? ?"UserDetailsService returned null, which is an interface contract violation");
?? ??? ??? ?}
?? ??? ??? ?return loadedUser;
?? ??? ?}
?? ??? ?catch (UsernameNotFoundException ex) {
?? ??? ??? ?mitigateAgainstTimingAttack(authentication);
?? ??? ??? ?throw ex;
?? ??? ?}
?? ??? ?catch (InternalAuthenticationServiceException ex) {
?? ??? ??? ?throw ex;
?? ??? ?}
?? ??? ?catch (Exception ex) {
?? ??? ??? ?throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
?? ??? ?}
?? ?}

?? ?@Override
?? ?protected Authentication createSuccessAuthentication(Object principal, Authentication authentication,
?? ??? ??? ?UserDetails user) {
? ? ? ? // 判斷是否用了密碼加密 針對這個點 沒有深入 大家好奇可以去查一查這個知識點
?? ??? ?boolean upgradeEncoding = this.userDetailsPasswordService != null
?? ??? ??? ??? ?&& this.passwordEncoder.upgradeEncoding(user.getPassword());
?? ??? ?if (upgradeEncoding) {
?? ??? ??? ?String presentedPassword = authentication.getCredentials().toString();
?? ??? ??? ?String newPassword = this.passwordEncoder.encode(presentedPassword);
?? ??? ??? ?user = this.userDetailsPasswordService.updatePassword(user, newPassword);
?? ??? ?}
?? ??? ?return super.createSuccessAuthentication(principal, authentication, user);
?? ?}

}

4.2、抄作業(yè)啦

看完源碼,其實我們?nèi)绻貙懙脑?,主要要做到以下幾個事情:

重寫public boolean supports(Class<?> authentication)方法。

有時候我們有多個不同的 AuthenticationProvider,它們分別支持不同的 Authentication對象,那么當一個具體的 AuthenticationProvier 傳進入 ProviderManager的內(nèi)部時,就會在 AuthenticationProvider列表中挑選其對應(yīng)支持的 provider 對相應(yīng)的 Authentication對象進行驗證

簡單說就是指定AuthenticationProvider驗證哪個 Authentication 對象。如指定DaoAuthenticationProvider認證UsernamePasswordAuthenticationToken,

所以我們指定EmailCodeAuthenticationProvider認證EmailCodeAuthenticationToken。

檢索數(shù)據(jù)庫,返回一個安全核心類UserDetail。

創(chuàng)建一個經(jīng)過身份驗證的Authentication對象

了解要做什么事情了,我們就可以動手看看代碼啦。

/**
?* @Author: crush
?* @Date: 2021-09-08 21:14
?* version 1.0
?*/
@Slf4j
public class EmailCodeAuthenticationProvider implements AuthenticationProvider {

? ? ITbUserService userService;

? ? public EmailCodeAuthenticationProvider(ITbUserService userService) {
? ? ? ? this.userService = userService;
? ? }


? ? /**
? ? ?* 認證
? ? ?*/
? ? @Override
? ? public Authentication authenticate(Authentication authentication) throws AuthenticationException {
? ? ? ? if (!supports(authentication.getClass())) {
? ? ? ? ? ? return null;
? ? ? ? }
? ? ? ? log.info("EmailCodeAuthentication authentication request: %s", authentication);
? ? ? ? EmailCodeAuthenticationToken token = (EmailCodeAuthenticationToken) authentication;

? ? ? ? UserDetails user = userService.getByEmail((String) token.getPrincipal());

? ? ? ? System.out.println(token.getPrincipal());
? ? ? ? if (user == null) {
? ? ? ? ? ? throw new InternalAuthenticationServiceException("無法獲取用戶信息");
? ? ? ? }
? ? ? ? System.out.println(user.getAuthorities());
? ? ? ? EmailCodeAuthenticationToken result =
? ? ? ? ? ? ? ? new EmailCodeAuthenticationToken(user, user.getAuthorities());
? ? ? ? ? ? ? ? /*
? ? ? ? ? ? ? ? Details 中包含了 ip地址、 sessionId 等等屬性 也可以存儲一些自己想要放進去的內(nèi)容
? ? ? ? ? ? ? ? */
? ? ? ? result.setDetails(token.getDetails());
? ? ? ? return result;
? ? }

? ? @Override
? ? public boolean supports(Class<?> aClass) {
? ? ? ? return EmailCodeAuthenticationToken.class.isAssignableFrom(aClass);
? ? }
}

五、在配置類中進行配置

主要就是做下面幾件事:將過濾器、認證器注入到spring中
將登錄成功處理、登錄失敗處理器注入到Spring中,或者在自定義過濾器中對登錄成功和失敗進行處理。
添加到過濾鏈中

? ? @Bean
? ? public EmailCodeAuthenticationFilter emailCodeAuthenticationFilter() {
? ? ? ? EmailCodeAuthenticationFilter emailCodeAuthenticationFilter = new EmailCodeAuthenticationFilter();
? ? ? ? emailCodeAuthenticationFilter.setAuthenticationSuccessHandler(myAuthenticationSuccessHandler);
? ? ? ? emailCodeAuthenticationFilter.setAuthenticationFailureHandler(myAuthenticationFailureHandler);
? ? ? ? return emailCodeAuthenticationFilter;
? ? }

? ? @Bean
? ? public EmailCodeAuthenticationProvider emailCodeAuthenticationProvider() {
? ? ? ? return new EmailCodeAuthenticationProvider(userService);
? ? }

? ? /**
? ? ?* 因為使用了BCryptPasswordEncoder來進行密碼的加密,所以身份驗證的時候也的用他來判斷哈、,
? ? ?*
? ? ?* @param auth
? ? ?* @throws Exception
? ? ?*/
? ? @Override
? ? protected void configure(AuthenticationManagerBuilder auth) throws Exception {
? ? ? ? auth.userDetailsService(userDetailService).passwordEncoder(passwordEncoder());
? ? ? ? //authenticationProvider 根據(jù)傳入的自定義AuthenticationProvider添加身份AuthenticationProvider 。
? ? ? ? auth.authenticationProvider(emailCodeAuthenticationProvider());
? ? }
.and()
? ? .authenticationProvider(emailCodeAuthenticationProvider())
? ? .addFilterBefore(emailCodeAuthenticationFilter(),UsernamePasswordAuthenticationFilter.class)

? ? .authenticationProvider(mobileCodeAuthenticationProvider())
? ? .addFilterBefore(mobileCodeAuthenticationFilter(),UsernamePasswordAuthenticationFilter.class)

六、測試及源代碼

項目具體的配置、啟動方式、環(huán)境等、都在github及gitee的文檔上有詳細說明。

源代碼中包含sql文件、配置文件以及相關(guān)博客鏈接,源代碼中也加了很多注釋,盡最大程度讓大家能夠看明白。

在最大程度上保證大家都能正確的運行及測試。
源碼:gitee-Security

七、自言自語

如果這篇存在不太懂的內(nèi)容,可以先看我的另一篇文章:

SpringBoot集成Security實現(xiàn)安全控制,使用Jwt制作Token令牌。

之后再回過頭來看這一篇文章,應(yīng)該會更加容易理解。

到此這篇關(guān)于Spring Security 實現(xiàn)多種登錄方式(常規(guī)方式外的郵件、手機驗證碼登錄)的文章就介紹到這了,更多相關(guān)SpringSecurity 登錄 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • spring 整合mybatis后用不上session緩存的原因分析

    spring 整合mybatis后用不上session緩存的原因分析

    因為一直用spring整合了mybatis,所以很少用到mybatis的session緩存。什么原因呢?下面小編給大家介紹spring 整合mybatis后用不上session緩存的原因分析,需要的朋友可以參考下
    2017-02-02
  • 修改jvm-sandbox源碼導(dǎo)致線程安全分析

    修改jvm-sandbox源碼導(dǎo)致線程安全分析

    這篇文章主要為大家介紹了修改jvm-sandbox源碼導(dǎo)致線程安全分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-06-06
  • java中堆和棧的區(qū)別分析

    java中堆和棧的區(qū)別分析

    這篇文章主要介紹了java中堆和棧的區(qū)別,分析了Java中堆和棧的原理及使用時的注意事項,需要的朋友可以參考下
    2014-09-09
  • jackson 實現(xiàn)null轉(zhuǎn)0 以及0轉(zhuǎn)null的示例代碼

    jackson 實現(xiàn)null轉(zhuǎn)0 以及0轉(zhuǎn)null的示例代碼

    這篇文章主要介紹了jackson 實現(xiàn)null轉(zhuǎn)0 以及0轉(zhuǎn)null的示例代碼,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-09-09
  • Java中的Kotlin?內(nèi)部類原理

    Java中的Kotlin?內(nèi)部類原理

    這篇文章主要介紹了Java中的Kotlin?內(nèi)部類原理,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,感興趣的小伙伴可以參考一下
    2022-06-06
  • SpringBoot @FixMethodOrder 如何調(diào)整單元測試順序

    SpringBoot @FixMethodOrder 如何調(diào)整單元測試順序

    這篇文章主要介紹了SpringBoot @FixMethodOrder 調(diào)整單元測試順序方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • SpringBoot 自定義+動態(tài)切換數(shù)據(jù)源教程

    SpringBoot 自定義+動態(tài)切換數(shù)據(jù)源教程

    這篇文章主要介紹了SpringBoot 自定義+動態(tài)切換數(shù)據(jù)源教程,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • Spring Boot中使用RabbitMQ的示例代碼

    Spring Boot中使用RabbitMQ的示例代碼

    本篇文章主要介紹了Spring Boot中使用RabbitMQ的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-04-04
  • 淺析NIO系列之TCP

    淺析NIO系列之TCP

    NIO即同步非阻塞式IO,它和傳統(tǒng)的BIO比較最大的區(qū)別在于在執(zhí)行accept、connect、read、write操作時是非阻塞的。很有利于實現(xiàn)用少量線程來處理多個客戶端請求,可以隨時讓線程切換所處理的客戶端,從而可以實現(xiàn)高并發(fā)服務(wù)器的開發(fā)
    2021-06-06
  • 解決spring-boot-maven-plugin報紅的問題

    解決spring-boot-maven-plugin報紅的問題

    這篇文章主要給大家介紹一下如何解決spring-boot-maven-plugin報紅的問題,文中通過圖文講解的非常詳細,具有一定的參考價值,需要的朋友可以參考下
    2023-08-08

最新評論