昨天我們談到 Cookie 與 Session 的原理:
今天,我們就用 Spring Boot Security 來實作一次,看看 JSESSIONID 是如何在背後幫我們完成「使用者記住狀態」的。
Spring boot需要注入依賴,基本上就是匯入別人已經寫好的模組,我們這裡需要匯入的就是Spring boot Security,去使用裡面的功能,詳細可以參考我的這個Github
https://github.com/AnsathSean/spring-security-30days/tree/day06-CookieBased-auth
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
記得也要匯入最基本的Web模式,大家可以自己去自己的Pom確認有沒有這兩個dependency,
專案的基本設定很簡單,基本上就是這三份程式碼
主程式入口(不做任何修改):
SpringSecurity30daysApplication.java
package com.ansathsean.security;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringSecurity30daysApplication {
public static void main(String[] args) {
SpringApplication.run(SpringSecurity30daysApplication.class, args);
}
}
接著,我們要開始建立一個簡單的 API,測試登入前後的差異。
package com.ansathsean.security;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import jakarta.servlet.http.HttpSession;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
@RestController
public class Controller {
@GetMapping("/")
public String home() {
return "Welcome to iT鐵人賽系列文!";
}
@GetMapping("/hello")
public String hello() {
return "Hello, you are authenticated with Session!";
}
@GetMapping("/public")
public String publicPage() {
return "這也是公開 API";
}
@GetMapping("/user")
public String userPage() {
return "這是 USER 登入後才可以看到的頁面";
}
@GetMapping("/admin")
public String adminPage() {
return "這是 ADMIN 才能看到的頁面";
}
@GetMapping("/me")
public String getSessionInfo(HttpSession session) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
return "SessionId: " + session.getId() +
" | User: " + auth.getName() +
" | Roles: " + auth.getAuthorities();
}
}
/
→ 公開 API(任何人都能存取)/public
→ 公開 API(任何人都能存取)受保護 AI(需要登入 Session 才能存取)/user
→受保護 AI(需要user登入 Session 才能存取)/admin
→受保護 AI(需要admin登入 Session 才能存取)Spring boot Security本身就是讓大家快速建立授權跟認證而使用的,如果要使用Spring boot Security的套件,我們就要先建立SecurityConfig,然後在裡面設定我們要保護的API,跟我們要使用的加密方法,以及我們的帳號密碼。在這個階段為了展示,我們簡化了帳號密碼設定,將密碼直些寫到設定檔中,如果要進階使用的話,就要拆開來,把帳密存到DB裡面取用。
package com.ansathsean.security;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/public").permitAll()
.requestMatchers("/user").hasRole("USER")
.requestMatchers("/admin").hasRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults()) // 啟用表單登入
.logout(Customizer.withDefaults()) // 登出
.csrf(csrf -> csrf.disable());
// .csrf(Customizer.withDefaults()); // 啟用 CSRF 防護
return http.build();
}
@Bean
UserDetailsService userDetailsService(PasswordEncoder passwordEncoder) {
return new InMemoryUserDetailsManager(
User.withUsername("user")
.password(passwordEncoder.encode("password"))
.roles("USER")
.build(),
User.withUsername("admin")
.password(passwordEncoder.encode("admin123"))
.roles("ADMIN")
.build()
);
}
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
這裡我們做了幾件事:
/
API 可以匿名訪問,/user
、/admin
API 需要登入。/login
頁面)。接著讓我們啟動專案。
啟動專案後,打開 Postman 測試:
http://localhost:8080/
直接回應:
Welcome to iT鐵人賽系列文!
/user
(未登入)
/login
。從Postman裡面會看到這些內容,如果從網頁瀏覽器的話就會看到這個登入畫面,代表我們需要登入才有權限看到後面的東西
/login
我們在 Body 帶入帳密,在PostMan中記得把方法改成Post:
username=user
password=password
- 登入成功後,伺服器回傳 Header,而這個就是我們的Session cookie:
```
Set-Cookie: JSESSIONID=xxxxxx
```
/user
(已登入)
JSESSIONID
,我們不需要再使用body的帳號密碼,記得要改回Get我們在controller中開發了一個/me的功能,讓我們可以從這個API中取得我們的Session資訊,我們接著輸入這個網址,就能夠拿到以下內容,他會呈現我們的SessionID,以及我們的User名稱、User的腳色資訊。
這就是 Session ID,它讓伺服器能辨識「這個人已經登入過了」。
優點:
限制:
這也是為什麼後來會發展出 Token / JWT,把狀態轉移到客戶端,減輕伺服器負擔。
今天我們透過 Spring Boot Security 成功體驗了 Cookie + Session 機制:
JSESSIONID
明天,我們要進入 Token Authentication(概念篇),來看看為什麼在分散式架構與 API 服務中,Token 會比 Session 更方便、更彈性。
大家可以慢慢吸收,慢慢了解,我們就是慢慢等,慢慢等,等紅燈,變綠燈~
我們明天見!