初探 Spring Boot Starter Security:構建更安全的Spring Boot應用

引言

Spring Boot 作為 Java 生態系統下的熱門框架,以其簡潔和易上手著稱。而在構建 Web 應用程序時,安全性始終是開發者必須重視的一個方面。Spring Boot Starter Security 為開發者提供了一個簡單但功能強大的安全框架,使得實現身份驗證和授權變得相對容易。

本文將帶你深入了解如何使用 Spring Boot Starter Security 來構建一個安全的 Spring Boot 應用,包括基本配置、常見用例以及一些技巧和最佳實踐。

目錄

  1. 什么是 Spring Boot Starter Security?
  2. 初始設置
    • 添加依賴
    • 基本配置
  3. 基本概念
    • 認證與授權
    • Filter 和 SecurityContext
  4. 示例:創建一個簡單的安全應用
    • 設定用戶角色
    • 自定義登錄頁面
    • 基于角色的訪問控制
  5. 高級配置
    • 自定義 UserDetailsService
    • 自定義 Security Configuration
    • 使用 JWT 進行身份驗證
  6. 綜合示例:構建一個完整的安全應用
    • 項目結構
    • 代碼實現
    • 測試和驗證
  7. 最佳實踐與常見問題
    • 安全最佳實踐
    • 常見問題及解決方案
  8. 結論

1. 什么是 Spring Boot Starter Security?

Spring Boot Starter Security 是一個簡化的 Spring Security 集成包,使得我們可以非常容易地在 Spring Boot 應用中添加強大的安全功能。它提供了一套靈活的工具和配置,用于實現認證和授權,使得應用程序更加安全。

2. 初始設置

添加依賴

首先,我們需要在 pom.xml 文件中添加 Spring Boot Starter Security 的依賴:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>

基本配置

在添加依賴后,Spring Security 會自動為我們的應用添加一些默認的安全配置,例如 HTTP Basic Authentication(基于 HTTP 的基礎身份驗證)。這意味著,我們可以立即看到應用要求用戶進行身份驗證。

@SpringBootApplication
public class SecurityApplication {public static void main(String[] args) {SpringApplication.run(SecurityApplication.class, args);}
}

此時,運行應用后,您會看到 Spring Boot 自動生成了一個密碼,并在控制臺輸出。

3. 基本概念

認證與授權

  • 認證(Authentication):驗證用戶的身份。
  • 授權(Authorization):確定用戶是否有權訪問某個資源。

Filter 和 SecurityContext

Spring Security 通過一系列的過濾器(Filters)來處理安全邏輯。這些過濾器會攔截每個請求,并應用相應的認證和授權邏輯。所有安全相關的信息都會被存儲在 SecurityContext 中,從而使得后續的請求處理可以基于這些信息進行訪問控制。

4. 示例:創建一個簡單的安全應用

設定用戶角色

我們可以通過創建一個配置類來設定用戶角色:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.inMemoryAuthentication().withUser("user").password(passwordEncoder().encode("password")).roles("USER").and().withUser("admin").password(passwordEncoder().encode("admin")).roles("ADMIN");}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/admin/**").hasRole("ADMIN").antMatchers("/user/**").hasRole("USER").and().formLogin();}
}

在上面的配置中,我們創建了兩個用戶(user 和 admin),并且設置了不同的角色(USER 和 ADMIN)。此外,我們還定義了不同 URL 路徑對應的訪問權限。

自定義登錄頁面

我們可以自定義一個登錄頁面,以增強用戶體驗:

<!DOCTYPE html>
<html>
<head><title>Login Page</title>
</head>
<body><h2>Login</h2><form method="post" action="/login"><div><label>Username: </label><input type="text" name="username"></div><div><label>Password: </label><input type="password" name="password"></div><div><button type="submit">Login</button></div></form>
</body>
</html>

WebSecurityConfig 中,我們需要指定這個自定義登錄頁面:

@Override
protected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/admin/**").hasRole("ADMIN").antMatchers("/user/**").hasRole("USER").and().formLogin().loginPage("/login").permitAll();
}

基于角色的訪問控制

上述配置已經體現了基于角色的基本訪問控制。我們規定了 /admin/** 路徑只能由擁有 ADMIN 角色的用戶訪問,而 /user/** 路徑只能由擁有 USER 角色的用戶訪問。

5. 高級配置

自定義 UserDetailsService

有時候,我們需要從數據庫加載用戶信息。我們可以通過實現 UserDetailsService 接口來自定義加載用戶的邏輯:

@Service
public class CustomUserDetailsService implements UserDetailsService {@Autowiredprivate UserRepository userRepository;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {User user = userRepository.findByUsername(username);if (user == null) {throw new UsernameNotFoundException("User not found.");}return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), AuthorityUtils.commaSeparatedStringToAuthorityList(user.getRoles()));}
}

自定義 Security Configuration

除了基本配置外,有些時候我們需要更靈活的配置。例如,我們可以完全覆蓋默認的 Spring Security 配置:

@Configuration
@EnableWebSecurity
public class CustomSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate CustomUserDetailsService userDetailsService;@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.csrf().disable().authorizeRequests().anyRequest().authenticated().and().formLogin().loginPage("/login").permitAll().and().logout().permitAll();}
}

使用 JWT 進行身份驗證

JWT(JSON Web Token)是一種更加輕便的授權機制,我們可以采用它來替代 Session Cookie 進行身份驗證。實現 JWT 需要進行以下幾步:

  1. 添加 jwt 相關的依賴;
  2. 創建 token 提供者;
  3. 創建過濾器來驗證 token ;
添加 JWT 依賴

pom.xml 中添加以下依賴:

<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.1</version>
</dependency>
創建 TokenProvider
@Component
public class TokenProvider {private final String jwtSecret = "yourSecretKey";private final long jwtExpirationMs = 3600000;public String generateToken(Authentication authentication) {String username = authentication.getName();Date now = new Date();Date expiryDate = new Date(now.getTime() + jwtExpirationMs);return Jwts.builder().setSubject(username).setIssuedAt(now).setExpiration(expiryDate).signWith(SignatureAlgorithm.HS512, jwtSecret).compact();}public String getUsernameFromToken(String token) {return Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(token).getBody().getSubject();}public boolean validateToken(String authToken) {try {Jwts.parser().setSigningKey(jwtSecret).parseClaimsJws(authToken);return true;} catch (SignatureException | MalformedJwtException | ExpiredJwtException | UnsupportedJwtException | IllegalArgumentException e) {e.printStackTrace();}return false;}
}
創建 JWT 過濾器
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {@Autowiredprivate TokenProvider tokenProvider;@Autowiredprivate CustomUserDetailsService customUserDetailsService;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)throws ServletException, IOException {try {String jwt = getJwtFromRequest(request);if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {String username = tokenProvider.getUsernameFromToken(jwt);UserDetails userDetails = customUserDetailsService.loadUserByUsername(username);UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));SecurityContextHolder.getContext().setAuthentication(authentication);}} catch (Exception ex) {logger.error("Could not set user authentication in security context", ex);}filterChain.doFilter(request, response);}private String getJwtFromRequest(HttpServletRequest request) {String bearerToken = request.getHeader("Authorization");if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {return bearerToken.substring(7);}return null;}
}
調整 Security Configuration
@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate JwtAuthenticationFilter jwtAuthenticationFilter;@Overrideprotected void configure(HttpSecurity http) throws Exception {http.cors().and().csrf().disable().authorizeRequests().antMatchers("/login", "/signup").permitAll().anyRequest().authenticated();http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);}
}

6. 綜合示例:構建一個完整的安全應用

接下里,我們將創建一個功能更全的示例應用,結合之前介紹的各種配置,實現用戶注冊、登錄、基于角色的訪問控制和 JWT 身份驗證。

項目結構

src└── main├── java│    └── com.example.security│         ├── controller│         ├── model│         ├── repository│         ├── security│         ├── service│         └── SecurityApplication.java└── resources├── templates└── application.yml

代碼實現

模型類
@Entity
public class User {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String username;private String password;private String roles;  // e.g., "USER, ADMIN"// getters and setters
}
Repository
@Repository
public interface UserRepository extends JpaRepository<User, Long> {User findByUsername(String username);
}
UserDetailsService 實現
@Service
public class CustomUserDetailsService implements UserDetailsService {@Autowiredprivate UserRepository userRepository;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {User user = userRepository.findByUsername(username);if (user == null) {throw new UsernameNotFoundException("User not found.");}return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), AuthorityUtils.commaSeparatedStringToAuthorityList(user.getRoles()));}
}
安全配置
@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate JwtAuthenticationFilter jwtAuthenticationFilter;@Autowiredprivate CustomUserDetailsService customUserDetailsService;@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(customUserDetailsService).passwordEncoder(new BCryptPasswordEncoder());}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.cors().and().csrf().disable().authorizeRequests().antMatchers("/login", "/signup").permitAll().anyRequest().authenticated();http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);}
}
控制器
@RestController
public class AuthController {@Autowiredprivate AuthenticationManager authenticationManager;@Autowiredprivate CustomUserDetailsService userDetailsService;@Autowiredprivate TokenProvider tokenProvider;@PostMapping("/login")public ResponseEntity<?> authenticateUser(@RequestBody LoginRequest loginRequest) {Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(loginRequest.getUsername(),loginRequest.getPassword()));SecurityContextHolder.getContext().setAuthentication(authentication);String jwt = tokenProvider.generateToken(authentication);return ResponseEntity.ok(new JwtAuthenticationResponse(jwt));}@PostMapping("/signup")public ResponseEntity<?> registerUser(@RequestBody SignUpRequest signUpRequest) {if(userRepository.existsByUsername(signUpRequest.getUsername())) {return new ResponseEntity<>(new ApiResponse(false, "Username is already taken!"), HttpStatus.BAD_REQUEST);}// Creating user's accountUser user = new User();user.setUsername(signUpRequest.getUsername());user.setPassword(passwordEncoder.encode(signUpRequest.getPassword()));user.setRoles("USER");userRepository.save(user);return ResponseEntity.ok(new ApiResponse(true, "User registered successfully"));}
}

測試和驗證

我們已經完成了一個簡單但是功能齊全的 Spring Boot 安全應用。可以通過以下步驟進行測試和驗證:

  1. 啟動應用
  2. 通過 /signup 端點進行用戶注冊
  3. 通過 /login 端點進行用戶登錄,并獲取 JWT token
  4. 使用獲取的 JWT token 訪問其他受保護的端點

7. 最佳實踐和常見問題

安全最佳實踐

  • 使用強加密算法:如 BCryptPasswordEncoder 對密碼進行加密存儲。
  • 避免硬編碼密碼或密鑰:將敏感信息存儲在安全的配置文件或環境變量中。
  • 啟用 CSRF 保護:對于需要借助表單提交的應用保持 CSRF 保護。
  • 定期更新依賴:檢查依賴庫的安全更新,避免使用有已知漏洞的庫。
  • 輸入驗證:在用戶輸入點進行嚴格的輸入驗證,防止XSS和SQL注入等攻擊。

常見問題及解決方案

問題1:為什么自定義登錄頁面不顯示?

解決方案:確保在 WebSecurityConfig 中設置了 .loginPage("/login").permitAll(); 并且路徑正確。

問題2:身份驗證失敗,顯示 “Bad credentials”。

解決方案:確認用戶名和密碼是否正確,以及整體加密方式一致。

問題3:為什么 JWT 從請求中提取失敗?

解決方案:確認請求頭格式是否正確,Authorization: Bearer <token>,并且確保 JWT 過濾器在安全配置中正確添加。

結論

Spring Boot Starter Security 為開發者提供了豐富且靈活的安全配置選項,使得安全性實現變得相對簡單。在本文中,我們探討了基本概念和常見用例,并通過構建一個完整的示例應用,展示了其強大的功能。希望這些內容能幫助你在構建安全的 Spring Boot 應用時游刃有余。

通過對 Spring Boot Starter Security 的深入了解和實踐,我們不僅增強了應用的安全性,還為用戶提供了更為可靠的使用體驗。繼續學習和實踐,你將在開發和維護安全應用的道路上走得更遠。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/15983.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/15983.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/15983.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

從動態代理角度簡單理解Spring AOP

1. 概述 動態代理 是指在運行時&#xff0c;動態地創建目標類的代理對象&#xff0c;并對其中特定的方法進行攔截或增強的技術。這種技術主要用于在不修改目標類代碼的情況下&#xff0c;增強目標類的功能。 在Java中&#xff0c;動態代理主要基于Java的反射機制和接口來實現…

gdc2024:Raytracing in Snowdrop技術實現與性能優化策略

在今年的GDC&#xff08;游戲開發者大會&#xff09;的Advanced Graphics Summit上&#xff0c;關于Snowdrop引擎中光線追蹤技術的討論引起了廣泛關注。 一、光線追蹤全局照明的實現細節 屏幕空間追蹤&#xff1a; 屏幕空間追蹤從相機出發&#xff0c;對屏幕上的每個像素點生成…

DDL—表—數據類型—字符串類型相關語法

&#xff08;1&#xff09;表格可視化 普通字符串 類型大小描述CHAR0~255 bytes定長字符串&#xff0c;其表示即使你存儲一個字符&#xff0c;它也會占用你括號里個數的字符的空間&#xff0c;因為未占用的字符的其它空間會用空格進行補位。需要再后面跟一個參數&#xff1a;…

harmony 鴻蒙ArkUI動畫/交互事件開發常見問題(ArkTS)

ArkUI動畫/交互事件開發常見問題(ArkTS) 焦點事件onBlur/onFocus回調無法觸發(API 9) 問題現象 焦點事件onBlur/onFocus回調無法觸發 解決措施 焦點事件默認情況下需要外接鍵盤的Tab鍵&#xff0c;或方向鍵觸發&#xff0c;點擊觸發焦點事件需要添加焦點控制屬性focusOnTo…

Linux 監控USB硬盤插拔事件并自動掛載和卸載

定義udev規則來監控USB插拔事件。 一、在/etc/udev/rules.d目錄下隨意創建一個規則文件&#xff0c;例&#xff1a;99-usb-mount.rules KERNEL"sd[b-z]?",SUBSYSTEM"block",RUN"/usr/local/src/mountusb.sh %k $env{ACTION}"二、創建 /usr/lo…

基于arm64架構國產操作系統|Linux下的RTMP|RTSP低延時直播播放器開發探究

技術背景 2014年4月8日起&#xff0c;美國微軟公司停止了對Windows XP SP3操作系統提供服務支持&#xff0c;這引起了社會和廣大用戶的廣泛關注和對信息安全的擔憂。而2020年對Windows7服務支持的終止再一次推動了國產系統的發展。工信部對此表示&#xff0c;將繼續加大力度&a…

C++ 紅黑樹

目錄 1.紅黑樹的概念 2.紅黑樹的性質 3.紅黑樹節點的定義 4.紅黑樹的插入操作 5.數據測試 1.紅黑樹的概念 紅黑樹&#xff0c;是一種二叉搜索樹&#xff0c;但在每個結點上增加一個存儲位表示結點的顏色&#xff0c;可以是Red或Black。 通過對任何一條從根到葉子的路徑上各個…

C++基礎與深度解析 | 泛型算法 | bind | Lambda表達式

文章目錄 一、泛型算法1.泛型算法的分類2.迭代器分類 二、bind與lambda表達式1.bind2.lambda表達式 三、泛型算法的改進--ranges(c20) 一、泛型算法 C中的泛型算法是標準模板庫&#xff08;STL&#xff09;的一部分&#xff08;這里重點討論 C 標準庫中定義的算法&#xff0c;而…

【vue-cli搭建vue項目的過程2.x】

vue-cli搭建vue項目 vue-cli搭建vue項目安裝node安裝vue-cli腳手架并創建項目安裝 Ant Design Vue或element-ui(筆者使用Ant-design-vue組件&#xff0c;并全局引入)開發安裝三方庫包1、Package.json文件---引入如下package.json文件執行npm i或npm install命令即可下載如下依賴…

數據結構~~鏈式二叉樹

目錄 一、基本概念 鏈式存儲概念 二、鏈式二叉樹的結構 鏈式二叉樹結構 構建鏈式二叉樹 二叉樹的遍歷 二叉樹節點和高度等 二叉樹銷毀 三、鏈式二叉樹的練習 相同的樹 對稱二叉樹 另外一顆子樹 二叉樹前序遍歷 二叉樹遍歷 四、完整代碼 Tree.h Tree.c 五、總結 一…

Linux服務升級:Predixy 升級代理 Redis-cluster 集群

目錄 一、實驗 1.環境 2. 啟動Redis服務 3.Predixy 升級代理 Redis-cluster 集群 二、問題 1. Predixy進行set操作報錯 2.如何創建腳本啟動predixy 3.Redis代理對比 一、實驗 1.環境 &#xff08;1&#xff09;主機 表1 主機 系統版本節點軟件IP備注CentOS7.9Redis…

Springboot開發 -- Postman 調試類型詳解

引言 在 Spring Boot 應用開發過程中&#xff0c;接口測試是必不可少的一環。Postman 作為一款強大的 API 開發和測試工具&#xff0c;可以幫助開發者輕松構建、測試和管理 HTTP 請求。本文將為大家介紹如何在 Spring Boot 開發中使用 Postman 進行接口測試。 一、準備工作 安…

C/C++|malloc分配內存詳解

看本節前&#xff0c;希望讀者有linux內存分布的基本概念&#xff0c;可以閱讀這篇文章&#xff1a; 進程虛擬地址空間和函數調用棧 在本節中希望讀者可以一口氣閱讀完所有內容。 本博客內容全部來自小林coding&#xff1a;malloc 是如何分配內存的&#xff1f; 這里僅為筆記記…

Python-圖片旋轉360,保存對應圖片

#Author &#xff1a;susocool #Creattime:2024/5/25 #FileName:turn360 #Description: 會旋轉指定的圖像文件360度&#xff0c;并將每個旋轉后的圖像保存到指定目錄&#xff0c;文件名以旋轉角度命名。 from PIL import Imagedef rotate_and_save(image_path, output_dir) :# …

Linux/Ubuntu 中安裝 ZeroTier,實現內網穿透,2分鐘搞定

相信很多人都有遠程連接家中設備的需求&#xff0c;如遠程連接家中的NAS、Windows等服務&#xff0c;所以會涉及到一個內網穿透工具的使用&#xff0c;如果沒有公網IP的情況下&#xff0c;推薦大家使用ZeroTier&#xff0c;這是一款強大的內網穿透工具。 mac和windows版的操作…

Nginx-狂神說

Nginx概述 公司產品出現瓶頸&#xff1f; 我們公司項目剛剛上線的時候&#xff0c;并發量小&#xff0c;用戶使用的少&#xff0c;所以在低并發的情況下&#xff0c;一個jar包啟動應用就夠了&#xff0c;然后內部tomcat返回內容給用戶。 但是慢慢的&#xff0c;使用我們平臺…

HTTP 各版本差異

http1.0 它的特點是每次請球和響應完畢后都會銷毀TCP 連接。同時規走前一個響應完成后才發送下一個請求。這樣做有兩個問題&#xff1a; 無法復用連接了。 每次請求都要創建新的TCP連接&#xff0c;完成三次握手和四次揮手。網絡利用率低 隊頭阻塞 如果前一個請求被某種原因阻…

K8S認證|CKA題庫+答案| 13. sidecar 代理容器日志

目錄 13、使用 sidecar 代理容器日志 CKA v1.29.0模擬系統免費下載試用&#xff1a; 題目&#xff1a; 開始操作&#xff1a; 1&#xff09;、切換集群 2&#xff09;、生成yaml文件 3&#xff09;、官網找模板 4&#xff09;、編輯yaml文件 5&#xff09;、應用yaml…

車載電子電器架構 —— 智能座艙技術

車載電子電器架構 —— 智能座艙技術 我是穿拖鞋的漢子&#xff0c;魔都中堅持長期主義的汽車電子工程師。 老規矩&#xff0c;分享一段喜歡的文字&#xff0c;避免自己成為高知識低文化的工程師&#xff1a; 屏蔽力是信息過載時代一個人的特殊競爭力&#xff0c;任何消耗你的…

qt multiple definition of 報錯解決

qt編譯報了很多錯&#xff0c; multiple definition of xxx 原來一維設計文件ui 的問題 后來發現是pro中頭文件和cpp文件重寫了&#xff0c;導致重復編譯報的錯 解決方法&#xff1a;把重復的頭文件和cpp文件刪了就可以了。