Spring Security——基於表單登陸認證原理及實現

基於表單的登陸認證

1、默認安全驗證

當咱們項目裏添加spring security依賴它就已經起做用了,啓動項目訪問時,會出現彈出框。spring security默認
採用basic模式認證。瀏覽器發送http報文請求一個受保護的資源,瀏覽器會彈出對話框讓輸入用戶名和密碼。並以用
戶名:密碼的形式base64加密,加入Http報文頭部的Authorization(默認用戶名爲user,密碼則是會在啓動程序時後
臺console裏輸出,每次都不同)。後臺獲取Http報文頭部相關認證信息,認證成功返回相應內容,失敗則繼續認證。
下面會詳細介紹具體認證流程。
複製代碼

2、基於表單的認證原理

基本認證流程:
                     SecurityContextPersistenceFilter
                                   ↓
                   AbstractAuthenticationProcessingFilter
                                   ↓
                    UsernamePasswordAuthenticationFilter
                                   ↓
                         AuthenticationManager
                                   ↓
                         AuthenticationProvider
                                   ↓
                          userDetailsService
                                   ↓
                              userDetails
                                   ↓
                                認證經過
                                   ↓
                            SecurityContext
                                   ↓
                          SecurityContextHolder
                                   ↓
                           RememberMeServices
                                   ↓
                      AuthenticationSuccessHandler
SecurityContextPersistenceFilter會校驗請求中session是否有SecurityContext,有放SecurityContextHolder
中,返回時校驗SecurityContextHolder中是否有securityContext,有放session,從而實現認證信息在多個請求中共
享。
AbstractAuthenticationProcessingFilter中會調自身attemptAuthentication抽象方法,
UsernamePasswordAuthenticationFilter是其一個實現類。
複製代碼

它從請求中獲取帳號密碼後,構造了一個token,此時沒有認證,隨後會調用AuthenticationManager接口的
authenticate方法
複製代碼

下面是其構造函數,咱們可看到這個token沒有認證
複製代碼
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
        super((Collection)null);
        this.principal = principal;
        this.credentials = credentials;
        this.setAuthenticated(false);
    }
複製代碼

ProviderManager實現了AuthenticationManager接口,它獲取全部provider後遍歷調用其supports方法,去匹配哪
個provider支持以前申明的token,找到provider後調用authenticate方法。

到provider纔開始認證用戶信息,表單登陸的是DaoAuthenticationProvider其父類進行如下操做:
①調用userDetailsService接口的loadUserByUsername方法獲取UserDetails用戶信息
②檢查UserDetails用戶是否可用、是否帳戶沒有過時、是否帳戶沒有被鎖定
③檢查UserDetails密碼是否正確
④檢查UserDetails密碼是否沒有過時
⑤都經過會從新申明一個token,不過多了權限集合參數以下
複製代碼
public UsernamePasswordAuthenticationToken(Object principal, Object credentials,
    Collection authorities) {
        super(authorities);
        this.principal = principal;
        this.credentials = credentials;
        super.setAuthenticated(true);
    }
複製代碼
此時用戶才被認證經過,上述步驟有一個錯了會拋出AuthenticationException異常的子類,下面是其繼承圖:
複製代碼

認證結果返回給AbstractAuthenticationProcessingFilter,將結果放到SecurityContextHolder的
SecurityContext中,若添加了記住我功能又會調用rememberMeServices來實現,最後調用
AuthenticationSuccessHandler接口成功處理。
複製代碼

3、表單登陸實現

在第二部分介紹具體原理,接下來實現自定義登陸驗證。首先編寫一個config類繼承WebSecurityConfigurerAdapter
類重寫configure方法填寫配置
複製代碼
protected void configure(HttpSecurity http) throws Exception {
        http
                /**
                 * 表單登陸配置
                 */
                .formLogin() //表單登陸
                .loginPage("/authentication/login.html") //自定義登陸頁面
                .loginProcessingUrl("/authentication/form") //與自定義登陸頁面處理路徑一致
//        http.httpBasic()   basic模式彈出框登陸
                .successHandler(myAuthenticationSuccessHandler)  //自定義認證成功處理
                .failureHandler(myAuthenticationFailureHandler)  //自定義認證失敗處理
                .and()
                /**
                 * 須要認證的請求配置,
                 * 注:最爲具體的請求路徑放在前面,而最不具體的路徑(如anyRequest())放在最後面。
                 * 若是不這樣作的話,那不具體的路徑配置將會覆蓋掉更爲具體的路徑配置
                 */
                .authorizeRequests()
                //容許這樣請求經過,須要將登陸所需路徑配好,否則會一直重定向
                .antMatchers("/authentication/*").permitAll() 
                .anyRequest().authenticated()//任何請求都須要認證
                .and()
                .csrf().disable(); //關閉csrf防護機制
    }
    複製代碼
自定義用戶實現UserDetailsService接口重寫loadUserByUsername方法
複製代碼
@Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return new User(username, "123456", true, true,
                true, true, new ArrayList());
    }
複製代碼
這裏的User是security本身的,寫死了密碼爲123456,正常應該根據用戶名從數據庫查出用戶信息。固然密碼也不可
能爲明文,這裏推薦使用BCryptPasswordEncoder加密,由於其每次加密都會生成隨機鹽加入字符串中。須要在以前申
明的config類添加便可
複製代碼
@Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
複製代碼
這四個boolean值分別表示是否可用、是否帳戶沒有過時、是否密碼沒有過時、是否帳戶沒有被鎖定。構造函數
最後一個參數爲該用戶用哪些接口權限,一樣也從數據庫查,可是這個權限只會在登陸時初始化一次,若我登陸後修
改權限,則沒法同步,後續介紹解決辦法。
    接下來就是兩個登陸的自定義登陸處理,成功的實現AuthenticationSuccessHandler接口,失敗的則實現
AuthenticationFailureHandler接口
複製代碼
@Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException{
        response.setContentType("application/json;UTF-8");
        response.getWriter().println("{\"success\":true,\"result\":\"" + "登陸成功" + "\"}");
    }
    
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        response.setContentType("application/json;UTF-8");
        response.getWriter().println("{\"error\":true,\"message\":\"登陸名或者密碼錯誤\"}");
    }
複製代碼
注意成功和失敗最後的方法參數是不一樣的,一個是成功的認證信息,另外一個失敗拋出的認證異常json串的key能夠
根據自身登陸頁面ajax處理結果的屬性來定,必須保持一致。
    頁面代碼就不復制了,作一個簡單html就行。
複製代碼

4、Remember Me

remember me顧名思義就是記住我,在登陸成功認證之後服務端會發送cookie給瀏覽器,同時把用戶名、base64加密隨
機序列、生成token存入persistent_logins表中。當我下次再訪問時,會讀取cookie中的token與數據庫中的token作
對比,若一直能表示認證經過,會從新生成新的token存入數據庫中,序列戶不變,再從新生成新的cookie發給瀏覽器。
下面看具體源碼:
複製代碼

在第二部分說了登陸認證成功後會調用rememberMeServices的loginSuccess方法,這是最後調用的方法,咱們看
到了它構造了一個rememberMeToken,將其存入數據庫,併發送cookie。
複製代碼

當下次登陸,會請求到RememberMeAuthenticationFilter,它調用rememberMeServices接口的autoLogin方法,
去對比cookie中的token與數據庫中的token,又判斷了token是否過時,驗證經過會根據token中的用戶名調用
userDetailsService的loadUserByUsername方法獲取用戶信息。最後根據用戶信息構造
一個RememberMeAuthenticationToken認證成功。
複製代碼

4、Remember Me實現

remember me實現較爲簡單,只須要在申明的config類中添加
複製代碼
@Autowired
private DataSource dataSource;

@Autowired
    private UserDetailsService userDetailsService;

@Bean
    public PersistentTokenRepository persistentTokenRepository() {

        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        tokenRepository.setDataSource(dataSource);
        //tokenRepository.setCreateTableOnStartup(true);
        return tokenRepository;
    }
複製代碼
在configure方法里加
複製代碼
.rememberMe()
                .tokenRepository(persistentTokenRepository())
                .tokenValiditySeconds(3600) //有效時間
                .userDetailsService(userDetailsService)
複製代碼

5、代碼地址

https://github.com/qumaoming/spring-security
複製代碼
相關文章
相關標籤/搜索