SpringBoot+SpringSecurity+JWT實現認證和授權

一、背景:

在 B/S 系統中,登錄功基本都是依靠 Cookie 來實現的,用戶登錄成功之後主要需要客戶端和服務端完成以下兩項工作:

(1)服務端將登錄狀態記錄到 Session 中,或者簽發Token;

(2)客戶端利用Cookie保存於服務端對應的 Session ID 或 Token。之後每次請求都會帶上Cookie信息(包含Session ID或者Token),當服務端收到請求後,通過驗證 Cookie 中的信息來判斷用戶是否登錄 。

單點登錄: 單點登錄(Single Sign On, SSO)是指在同一帳號平臺下的多個應用系統中,用戶只需登錄一次,即可訪問所有相互信任的應用系統。單點登錄的本質就是在多個應用系統中共享登錄狀態。 SSO是目前比較流行的企業業務整合的解決方案之一。

如果用戶的登錄狀態是記錄在 Session 中的,要實現共享登錄狀態,就要先共享 Session,比如可以將 Session 序列化到 Redis 中,讓多個應用系統共享同一個 Redis,直接讀取 Redis 來獲取 Session。當然僅此是不夠的,因爲不同的應用系統有着不同的域名,儘管 Session 共享了,但是由於 Session ID 是往往保存在瀏覽器 Cookie 中的,因此存在作用域的限制,無法跨域名傳遞,也就是說當用戶在 app1.com 中登錄後,Session ID 僅在瀏覽器訪問 app1.com 時纔會自動在請求頭中攜帶,而當瀏覽器訪問 app2.com 時,Session ID 是不會被帶過去的。實現單點登錄的關鍵在於,如何讓 Session ID(或 Token)在多個域中共享。

目前而言,主要有以下3種方式:

(1)父域 Cookie:例如:baike.baidu.com、wenku.baidu.com、zhida.baidu.com可以將認證的cookie放入baidu.com這個父級域名中,從而實現登錄信息的共享。此種實現方式比較簡單,但不支持跨主域名。

(2)認證中心:我們可以部署一個認證中心,認證中心就是一個專門負責處理登錄請求的獨立的 Web 服務。用戶統一在認證中心進行登錄,登錄成功後,認證中心記錄用戶的登錄狀態,並將 Token 寫入 Cookie。(注意這個 Cookie 是認證中心的,應用系統是訪問不到的。)

應用系統檢查當前請求有沒有 Token,如果沒有,說明用戶在當前系統中尚未登錄,那麼就將頁面跳轉至認證中心。由於這個操作會將認證中心的 Cookie 自動帶過去,因此,認證中心能夠根據 Cookie 知道用戶是否已經登錄過了。如果認證中心發現用戶尚未登錄,則返回登錄頁面,等待用戶登錄,如果發現用戶已經登錄過了,就不會讓用戶再次登錄了,而是會跳轉回目標 URL ,並在跳轉前生成一個 Token,拼接在目標 URL 的後面,回傳給目標應用系統。

應用系統拿到 Token 之後,還需要向認證中心確認下 Token 的合法性,防止用戶僞造。確認無誤後,應用系統記錄用戶的登錄狀態,並將 Token 寫入 Cookie,然後給本次訪問放行。(注意這個 Cookie 是當前應用系統的,其他應用系統是訪問不到的。)當用戶再次訪問當前應用系統時,就會自動帶上這個 Token,應用系統驗證 Token 發現用戶已登錄,於是就不會有認證中心什麼事了。

目前被廣泛使用的方案主要是Apereo CAS 。Apereo CAS 是一個企業級單點登錄系統,其中 CAS 的意思是”Central Authentication Service“。它最初是耶魯大學實驗室的項目,後來轉讓給了 JASIG 組織,項目更名爲 JASIG CAS,後來該組織併入了 Apereo 基金會,項目也隨之更名爲 Apereo CAS。

(3)LocalStorage 跨域:在前後端分離的情況下,完全可以不使用 Cookie,我們可以選擇將 Session ID (或 Token )保存到瀏覽器的 LocalStorage 中,讓前端在每次向後端發送請求時,主動將 LocalStorage 的數據傳遞給服務端。這些都是由前端來控制的,後端需要做的僅僅是在用戶登錄成功後,將 Session ID (或 Token )放在響應體中傳遞給前端。在這樣的場景下,單點登錄完全可以在前端實現。前端拿到 Session ID (或 Token )後,除了將它寫入自己的 LocalStorage 中之外,還可以通過特殊手段將它寫入多個其他域下的 LocalStorage 中。

二、JWT介紹

JWT概念說明

從分佈式認證流程中,我們不難發現,這中間起最關鍵作用的就是token,token的安全與否,直接關係到系統的健壯性,這裏我們選擇使用JWT來實現token的生成和校驗。JWT,全稱JSON Web Token,官網地址https://jwt.io,是一款出色的分佈式身份校驗方案。可以生成token,也可以解析檢驗token。

JWT生成的token由三部分組成:

該對象爲一個很長的字符串,字符之間通過"."分隔符分爲三個子串。 每一個子串表示了一個功能塊,總共有以下三個部分:JWT 頭、有效載荷和簽名。

頭部JWT 頭部分是一個描述 JWT 元數據的 JSON 對象,主要設置一些規範信息,簽名部分的編碼格式就在頭部中聲明。

載荷:token中存放有效信息的部分,是 JWT 的主體內容部分,是一個 JSON 對象,包含需要傳遞的數據。比如用戶名,用戶角色,過期時間等,但是不要放密碼,會泄露!

指定七個默認字段供選擇。
iss:發行人
exp:到期時間
sub:主題
aud:用戶
nbf:在此之前不可用
iat:發佈時間
jti:JWT ID 用於標識該 JWT
除以上默認字段外,我們還可以自定義私有字段,如下例:
{
"sub": "11111",
"name": "Atom",
"admin": true
}

簽名:將頭部與載荷分別採用base64編碼後,用“.”相連,再加入鹽,最後使用頭部聲明的編碼類型進行編碼,就得到了簽名。通過指定的算法生成哈希,以確保數據不會被篡改。 首先,需要指定一個密碼(secret)。該密碼僅僅爲保存在服務器中,並且不能向用戶公 開。然後,使用標頭中指定的簽名算法(默認情況下爲 HMAC SHA256)根據以下公式生成簽名。 HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(claims), secret) 在計算出簽名哈希後,JWT 頭,有效載荷和簽名哈希的三個部分組合成一個字符串,每個部分用"."分隔,就構成整個 JWT 對象。

JWT生成token的安全性分析

從JWT生成的token組成上來看,要想避免token被僞造,主要就得看簽名部分了,而簽名部分又有三部分組成,其中頭部和載荷的base64編碼,幾乎是透明的,毫無安全性可言,那麼最終守護token安全的重擔就落在了加入的上面了!試想:如果生成token所用的鹽與解析token時加入的鹽是一樣的。豈不是類似於中國人民銀行把人民幣防僞技術公開了?大家可以用這個鹽來解析token,就能用來僞造token。這時,我們就需要對鹽採用非對稱加密的方式進行加密,以達到生成token與校驗token方所用的鹽不一致的安全效果!

非對稱加密RSA介紹

基本原理:同時生成兩把密鑰:私鑰和公鑰,私鑰隱祕保存,公鑰可以下發給信任客戶端私鑰加密,持有私鑰或公鑰纔可以解密公鑰加密,持有私鑰纔可解密 優點:安全,難以破解 缺點:算法比較耗時,爲了安全,可以接受 歷史:三位數學家Rivest、Shamir 和 Adleman 設計了一種算法,可以實現非對稱加密。這種算法用他們三個人的名字縮寫:RSA。

三、具體代碼實現

集中式認證流程

  • 用戶認證: 使用UsernamePasswordAuthenticationFilter過濾器中attemptAuthentication方法實現認證功能,該過濾器父類中successfulAuthentication方法實現認證成功後的操作。
  • 身份校驗: 使用BasicAuthenticationFilter過濾器中doFilterInternal方法驗證是否登錄,以決定能否進入後續過濾器。


分佈式認證流程

  • 用戶認證:由於分佈式項目,多數是前後端分離的架構設計,我們要滿足可以接受異步post的認證請求參數,需要修改UsernamePasswordAuthenticationFilter過濾器中attemptAuthentication方法,讓其能夠接收請求體。另外,默認successfulAuthentication方法在認證通過後,是把用戶信息直接放入session就完事了,現在我們需要修改這個方法,在認證通過後生成token並返回給用戶。
  • 身份校驗: 原來BasicAuthenticationFilter過濾器中doFilterInternal方法校驗用戶是否登錄,就是看session中是否有用戶信息,我們要修改爲,驗證用戶攜帶的token是否合法,並解析出用戶信息,交給SpringSecurity,以便於後續的授權功能可以正常使用。

3.1引入依賴

<!--spring security 安全框架-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--JWT工具依賴-->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.0</version>
</dependency>

3.2編寫核心配置類

Spring Security 的核心配置就是繼承 WebSecurityConfigurerAdapter 並註解@EnableWebSecurity 的配置。這個配置指明瞭用戶名密碼的處理方式、請求路徑、登錄、登出控制等和安全相關的配置。

package com.oyc.security.config;

import com.oyc.security.filter.TokenAuthenticationFilter;
import com.oyc.security.filter.TokenLoginFilter;
import com.oyc.security.handler.UnauthorizedEntryPoint;
import com.oyc.security.util.DefaultPasswordEncoder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;

/**
 * @ClassName: TokenWebSecurityConfig
 * @Description: TokenWebSecurityConfig
 * @Author oyc
 * @Date 2021/1/18 10:57
 * @Version 1.0
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * 密碼管理工具類
     */
    @Autowired
    private DefaultPasswordEncoder defaultPasswordEncoder;

    /**
     * 用戶服務類
     */
    @Autowired
    private UserDetailsService userDetailsService;

    /**
     * 配置設置,設置退出的地址和token
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.exceptionHandling()
                //未授權處理
                .authenticationEntryPoint(new UnauthorizedEntryPoint())
                .and().authorizeRequests()
                .anyRequest().authenticated()
                .and().csrf().disable()
                .logout().logoutUrl("/logout")
                .and()
                //.addLogoutHandler(new TokenLogoutHandler(tokenManager))
                .addFilter(new TokenLoginFilter(authenticationManager()))
                .addFilter(new TokenAuthenticationFilter(authenticationManager())).httpBasic();
    }

    /**
     * 密碼處理
     */
    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(defaultPasswordEncoder);
    }

    /**
     * 配置哪些請求不攔截
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/index**", "/api/**", "/swagger-ui.html/**");
    }
}

3.3 創建認證授權相關的工具類

(1)DefaultPasswordEncoder:密碼處理的方法

package com.oyc.security.util;

import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;

/**
 * @ClassName: DefaultPasswordEncoder
 * @Description: DefaultPasswordEncoder
 * @Author oyc
 * @Date 2021/1/18 10:58
 * @Version 1.0
 */
@Component
@Slf4j
public class DefaultPasswordEncoder extends BCryptPasswordEncoder {

    @Override
    public String encode(CharSequence rawPassword) {
        return rawPassword.toString();
    }

    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        if (rawPassword == null) {
            throw new IllegalArgumentException("rawPassword cannot be null");
        }
        if (encodedPassword == null || encodedPassword.length() == 0) {
            log.error("Empty encoded password");
            throw new IllegalArgumentException("encodedPassword is null");
        }
        return encodedPassword.equals(rawPassword);
    }
}

(2)JwtTokenUtil:token 操作的工具類

package com.oyc.security.util;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.CompressionCodecs;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.util.Date;

/**
 * @ClassName: TokenManager
 * @Description: TokenManager
 * @Author oyc
 * @Date 2021/1/18 10:58
 * @Version 1.0
 */
public class JwtTokenUtil {
    private static long tokenExpiration = 24 * 60 * 60 * 1000;
    private static String tokenSignKey = "123456";
    private static String userRoleKey = "userRole";

    public String createToken(String userName) {
        String token = Jwts.builder().setSubject(userName)
                .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
                .signWith(SignatureAlgorithm.HS512, tokenSignKey).compressWith(CompressionCodecs.GZIP).compact();
        return token;
    }

    public static String createToken(String userName, String role) {
        String token = Jwts.builder().setSubject(userName)
                .claim(userRoleKey, role)
                .setExpiration(new Date(System.currentTimeMillis() + tokenExpiration))
                .signWith(SignatureAlgorithm.HS512, tokenSignKey).compressWith(CompressionCodecs.GZIP).compact();
        return token;
    }

    public static String getUserNameFromToken(String token) {
        String userName = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody().getSubject();
        return userName;
    }

    public static String getUserRoleFromToken(String token) {
        Claims claims = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody();
        return claims.get(userRoleKey).toString();
    }

}
(3)ResponseUtil :接口響應工具類
package com.oyc.security.util;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @ClassName: ResponseUtil
 * @Description: ResponseUtil
 * @Author oyc
 * @Date 2020/12/29 20:14
 * @Version 1.0
 */
public class ResponseUtil {
    public static void out(HttpServletResponse response, Result result) {
        ObjectMapper mapper = new ObjectMapper();
        PrintWriter writer = null;
        response.setStatus(HttpStatus.OK.value());
        response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
        try {
            writer = response.getWriter();
            mapper.writeValue(writer, result);
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (writer != null) {
                writer.flush();
                writer.close();
            }
        }
    }
}

3.4 創建認證和授權的 filter

(1)TokenLoginFilter:認證的 filter
package com.oyc.security.filter;

import com.oyc.security.util.JwtTokenUtil;
import com.oyc.security.util.ResponseUtil;
import com.oyc.security.util.Result;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.util.StringUtils;

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

/**
 * @ClassName: TokenAuthenticationFilter
 * @Description: TokenAuthenticationFilter
 * @Author oyc
 * @Date 2021/1/18 10:59
 * @Version 1.0
 */
public class TokenAuthenticationFilter extends BasicAuthenticationFilter {

    public TokenAuthenticationFilter(AuthenticationManager authManager) {
        super(authManager);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        logger.info("=================" + request.getRequestURI());
        //不需要鑑權
        if (request.getRequestURI().indexOf("index") != -1) {
            chain.doFilter(request, response);
        }
        UsernamePasswordAuthenticationToken authentication = null;
        try {
            authentication = getAuthentication(request);
        } catch (Exception e) {
            ResponseUtil.out(response, Result.error(e.getMessage()));
        }
        if (authentication != null) {
            SecurityContextHolder.getContext().setAuthentication(authentication);
        } else {
            ResponseUtil.out(response, Result.error("鑑權失敗"));
        }
        chain.doFilter(request, response);
    }

    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
        // 獲取Token字符串,token 置於 header 裏
        String token = request.getHeader("token");
        if (!StringUtils.hasText(token)) {
            token = request.getParameter("token");
        }
        if (token != null && !"".equals(token.trim())) {
            // 從Token中解密獲取用戶名
            String userName = JwtTokenUtil.getUserNameFromToken(token);

            if (userName != null) {
                // 從Token中解密獲取用戶角色
                String role = JwtTokenUtil.getUserRoleFromToken(token);
                // 將ROLE_XXX,ROLE_YYY格式的角色字符串轉換爲數組
                String[] roles = role.split(",");
                Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
                for (String s : roles) {
                    authorities.add(new SimpleGrantedAuthority(s));
                }
                return new UsernamePasswordAuthenticationToken(userName, token, authorities);
            }
            return null;
        }
        return null;
    }
}
(2)TokenAuthenticationFilter:授權 filter
 
 
package com.oyc.security.filter;

import com.oyc.security.util.JwtTokenUtil;
import com.oyc.security.util.ResponseUtil;
import com.oyc.security.util.Result;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.util.StringUtils;

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

/**
 * @ClassName: TokenAuthenticationFilter
 * @Description: TokenAuthenticationFilter
 * @Author oyc
 * @Date 2021/1/18 10:59
 * @Version 1.0
 */
public class TokenAuthenticationFilter extends BasicAuthenticationFilter {

    public TokenAuthenticationFilter(AuthenticationManager authManager) {
        super(authManager);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        logger.info("=================" + request.getRequestURI());
        //不需要鑑權
        if (request.getRequestURI().indexOf("index") != -1) {
            chain.doFilter(request, response);
        }
        UsernamePasswordAuthenticationToken authentication = null;
        try {
            authentication = getAuthentication(request);
        } catch (Exception e) {
            ResponseUtil.out(response, Result.error(e.getMessage()));
        }
        if (authentication != null) {
            SecurityContextHolder.getContext().setAuthentication(authentication);
        } else {
            ResponseUtil.out(response, Result.error("鑑權失敗"));
        }
        chain.doFilter(request, response);
    }

    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
        // 獲取Token字符串,token 置於 header 裏
        String token = request.getHeader("token");
        if (!StringUtils.hasText(token)) {
            token = request.getParameter("token");
        }
        if (token != null && !"".equals(token.trim())) {
            // 從Token中解密獲取用戶名
            String userName = JwtTokenUtil.getUserNameFromToken(token);

            if (userName != null) {
                // 從Token中解密獲取用戶角色
                String role = JwtTokenUtil.getUserRoleFromToken(token);
                // 將ROLE_XXX,ROLE_YYY格式的角色字符串轉換爲數組
                String[] roles = role.split(",");
                Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
                for (String s : roles) {
                    authorities.add(new SimpleGrantedAuthority(s));
                }
                return new UsernamePasswordAuthenticationToken(userName, token, authorities);
            }
            return null;
        }
        return null;
    }
}

3.5 UnauthorizedEntryPoint:未授權統一處理handler

public class UnauthorizedEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        ResponseUtil.out(response, Result.error("未授權統一處理"));
    }
}

3.6 測試控制類

package com.oyc.security.controller;

import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @ClassName: TestController
 * @Description: TestController
 * @Author oyc
 * @Date 2021/1/18 11:06
 * @Version 1.0
 */
@RestController
public class TestController {

    @GetMapping(value = {"", "welcome"})
    public String welcome() {
        return "Welcome!!!";
    }

    @GetMapping("index")
    public String index() {
        return "index!!!";
    }

    @GetMapping("admin")
    public String admin() {
        return "admin!!!";
    }

    @GetMapping("user")
    public String user() {
        return "user!!!";
    }

    @GetMapping("customer")
    public String customer() {
        return "customer!!!";
    }

    /**
     * 方法執行前鑑權
     *
     * @return
     */
    @GetMapping("roleAdmin")
    @Secured("ROLE_ADMIN")
    public String roleAdmin() {
        return "roleAdmin!!!";
    }

    /**
     * 方法執行前鑑權
     *
     * @return
     */
    @GetMapping("preAuthorize")
    @PostAuthorize("hasAnyRole('ROLE_ADMIN')")
    public String preAuthorize() {
        System.out.println("preAuthorize…………");
        return "preAuthorize!!!";
    }

    /**
     * 方法執行完再鑑權
     *
     * @return
     */
    @GetMapping("postAuthorize")
    @PostAuthorize("hasAnyRole('ROLE_USER')")
    public String postAuthorize() {
        System.out.println("postAuthorize…………");
        return "PostAuthorize!!!";
    }
}

3.7 測試

登錄返回token:

校驗登錄狀態:

四、SpringSecurity 原理總結

4.1 SpringSecurity 的過濾器介紹
SpringSecurity 採用的是責任鏈的設計模式,它有一條很長的過濾器鏈。現在對這條過濾器鏈的 15 個過濾器進行說明:
(1) WebAsyncManagerIntegrationFilter:將 Security 上下文與 Spring Web 中用於處理異步請求映射的 WebAsyncManager 進行集成。
(2) SecurityContextPersistenceFilter:在每次請求處理之前將該請求相關的安全上下文信息加載到 SecurityContextHolder 中,然後在該次請求處理完成之後,將SecurityContextHolder 中關於這次請求的信息存儲到一個“倉儲”中,然後將SecurityContextHolder 中的信息清除,例如在 Session 中維護一個用戶的安全信 息就是這個過濾器處理的。
(3) HeaderWriterFilter:用於將頭信息加入響應中。
(4) CsrfFilter:用於處理跨站請求僞造。
(5)LogoutFilter:用於處理退出登錄。
(6)UsernamePasswordAuthenticationFilter:用於處理基於表單的登錄請求,從表單中獲取用戶名和密碼。默認情況下處理來自 /login 的請求。從表單中獲取用戶名和密碼時,默認使用的表單 name 值爲 username 和 password,這兩個值可以通過設置這個過濾器的 usernameParameter 和 passwordParameter 兩個參數的值進行修改。
(7)DefaultLoginPageGeneratingFilter:如果沒有配置登錄頁面,那系統初始化時就會配置這個過濾器,並且用於在需要進行登錄時生成一個登錄表單頁面。
(8)BasicAuthenticationFilter:檢測和處理 http basic 認證。
(9)RequestCacheAwareFilter:用來處理請求的緩存。
(10)SecurityContextHolderAwareRequestFilter:主要是包裝請求對象 request。
(11)AnonymousAuthenticationFilter:檢測 SecurityContextHolder 中是否存在
Authentication 對象,如果不存在爲其提供一個匿名 Authentication。
(12)SessionManagementFilter:管理 session 的過濾器
(13)ExceptionTranslationFilter:處理 AccessDeniedException 和
AuthenticationException 異常。
(14)FilterSecurityInterceptor:可以看做過濾器鏈的出口。
(15)RememberMeAuthenticationFilter:當用戶沒有登錄而直接訪問資源時, 從 cookie裏找出用戶的信息, 如果 Spring Security 能夠識別出用戶提供的 remember me cookie, 用戶將不必填寫用戶名和密碼, 而是直接登錄進入系統,該過濾器默認不開啓。 
 
4.2 SpringSecurity 基本流程
Spring Security 採取過濾鏈實現認證與授權,只有當前過濾器通過,才能進入下一個
過濾器:

綠色部分是認證過濾器,需要我們自己配置,可以配置多個認證過濾器。認證過濾器可以使用 Spring Security 提供的認證過濾器,也可以自定義過濾器(例如:短信驗證)。認證過濾器要在 configure(HttpSecurity http)方法中配置,沒有配置不生效。下面會重點介紹以下三個過濾器:

UsernamePasswordAuthenticationFilter 過濾器:該過濾器會攔截前端提交的 POST 方式的登錄表單請求,並進行身份認證。

ExceptionTranslationFilter 過濾器:該過濾器不需要我們配置,對於前端提交的請求會直接放行,捕獲後續拋出的異常並進行處理(例如:權限訪問限制)。
 
FilterSecurityInterceptor 過濾器:該過濾器是過濾器鏈的最後一個過濾器,根據資源權限配置來判斷當前請求是否有權限訪問對應的資源。如果訪問受限會拋出相關異常,並由 ExceptionTranslationFilter 過濾器進行捕獲和處理。
 
4.3 SpringSecurity 認證流程
認證流程是在 UsernamePasswordAuthenticationFilter 過濾器中處理的,具體流程如下
所示:

 

對應源碼:https://github.com/oycyqr/springboot-learning-demo/tree/master/springboot-security-token

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章