本篇文章會將剩下的不會完成,最後會使用Postman 測試整個後端系統
這裡使用到 HttpServletRequest
和 HttpServletResponse
,這兩個可以在用戶發起請求時,獲取所有相關的資料,包括:「用戶 IP、用戶請求的方法...」,詳細的項目有附在程式碼裡面
在 介紹 JWT 的文章中有提到 JWT 的組成,在這裡我們便是將 JWT 慢慢拆解,從標頭的 Authorization,接著將會放在 JWT 前面的 Bearer 拿掉,剩下的內容就會去比對資料庫中的資料,如果都是正常的那麼這個用戶發起的請求就會讓他繼續執行下去,但是如果有任何一個地方有問題,就會馬上被 Filter給篩掉
@Slf4j
@Component // 標記為 Spring 組件,以便在 Spring 容器中進行管理和自動裝配
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtService jwtService;
private final UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(
//標記為不可為空值
//request、response會在使用者發起HTTP請求時自動獲取相關資料
@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain
) throws ServletException, IOException {
// 從 HTTP 請求中獲取 Authorization 頭部
//範例: Authorization: Bearer eyJhbGciOiJIUzI1NiIsI...
final String authHeader = request.getHeader("Authorization");
//用來存取Bearer後面的JWT(JSON WEB TOKEN)
final String jwt;
//存取JWT解析後的userEmail
final String userEmail;
// log.info("requestURL :" + request.getRequestURL().toString());
// log.info("requestURI :" + request.getRequestURI());
// log.info("URL地址中附帶的參數 :" + request.getQueryString());
// log.info("來訪者的IP位址 :" + request.getRemoteAddr());
// log.info("RemoteUser :" + request.getRemoteUser());
// log.info("來訪者主機名 :" + request.getRemoteHost());
// log.info("來訪者Port號 :" + request.getRemotePort());
// log.info("請求URL地址用的方法 :" + request.getMethod());
// log.info("PathInfo :" + request.getPathInfo());
// log.info("Web服務端IP地址 :" + request.getLocalAddr());
// log.info("Web服務端主機名 :" + request.getLocalName());
// 檢查 Authorization 頭部是否存在且格式正確
if (authHeader == null || !authHeader.startsWith("Bearer ")){
// 如果不符合,則直接將目前的請求傳遞給下一個過濾器
filterChain.doFilter(request, response);
return;
}
// 從 Authorization 頭部中提取 JWT token
//範例: Authorization: Bearer eyJhbGciOiJIUzI1NiIsI...
//擷取索引值為7之後的資料,因此會將「 Bearer 」這一段去掉,留下剩下的JWT
jwt = authHeader.substring(7);
// 使用 JwtService 從 token 中提取用戶名
userEmail = jwtService.extractUserName(jwt);
// 檢查用戶是否已經被驗證
if (userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null){
// 加載用戶詳細信息
UserDetails userDetails = this.userDetailsService.loadUserByUsername(userEmail);
// 驗證 token 是否有效
if (jwtService.isTokenValid(jwt, userDetails)){
// 創建身份驗證令牌:
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
//用戶資料
userDetails,
//額外辨識憑證
null,
//用戶角色權限
userDetails.getAuthorities()
);
// 設置用戶的身份驗證詳細訊息
authToken.setDetails(
new WebAuthenticationDetailsSource().buildDetails(request)
);
// 將身份驗證令牌設置到 SecurityContext
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
// 繼續處理請求
filterChain.doFilter(request, response);
}
}
在這裡我們要做的工作就是規定用戶的角色權限,比如只有admin可以建立、刪除,user只能查看資料等等,也就是白名單的功能
可以注意到白名單路徑的部分,路徑是寫成 "/api/auth/**"
,那個 ** 的意思,就是指 /apu/auth 底下的所有路徑
@Configuration // 表示此類為配置類
@EnableWebSecurity // 啟用 Spring Security 的 Web 安全支持
@RequiredArgsConstructor // Lombok 注解,自動生成含有所有 final 字段的構造函數
public class SecurityConfiguration {
//添加白名單,在這個路徑底下的要求都不用Token
private static final String WHITE_LIST_URL = "/api/auth/**";
//// 可以將所有不須經過Token的路徑用List的方式一次輸入
// private static final String[] WHITE_LIST_URL = {"/api/v1/auth/**",
// "api/v1/user/**"};
private final JwtAuthenticationFilter jwtAuthFilter; // JWT 身份驗證過濾器
private final AuthenticationProvider authenticationProvider; // 自定義身份驗證提供者
@Bean // 定義一個 Bean,Spring 容器會管理其生命周期
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable) // 禁用 CSRF 防護,CSRF防護要求用戶在請求的時候需要將創建時的Token一起傳入,經過認證後才可以使用請求
.authorizeHttpRequests(req -> // 開始配置請求的授權規則
req.requestMatchers(WHITE_LIST_URL) // 對於白名單的 URL
.permitAll() // 允許所有請求,不需要Token即可使用
//針對特定的URL設定需要的權限
.requestMatchers(DELETE,"api/v1/user/**").hasAnyAuthority("admin") // 只有 admin 權限可以執行 DELETE 操作
.requestMatchers(POST,"api/v1/user/**").hasAnyAuthority("admin") // 只有 admin 權限可以執行 POST 操作
.requestMatchers(PUT,"api/v1/user/**").hasAnyAuthority("admin") // 只有 admin 權限可以執行 PUT 操作
.requestMatchers(GET,"api/v1/user/**").hasAnyAuthority("admin", "user") // admin 和 user 權限都可以執行 GET 操作
//任何請求代表其他沒有特別設定權限的位置
.anyRequest() // 任何其他請求
.authenticated() // 都需要通過身份驗證
)
.sessionManagement(session -> session.sessionCreationPolicy(STATELESS)) // 設置為無狀態會話
.authenticationProvider(authenticationProvider) // 設置自定義的身份驗證提供者
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class); // 在 UsernamePasswordAuthenticationFilter 之前添加 JWT 過濾器
return http.build(); // 建立並返回 SecurityFilterChain 實例
}
}