昨日設計Security會根據 admin及member路由來選擇UserDetialService的邏輯,但是目前還有遺漏的部分,目前UserAuthFilter的驗證邏輯
以及Security的配置會根據URL規則,決定要驗證的項目,目前做的驗證只有「你有沒有登入」,換句話說,只要你攜帶的Token可以使用,滿足能登入的條件,就能在管理後台跟使用者前台,進行請求操作
安全性來說這樣是不夠的,這個問題可以套用Spring Security提供的角色驗證機制解決,在使用者登入後,綁定管理者或前台會員角色,今天來修正未檢查正確角色的問題
正式開始前先來了解過濾器鍊
這張圖說明了 Spring Security預設的過濾鍊,實際上可以分為兩大核心步驟,身分驗證以及授權,先前的Securtiy配置,會在身分驗證前執行自訂的 UserAuthFilter,根據實際邏輯登入驗證成功就將當前請求使用者註明通過身分驗證
並且在配置的規則中並沒有要求角色檢查,因此不會觸發 FilterSecurityInteceptor 這個過濾器,針對角色或權限進行驗證
現在方向很明朗,今日修復驗證問題的流程
將先前執行的 authenticated 改為 hasRole,表示需根據當前使用者,驗證ROLE符合規則才可放行
authorize
.requestMatchers("/api/admin/login").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/api/member/login").permitAll()
.requestMatchers("/api/member/register").permitAll()
.requestMatchers("/api/member/**").hasRole("MEMBER")
.anyRequest().permitAll();
作法很簡單,在User類別建構子的第三個參數,輸入相應ROLE即可
注意: User類別路徑是 org.springframework.security.core.userdetails.User
按照規定 第三個參數使用的項目,必須實作介面 GrantedAuthority,表示授權的物件
調整 DBUserDetailsService.java
@Override
public UserDetails loadUserByUsername(String account) throws UsernameNotFoundException {
Member member = memberMapper.getByAccount(account);
if (member == null) {
throw new UsernameNotFoundException("Member not found");
}
/**
* 添加這段,表示回傳的User屬於 MEMBER角色
* 要注意必須用 ROLE_當前綴,後面補上角色名稱
*/
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ROLE_MEMBER"));
return new User(
member.getAccount(),
member.getPassword(),
authorities
);
}
調整 AdminUserDetailsService.java
@Override
public UserDetails loadUserByUsername(String account) throws UsernameNotFoundException {
Admin admin = adminMapper.getByAccount(account);
if (admin == null) {
throw new UsernameNotFoundException("Admin Not Found");
}
// 添加這段
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
return new User(
admin.getAccount(),
admin.getPassword(),
authorities);
}
AdminController 新增取得當前 User API
// /api/admin/me
@GetMapping("/me")
public ResponseEntity<Object> me() {
// 從 Context取得當前登入 User(UserDetails)
Authentication authUser = SecurityContextHolder.getContext().getAuthentication();
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(Map.of(
"authorities", authUser.getAuthorities(),
"account", authUser.getName()));
}
用Postman登入取得Token,請求 /api/admin/me 成功拿到預期內容