Spring Secutity 添加過(guò)濾器實(shí)現(xiàn)自定義登錄認(rèn)證

上一篇文章我們講了Spring Security 如何實(shí)現(xiàn)OAuth2.0自定義登錄頁(yè)面 + JWT Token配置,但是在有些場(chǎng)景下,我們有可能需要支持多種登錄方式,如用戶名密碼登錄、手機(jī)驗(yàn)證碼登錄等等。

此時(shí)我們需要能夠?qū)崿F(xiàn)一個(gè)認(rèn)證流程同時(shí)支持多種認(rèn)證方式,基于Spring Security認(rèn)證的原理提到的Spring Security 的認(rèn)證流程的本質(zhì)上就是新增、刪除、修改過(guò)濾器。

本文在上一篇文章的基礎(chǔ)上繼續(xù)介紹如何通過(guò)自己添加Filter的方式實(shí)現(xiàn)支持多種方式自定義登錄認(rèn)證,只需要三個(gè)步驟(知道本質(zhì)后是不是并不覺(jué)得復(fù)雜 ):

  1. 添加CustomAuthenticationProcessingFilter用于處理登錄請(qǐng)求
  2. 添加CustomerAuthenticationProvider進(jìn)行實(shí)際認(rèn)證
  3. CustomAuthenticationProcessingFilterCustomerAuthenticationProvider配置到Spring Security框架中

代碼實(shí)現(xiàn)

1. 添加CustomAuthenticationProcessingFilter用于處理登錄請(qǐng)求

CustomAuthenticationProcessingFilter通常實(shí)現(xiàn)兩件事情:

  • 設(shè)置我要處理的登錄請(qǐng)求Url和方法
  • 將請(qǐng)求中的認(rèn)證信息保存起來(lái)以便CustomerAuthenticationProvider處理認(rèn)證

下面代碼attemptAuthentication首先定義了登錄請(qǐng)求的url為/oauth/custom/token POST方法;然后將認(rèn)證的信息包括認(rèn)證的類型、用戶名和憑證生成到CustomAuthenticationToken中(框架會(huì)通過(guò)SecurityContextHolderCustomAuthenticationToken保存以用于AuthenticationProvider的實(shí)際認(rèn)證),最后通過(guò) AuthenticationManager去調(diào)用 AuthenticationProvider去認(rèn)證。
setDetail主要是為了未來(lái)方便擴(kuò)展認(rèn)證請(qǐng)求里面的信息。

public class CustomAuthenticationProcessingFilter extends AbstractAuthenticationProcessingFilter {

    private static final String AUTH_TYPE = "auth_type";
    private static final String USERNAME = "username";
    private static final String CREDENTIALS = "credentials";
    private static final String OAUTH_TOKEN_URL = "/oauth/custom/token";
    private static final String HTTP_METHOD_POST = "POST";

    public CustomAuthenticationProcessingFilter() {
        super(new AntPathRequestMatcher(OAUTH_TOKEN_URL, "POST"));
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException {
        if (!HTTP_METHOD_POST.equals(request.getMethod().toUpperCase())){
           throw  new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        }

        AbstractAuthenticationToken authRequest = new CustomAuthenticationToken(
                request.getParameter(AUTH_TYPE), request.getParameter(USERNAME), request.getParameter(CREDENTIALS),
                request.getParameterMap(),new ArrayList<>()
        );

        this.setDetails(request, authRequest);

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

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

}

注意因?yàn)橐С植煌牡卿浄绞?,所以這里我們自己定義了CustomAuthenticationToken用于保存認(rèn)證信息,auth_type用于記錄登錄方式用戶名密碼登錄還是手機(jī)登錄或者其它方式, principal在手機(jī)登錄中就是手機(jī)號(hào)(前端傳的是username,filter保存到了principal中),credentials是驗(yàn)證碼,你也可以自己定義,只要能從前端請(qǐng)求中獲取到就可以。authParams用以保存其它參數(shù)信息,如果有的話。

public class CustomAuthenticationToken extends AbstractAuthenticationToken {

    private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;

    private String authType;

    private Map<String,String[]> authParams;

    private Object principal;

    private Object credentials;
}

2. 添加CustomerAuthenticationProvider進(jìn)行實(shí)際認(rèn)證

CustomerAuthenticationProvider通常也是實(shí)現(xiàn)兩件事情:

  • 根據(jù)認(rèn)證的類型(用戶名密碼還是驗(yàn)證碼或者其它方式)進(jìn)行認(rèn)證
  • 認(rèn)證成功后加入你需要的用戶信息用于生成Token
public class CustomerAuthenticationProvider implements AuthenticationProvider {

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

        if(authentication.getPrincipal() == null){
            throw  new BadCredentialsException("用戶名為空");
        }

        CustomAuthenticationToken customAuthenticationToken =(CustomAuthenticationToken) authentication;

        // 頁(yè)面調(diào)用時(shí)傳遞auth_type參數(shù),如手機(jī)驗(yàn)證碼驗(yàn)證或者其它類型。根據(jù)不同的類型的auth type采用不同的驗(yàn)證方式驗(yàn)證是否登錄成功
        if("mobile".equals(customAuthenticationToken.getAuthType())) {
            // 手機(jī)認(rèn)證
            
        } else if("password".equals(customAuthenticationToken.getAuthType())) {
            // 用戶名和密碼
            
        } else {
            // 其它方式
            
        }

        List<GrantedAuthority> authorities = new ArrayList<>();
        authorities.add(new SimpleGrantedAuthority("internal::user"));

        CustomAuthenticationToken authenticationToken = new CustomAuthenticationToken
                (((CustomAuthenticationToken) authentication).getAuthType(), authentication.getPrincipal(), null,
                        null, authorities);

        Map<String, Object> details = new HashMap<>(1);
        details.put("name", customAuthenticationToken.getPrincipal());
        authenticationToken.setDetails(details);

        return authenticationToken;
    }

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

3. 將CustomAuthenticationProcessingFilter和CustomerAuthenticationProvider配置到Spring Security框架中

首先配置CustomAuthenticationProcessingFilterexternalAuthenticationProcessingFilter之后然后配置加入CustomerAuthenticationProvider很簡(jiǎn)單

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    CustomLogoutSuccessHandler customLogoutSuccessHandler;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UsernamePasswordUserDetailService usernamePasswordUserDetailService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 默認(rèn)支持./login實(shí)現(xiàn)authorization_code認(rèn)證
        http
            .formLogin().loginPage("/index.html").loginProcessingUrl("/login")
            .and()
            .authorizeRequests()
            .antMatchers("/index.html", "/login", "/resources/**", "/static/**").permitAll()
            .anyRequest() // 任何請(qǐng)求
            .authenticated()// 都需要身份認(rèn)證
            .and()
            .logout().invalidateHttpSession(true).deleteCookies("JSESSIONID").logoutSuccessHandler(customLogoutSuccessHandler).permitAll()
            .and()
            .csrf().disable();

        // 自定義認(rèn)證filter,支持./oauth/custom/token實(shí)現(xiàn)authorization_code認(rèn)證
        http.addFilterAfter(externalAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class);
    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) {
        // 定義認(rèn)證的provider用于實(shí)現(xiàn)用戶名和密碼認(rèn)證
        auth.authenticationProvider(new UsernamePasswordAuthenticationProvider(usernamePasswordUserDetailService));
        // 自定義provider用于實(shí)現(xiàn)自定義的登錄認(rèn)證, 如不需要其它形式認(rèn)證如短信登錄,可刪除
        auth.authenticationProvider(new CustomerAuthenticationProvider());
    }

    @Bean
    public CustomAuthenticationProcessingFilter externalAuthenticationProcessingFilter() {
        // 自定義認(rèn)證filter,需要實(shí)現(xiàn)CustomAuthenticationProcessingFilter和CustomerAuthenticationProvider
        // filter將過(guò)濾url并把認(rèn)證信息塞入authentication作為CustomerAuthenticationProvider.authenticate的入?yún)?        CustomAuthenticationProcessingFilter filter = new CustomAuthenticationProcessingFilter();

        // 默認(rèn)自定義認(rèn)證方式grant_type為authorization_code方式,如果直接返回內(nèi)容,則需自定義success和fail handler
        // filter.setAuthenticationSuccessHandler(new CustomAuthenticationSuccessHandler());
        // filter.setAuthenticationFailureHandler(new CustomAuthenticationFailureHandler());

        filter.setAuthenticationManager(authenticationManager);
        return filter;
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}

效果

因?yàn)樯婕暗角昂蠖说呐浜霞巴ㄟ^(guò)code換取token的轉(zhuǎn)換,本項(xiàng)目實(shí)現(xiàn)了一個(gè)前端項(xiàng)目直接使用Spring Security及其登錄頁(yè)面,登錄成功后跳回到前端首頁(yè),參考文末源碼awesome-admin中admin-ui。

  1. 登錄頁(yè)面, url為localhost:8000( 會(huì)自動(dòng)跳轉(zhuǎn)到auth服務(wù)的登錄頁(yè)面), 用戶名和密碼用上面代碼可以看到,對(duì)用戶名和密碼沒(méi)有實(shí)質(zhì)的檢驗(yàn)可以隨便輸,目前還沒(méi)有前端加上其它認(rèn)證方式。


    登錄頁(yè)面
  2. 請(qǐng)求到/oauth/custom/token登錄后成功, JWT Token會(huì)保存在cookie中以便前端使用。


    自定義認(rèn)證請(qǐng)求

面向Copy&Paste編程

  1. awesome-admin源碼
    https://gitee.com/awesome-engineer/awesome-admin
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容