最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Spring?Security自定義認證邏輯實例詳解

 更新時間:2022年01月21日 14:18:34   作者:Pseudocode  
這篇文章主要給大家介紹了關(guān)于Spring?Security自定義認證邏輯的相關(guān)資料,文中通過實例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下

前言

這篇文章的內(nèi)容基于對Spring Security 認證流程的理解,如果你不了解,可以讀一下這篇文章:Spring Security 認證流程 。

分析問題

以下是 Spring Security 內(nèi)置的用戶名/密碼認證的流程圖,我們可以從這里入手:

根據(jù)上圖,我們可以照貓畫虎,自定義一個認證流程,比如手機短信碼認證。在圖中,我已經(jīng)把流程中涉及到的主要環(huán)節(jié)標(biāo)記了不同的顏色,其中藍色塊的部分,是用戶名/密碼認證對應(yīng)的部分,綠色塊標(biāo)記的部分,則是與具體認證方式無關(guān)的邏輯。

因此,我們可以按照藍色部分的類,開發(fā)我們自定義的邏輯,主要包括以下內(nèi)容:

  • 一個自定義的 Authentication 實現(xiàn)類,與 UsernamePasswordAuthenticationToken 類似,用來保存認證信息。
  • 一個自定義的過濾器,與 UsernamePasswordAuthenticationFilter 類似,針對特定的請求,封裝認證信息,調(diào)用認證邏輯。
  • 一個 AuthenticationProvider 的實現(xiàn)類,提供認證邏輯,與 DaoAuthenticationProvider 類似。

接下來,以手機驗證碼認證為例,一一完成。

自定義 Authentication

先給代碼,后面進行說明:

public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken {

    private final Object principal;

    private Object credentials;

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

    @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;
    }
}

UsernamePasswordAuthenticationToken 一樣,繼承 AbstractAuthenticationToken 抽象類,需要實現(xiàn) getPrincipalgetCredentials 兩個方法。在用戶名/密碼認證中,principal 表示用戶名,credentials 表示密碼,在此,我們可以讓它們指代手機號和驗證碼,因此,我們增加這兩個屬性,然后實現(xiàn)方法。

除此之外,我們需要寫兩個構(gòu)造方法,分別用來創(chuàng)建未認證的和已經(jīng)成功認證的認證信息。

自定義 Filter

這一部分,可以參考 UsernamePasswordAuthenticationFilter 來寫。還是線上代碼:

public class SmsCodeAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter {

    public static final String FORM_MOBILE_KEY = "mobile";
    public static final String FORM_SMS_CODE_KEY = "smsCode";

    private static final AntPathRequestMatcher DEFAULT_ANT_PATH_REQUEST_MATCHER = new AntPathRequestMatcher("/sms/login",
            "POST");

    private boolean postOnly = true;

    protected SmsCodeAuthenticationProcessingFilter() {
        super(DEFAULT_ANT_PATH_REQUEST_MATCHER);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
        if (this.postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }
        String mobile = obtainMobile(request);
        mobile = (mobile != null) ? mobile : "";
        mobile = mobile.trim();
        String smsCode = obtainSmsCode(request);
        smsCode = (smsCode != null) ? smsCode : "";
        SmsCodeAuthenticationToken authRequest = new SmsCodeAuthenticationToken(mobile, smsCode);
        // Allow subclasses to set the "details" property
        setDetails(request, authRequest);
        return this.getAuthenticationManager().authenticate(authRequest);
    }

    private String obtainMobile(HttpServletRequest request) {
        return request.getParameter(FORM_MOBILE_KEY);
    }

    private String obtainSmsCode(HttpServletRequest request) {
        return request.getParameter(FORM_SMS_CODE_KEY);
    }

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

這部分比較簡單,關(guān)鍵點如下:

  • 首先,默認的構(gòu)造方法中制定了過濾器匹配那些請求,這里匹配的是 /sms/login 的 POST 請求。
  • 在 attemptAuthentication 方法中,首先從 request 中獲取表單輸入的手機號和驗證碼,創(chuàng)建未經(jīng)認證的 Token 信息。
  • 將 Token 信息交給 this.getAuthenticationManager().authenticate(authRequest) 方法。

自定義 Provider

這里是完成認證的主要邏輯,這里的代碼只有最基本的校驗邏輯,沒有寫比較嚴(yán)謹?shù)男r?,比如校驗用戶是否禁用等,因為這部分比較繁瑣但是簡單。

public class SmsCodeAuthenticationProvider implements AuthenticationProvider {

    public static final String SESSION_MOBILE_KEY = "mobile";
    public static final String SESSION_SMS_CODE_KEY = "smsCode";
    public static final String FORM_MOBILE_KEY = "mobile";
    public static final String FORM_SMS_CODE_KEY = "smsCode";

    private UserDetailsService userDetailsService;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        authenticationChecks(authentication);
        String mobile = authentication.getName();
        UserDetails userDetails = userDetailsService.loadUserByUsername(mobile);
        SmsCodeAuthenticationToken authResult = new SmsCodeAuthenticationToken(userDetails, userDetails.getAuthorities());
        return authResult;
    }

    /**
     * 認證信息校驗
     * @param authentication
     */
    private void authenticationChecks(Authentication authentication) {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        // 表單提交的手機號和驗證碼
        String formMobile = request.getParameter(FORM_MOBILE_KEY);
        String formSmsCode = request.getParameter(FORM_SMS_CODE_KEY);
        // 會話中保存的手機號和驗證碼
        String sessionMobile = (String) request.getSession().getAttribute(SESSION_MOBILE_KEY);
        String sessionSmsCode = (String) request.getSession().getAttribute(SESSION_SMS_CODE_KEY);

        if (StringUtils.isEmpty(sessionMobile) || StringUtils.isEmpty(sessionSmsCode)) {
            throw new BadCredentialsException("為發(fā)送手機驗證碼");
        }

        if (!formMobile.equals(sessionMobile)) {
            throw new BadCredentialsException("手機號碼不一致");
        }

        if (!formSmsCode.equals(sessionSmsCode)) {
            throw new BadCredentialsException("驗證碼不一致");
        }
    }

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

    public void setUserDetailsService(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }
}

這段代碼的重點有以下幾個:

  • supports 方法用來判斷這個 Provider 支持的 AuthenticationToken 的類型,這里對應(yīng)我們之前創(chuàng)建的 SmsCodeAuthenticationToken
  • 在 authenticate 方法中,我們將 Token 中的手機號和驗證碼與 Session 中保存的手機號和驗證碼進行對比。(向 Session 中保存手機號和驗證碼的部分在下文中實現(xiàn))對比無誤后,從 UserDetailsService 中獲取對應(yīng)的用戶,并依此創(chuàng)建通過認證的 Token,并返回,最終到達 Filter 中。

自定義認證成功/失敗后的 Handler

之前,我們通過分析源碼知道,F(xiàn)ilter 中的 doFilter 方法,其實是在它的父類

AbstractAuthenticationProcessingFilter 中的,attemptAuthentication 方法也是在 doFilter 中被調(diào)用的。

當(dāng)我們進行完之前的自定義邏輯,無論是否認證成功,attemptAuthentication 方法會返回認證成功的結(jié)果或者拋出認證失敗的異常。doFilter 方法中會根據(jù)認證的結(jié)果(成功/失?。?,調(diào)用不同的處理邏輯,這兩個處理邏輯,我們也可以進行自定義。

我直接在下面貼代碼:

public class SmsCodeAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        response.setContentType("text/plain;charset=UTF-8");
        response.getWriter().write(authentication.getName());
    }
}
public class SmsCodeAuthenticationFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        response.setContentType("text/plain;charset=UTF-8");
        response.getWriter().write("認證失敗");
    }
}

以上是成功和失敗后的處理邏輯,需要分別實現(xiàn)對應(yīng)的接口,并實現(xiàn)方法。注意,這里只是為了測試,寫了最簡單的邏輯,以便測試的時候能夠區(qū)分兩種情況。真實的項目中,要根據(jù)具體的業(yè)務(wù)執(zhí)行相應(yīng)的邏輯,比如保存當(dāng)前登錄用戶的信息等。

配置自定義認證的邏輯

為了使我們的自定義認證生效,需要將 Filter 和 Provider 添加到 Spring Security 的配置當(dāng)中,我們可以把這一部分配置先單獨放到一個配置類中:

@Component
@RequiredArgsConstructor
public class SmsCodeAuthenticationSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
    private final UserDetailsService userDetailsService;

    @Override
    public void configure(HttpSecurity http) {

        SmsCodeAuthenticationProcessingFilter smsCodeAuthenticationFilter = new SmsCodeAuthenticationProcessingFilter();
        smsCodeAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
        smsCodeAuthenticationFilter.setAuthenticationSuccessHandler(new SmsCodeAuthenticationSuccessHandler());
        smsCodeAuthenticationFilter.setAuthenticationFailureHandler(new SmsCodeAuthenticationFailureHandler());

        SmsCodeAuthenticationProvider smsCodeAuthenticationProvider = new SmsCodeAuthenticationProvider();
        smsCodeAuthenticationProvider.setUserDetailsService(userDetailsService);

        http.authenticationProvider(smsCodeAuthenticationProvider)
                .addFilterAfter(smsCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
    }
}

其中,有以下需要注意的地方:

  • 一定記得把 AuthenticationManager 提供給 Filter,回顧之前講到的認證邏輯,如果沒有這一步,在 Filter 中完成認證信息的封裝后,就沒辦法去找對應(yīng)的 Provider。
  • 要把成功/失敗后的處理邏輯的兩個類提供給 Filter,否則不會進入這兩個邏輯,而是會進入默認的處理邏輯。
  • Provider 中用到了 UserDetailsService,也要記得提供。
  • 最后,將兩者添加到 HttpSecurity 對象中。

接下來,需要在 Spring Security 的主配置中添加如下內(nèi)容。

  • 首先,注入 SmsCodeAuthenticationSecurityConfig 配置。
  • 然后,在 configure(HttpSecurity http) 方法中,引入配置:http.apply`` ( ``smsCodeAuthenticationSecurityConfig`` ) ``;
  • 最后,由于在認證前,需要請求和校驗驗證碼,因此,對 /sms/** 路徑進行放行。

測試

大功告成,我們測試一下,首先需要提供一個發(fā)送驗證碼的接口,由于是測試,我們直接將驗證碼返回。接口代碼如下:

@GetMapping("/getCode")
public String getCode(@RequestParam("mobile") String mobile,
                      HttpSession session) {
    String code = "123456";
    session.setAttribute("mobile", mobile);
    session.setAttribute("smsCode", code);
    return code;
}

為了能獲取到相應(yīng)的用戶,如果你還沒有實現(xiàn)自己的 UserDetailsService,先寫一個簡單的邏輯,完成測試,其中的 loadUserByUsername 方法如下即可:

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    // TODO: 臨時邏輯,之后對接用戶管理相關(guān)的服務(wù)
    return new User(username, "123456",
            AuthorityUtils.createAuthorityList("admin"));
}

OK,下面是測試結(jié)果:

總結(jié)

到此這篇關(guān)于Spring Security自定義認證邏輯的文章就介紹到這了,更多相關(guān)Spring Security自定義認證邏輯內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 迪米特法則_動力節(jié)點Java學(xué)院整理

    迪米特法則_動力節(jié)點Java學(xué)院整理

    這篇文章主要介紹了迪米特法則,迪米特法則就是一個在類創(chuàng)建方法和屬性時需要遵守的法則,有興趣的可以了解一下
    2017-08-08
  • mybatis中實現(xiàn)枚舉自動轉(zhuǎn)換方法詳解

    mybatis中實現(xiàn)枚舉自動轉(zhuǎn)換方法詳解

    在使用mybatis的時候經(jīng)常會遇到枚舉類型的轉(zhuǎn)換,下面這篇文章主要給大家介紹了關(guān)于mybatis中實現(xiàn)枚舉自動轉(zhuǎn)換的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起看看吧。
    2017-08-08
  • SpringBoot項目多數(shù)據(jù)源及mybatis 駝峰失效的問題解決方法

    SpringBoot項目多數(shù)據(jù)源及mybatis 駝峰失效的問題解決方法

    這篇文章主要介紹了SpringBoot項目多數(shù)據(jù)源及mybatis 駝峰失效的問題解決方法,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-07-07
  • 基于google zxing的Java二維碼生成與解碼

    基于google zxing的Java二維碼生成與解碼

    這篇文章主要為大家詳細介紹了基于google zxing的Java二維碼生成與解碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-07-07
  • springboot整合消息隊列RabbitMQ

    springboot整合消息隊列RabbitMQ

    這篇文章主要介紹了springboot整合消息隊列RabbitMQ,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下
    2022-08-08
  • Java中this和super的區(qū)別及this能否調(diào)用到父類使用

    Java中this和super的區(qū)別及this能否調(diào)用到父類使用

    這篇文章主要介紹了Java中this和super的區(qū)別及this能否調(diào)用到父類使用,this和super都是Java中常見的關(guān)鍵字,下文關(guān)于兩者區(qū)別介紹,需要的小伙伴可以參考一下
    2022-05-05
  • 關(guān)于Java錯誤提示之找不到或無法加載主類的問題及正確處理方法

    關(guān)于Java錯誤提示之找不到或無法加載主類的問題及正確處理方法

    當(dāng)我們在初學(xué)Java的是時候,類文件中是不設(shè)定包名(package)的,這種情況下注意classpath,基本上沒有問題,?本文主要說明classpath和系統(tǒng)環(huán)境變量PATH都沒問題的情況下出錯原因和正確處理方法,感興趣的朋友一起看看吧
    2022-01-01
  • Spring中基于xml的AOP的詳細步驟

    Spring中基于xml的AOP的詳細步驟

    這篇文章主要介紹了Spring中基于xml的AOP的詳細步驟,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-06-06
  • Java文件選擇對話框JFileChooser使用詳解

    Java文件選擇對話框JFileChooser使用詳解

    這篇文章主要介紹了Java文件選擇對話框JFileChooser使用詳解的相關(guān)資料,需要的朋友可以參考下
    2015-07-07
  • Spring Cloud動態(tài)配置刷新@RefreshScope與@Component的深度解析

    Spring Cloud動態(tài)配置刷新@RefreshScope與@Component的深度解析

    在現(xiàn)代微服務(wù)架構(gòu)中,動態(tài)配置管理是一個關(guān)鍵需求,Spring Cloud 提供了 @RefreshScope 注解,允許應(yīng)用在運行時動態(tài)更新配置,而無需重啟服務(wù),本文深入探析Spring Cloud動態(tài)配置刷新@RefreshScope與@Component,感興趣的朋友一起看看吧
    2025-04-04

最新評論

平度市| 韩城市| 桑日县| 桑植县| 海城市| 芦山县| 渭源县| 阿城市| 南宁市| 石阡县| 伊宁县| 平凉市| 芮城县| 湟中县| 拉萨市| 天等县| 土默特左旗| 固原市| 中江县| 永州市| 封开县| 乌兰察布市| 洱源县| 江阴市| 舞阳县| 吴忠市| 鹤岗市| 手机| 大同县| 莱西市| 贵港市| 准格尔旗| 会宁县| 万州区| 尼勒克县| 衡阳市| 禄丰县| 陆川县| 南丹县| 孝感市| 弋阳县|