把玩 Spring Security [1] 先讓一部分動起來
- notes spring-security
看到 Taiwan Backend Group 的朋友分享一個新的 Java 權限認證框架,就打趣地回了:
Spring Security 的吶喊:「你可以討厭我,但請不要討厭其他 Spring 專案」(阿醬語氣)
後端實作認證與權限管理是相當平常的需求,而再多學習一套非 Spring 系列的工具,多半是覺得無法理解 Spring Security 的用法,遭越了許多挫折而尋求新的捷徑。
Spring Security 是一個多數開發者都需要,但總是要一直 Google 查半天還不知道如何上手的 Spring 專案。不知是他的設計過於精巧,亦或他的封裝太過解耦之故。讓想要掌握他的開發者們總是不得其門而入。其實,只要懂弄了一次完整的 HTTP REQUEST 至 HTTP RESPONSE 之間的細節,就能大幅增加對 Spring Security 的理解。
無論你打怎麼驗證使用者或是 API Call 是否擁有權力使用服務,都是以上面這個循序圖的流程來運作的。儘管真實可靠的實作遠比這張圖複雜些,但只要抓住這張圖的精神,你就能掌握如何使用 Spring Security 囉! 作為把玩 Spring Security 系列的第一篇,我們先來玩玩簡單的:驗證。
實作情境
我們先以「極簡」的方式來練習 Spring Security:
- 建立個 Restful API
/
讓大家都能使用/users
只有有權限的使用者能使用
我們要實作一種新的驗證方式,來決定 RESTful API 是不是能使用 /users
取得結果。讓我們先挑簡單的部分做 🤤 這個 SimpleController 正式我們要的 API,接著要把它加點「安全」的成份:
@RestController
public class SimpleController {
@RequestMapping("/")
public String home() {
return "home";
}
@RequestMapping("/users")
public String users() {
return "users";
}
}
若你已經事先加好了 Spring Security 函式庫,那麼 Spring Security 會自動加上預設的 Security Filter,結果就是什麼東西都打不到:
$ curl http://127.0.0.1:8787/
{"timestamp":"2021-09-26T16:08:27.811+00:00","status":401,"error":"Unauthorized","path":"/"}
這時,就要用上我們啟用 Spring Security 的第一個類別 WebSecurityConfigurerAdapter,使用它的 DSL 去設定 Request Filter 的檢查規則:
- 由於我們打算做 API,那直接把 csrf 關掉 (實務上會再加開 cors,讓前端能順利呼叫)
- 針對所有授權的位置全部放行。
@Component
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.anyRequest().permitAll();
}
}
設定完全發現,情況相反了!剛剛是全都打不通,現在是全通了!
$ curl http://127.0.0.1:8787/users
users
這只要稍為修改一下 DSL 就行囉!多加一行 antMatchers
去捕抓 /users
相關 Pattern,令它必需要通過身份驗證:
http.csrf().disable()
.authorizeRequests()
.antMatchers("/users**").authenticated()
.anyRequest().permitAll();
$ curl http://127.0.0.1:8787/users
{"timestamp":"2021-09-26T16:19:04.881+00:00","status":403,"error":"Forbidden","path":"/users"}
製作新的 AuthenticationProvider
接著,我們要設法讓 /users
的 HTTP REQUEST 通過驗證。那麼,我們該怎麼做呢?什麼情況下,Spring Security 會覺得一個 HTTP REQUEST 是可以通過驗證的呢?請看下圖標示:
對於 Spring Security 會將 HTTP REQUEST 交給他有註冊的 Security Filter 處理,只要有任何一個 Security Filter 有設定 Authentication Object,那麼就是通過認證呦!思路就是這麼簡單。
假設,我們在研發一個嶄新的驗證方法,Spring Security 預設的方法都不適合我們,那就來做一個。這個新方法就是「不管你給什麼,我都讓你通過」的驗證方式:
- 做出一個 Security Filter
- 將這個 Filter 加到 Spring Security Filter 鏈之中
這個友善的 Filter,它的重點只有一個,那就是拿到 SecurityContext 並給它 Authentication Object:
public class FriendlyFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
SecurityContextHolder.getContext().setAuthentication(new ApiToken());
filterChain.doFilter(request, response);
}
}
ApiToken 是一實作 Authentication 介面的物件,只要它的 isAuthenticated
是回傳 true
,就會被 Spring Security 認為是通過驗證的:
public boolean isAuthenticated() {
return true;
}
重點摘要
我們透過了簡單的 Restful API 體驗了 Spring Security 的驗證流程,學習到二個重點:
WebSecurityConfigurerAdapter 為設定 Spring Security 的輔助類別
- 利用 HttpSecurity 調整 Security Filter 的作用範圍
- 利用 HttpSecurity 加入新的 Security Filter
對於 Spring Security 來說,只要 SecurityContext 含有標示為 已認證
的 Authentication Object 就會放行:
public boolean isAuthenticated() {
return true;
}
下一篇,我們會繼續目前的範例,探索 Access Control 的使用方式。本篇完整範例請參考: