spring-security使用-更友好的方式擴展登錄AuthenticationProvider(三)


說明

在 spring-security使用-登錄(一) 我們使用的是重寫了Spring-security的filter的方式來進行自定義,但是這樣的弊端,就是侵入太大。直接把spring-security的filter給替換掉了,

通過AuthenticationProvider的方式是在spring-security的filter內部留的擴展點進行擴展自定義登錄邏輯

接口定義

AuthenticationProvider

public interface AuthenticationProvider {
    /**
     * 驗證用戶身份
     * @param var1
     * @return
     * @throws AuthenticationException
     */
    Authentication authenticate(Authentication var1) throws AuthenticationException;

    /**
     * supports 則用來判斷當前的 AuthenticationProvider 是否支持對應的 Authentication。
     * @param var1
     * @return
     */
    boolean supports(Class<?> var1);
}

Authentication

封裝用戶身份信息

public interface Authentication extends Principal, Serializable {
    //用來獲取用戶的權限。
    Collection<? extends GrantedAuthority> getAuthorities();
    //方法用來獲取用戶憑證,一般來說就是密碼。
    Object getCredentials();
    //方法用來獲取用戶攜帶的詳細信息,可能是當前請求之類的東西。
    Object getDetails();
    //方法用來獲取當前用戶,可能是一個用戶名,也可能是一個用戶對象。
    Object getPrincipal();
    //當前用戶是否認證成功。
    boolean isAuthenticated();

    void setAuthenticated(boolean var1) throws IllegalArgumentException;
}

類圖

 

 

 

UsernamePasswordAuthenticationFilter 用的就是UserNamePasswordAuthenticationToken

使用AuthenticationProvider自定義登錄邏輯

1.增加自定義provider繼承DaoAuthenticationProvider 

public class CodeAuthenticationProvider extends DaoAuthenticationProvider {
    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication)   {
        HttpServletRequest req = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String code = req.getParameter("code");
        String verify_code = (String) req.getSession().getAttribute("verify_code");
        if (code == null || verify_code == null || !code.equals(verify_code)) {
            throw new AuthenticationServiceException("驗證碼錯誤");
        }
        super.additionalAuthenticationChecks(userDetails, authentication);
    }
}

2.在public class SecurityConfig extends WebSecurityConfigurerAdapter 類增加以下

 /**
     * 對密碼進行加密的實例
     * @return
     */
    @Bean
    PasswordEncoder passwordEncoder() {
        /**
         * 不加密所以使用NoOpPasswordEncoder
         * 更多可以參考PasswordEncoder 的默認實現官方推薦使用: BCryptPasswordEncoder,BCryptPasswordEncoder
         */
        return NoOpPasswordEncoder.getInstance();
    }
    /**
     * 自定義provider
     * @return
     */
   public CodeAuthenticationProvider codeAuthenticationProvider() {
        CodeAuthenticationProvider myAuthenticationProvider = new CodeAuthenticationProvider();
        //設置passorderEncoder
        myAuthenticationProvider.setPasswordEncoder(passwordEncoder());
        //設置UserDetailsService 可以參考第一篇登錄使用例子自定義userDetailServices
        myAuthenticationProvider.setUserDetailsService(userService);
        return myAuthenticationProvider;
    }

    /**
     * 重寫父類自定義AuthenticationManager 將provider注入進去
     * 當然我們也可以考慮不重寫 在父類的manager里面注入provider
     * @return
     * @throws Exception
     */
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        ProviderManager manager = new ProviderManager(Arrays.asList(codeAuthenticationProvider()));
        return manager;
    }

原理 

spring-security使用-登錄(一) 我們替換了默認的UsernamePasswordAuthenticationFilter

1.根據看這個類UsernamePasswordAuthenticationFilter我們可以看出他的父類實現了Servilet Fitler,所以spring-security是基於Filter的

org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter#doFilter 父類實現

  public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
                 //省略部分代碼......
                //調用子類的實現 也就是 UsernamePasswordAuthenticationFilter
                authResult = this.attemptAuthentication(request, response);
                if (authResult == null) {
                    return;
                }
        }
    }

2.看子類實現

org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter#attemptAuthentication

 public org.springframework.security.core.Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        //注意這里不支持post請求,如果我們要讓支持post請求,就要重寫filter吧這里去掉
        if (this.postOnly && !request.getMethod().equals("POST")) {
            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
        } else {
            //獲得登錄用戶名
            String username = this.obtainUsername(request);
            //用的登錄密碼
            String password = this.obtainPassword(request);
            if (username == null) {
                username = "";
            }

            if (password == null) {
                password = "";
            }

            username = username.trim();
            //通過UsernamePasswordAuthenticationToken 封裝
            UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
            this.setDetails(request, authRequest);
            //委托給AuthenticationManager 執行 我們上面重寫用的是ProviderManager
            return this.getAuthenticationManager().authenticate(authRequest);
        }
    }

3.providerManger實現

org.springframework.security.authentication.ProviderManager#authenticate

    public org.springframework.security.core.Authentication authenticate(org.springframework.security.core.Authentication authentication) throws AuthenticationException {
        //獲得Authentication的類型
        Class<? extends org.springframework.security.core.Authentication> toTest = authentication.getClass();
        AuthenticationException lastException = null;
        AuthenticationException parentException = null;
        org.springframework.security.core.Authentication result = null;
        org.springframework.security.core.Authentication parentResult = null;
        boolean debug = logger.isDebugEnabled();
        //獲得所有的Providers 就是我們定義的CodeAuthenticationProvider
        Iterator var8 = this.getProviders().iterator();
        while(var8.hasNext()) {
            //迭代器迭代獲取
            AuthenticationProvider provider = (AuthenticationProvider)var8.next();
            //判斷是否能處理
            if (provider.supports(toTest)) {
                if (debug) {
                    logger.debug("Authentication attempt using " + provider.getClass().getName());
                }

                try {
                    //調用provider的authenticate 執行身份認證
                    result = provider.authenticate(authentication);
                    if (result != null) {
                        this.copyDetails(authentication, result);
                        break;
                    }
                } catch (InternalAuthenticationServiceException | AccountStatusException var13) {
                    this.prepareException(var13, authentication);
                    throw var13;
                } catch (AuthenticationException var14) {
                    lastException = var14;
                }
            }
        }
      //省略部分代碼.......
    }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM