session, cookies & jwt
Tung Bui Thanh (TungDaDev)9 mins read

Làm web thì sớm muộn gì cũng phải tính chuyện "nhớ" người dùng: ai đang đăng nhập, có quyền gì. Ba cách hay gặp nhất là Session, Cookies và JWT (JSON Web Token). Mỗi cách một kiểu, có cái hay cái dở riêng, dùng cái nào thì tuỳ bài toán.
Bài này mình so sánh ba thằng: chạy ra sao, khác nhau chỗ nào, kèm code Java (Spring Boot) minh hoạ. Nói trước một chút cho khỏi lẫn: ba cái này không cùng tầng. Cookie là cơ chế để trình duyệt cất dữ liệu và tự gửi lại cho server, còn Session và JWT là hai cách giữ trạng thái đăng nhập. Session gần như luôn đi nhờ cookie, JWT thì có thể nằm trong header hoặc cũng nằm trong cookie.
Bắt đầu thôi :)))
# session
Session là chỗ server cất tạm thông tin của người dùng để nhớ họ trong suốt phiên làm việc. Người dùng vào trang, server tạo một session mới và lưu vào đó những thứ như thông tin đăng nhập, quyền hạn...
Cách hoạt động của Session:
- Người dùng vào trang, server tạo một session và giữ thông tin người dùng ở phía server.
- Server sinh ra một session ID duy nhất rồi gửi về trình duyệt qua cookie.
- Mỗi request sau đó, trình duyệt gửi kèm session ID trong cookie. Server nhìn ID là biết ai, lôi thông tin đã lưu ra dùng.
Ưu điểm:
- Dữ liệu nằm trên server nên an toàn hơn, client không giữ thông tin nhạy cảm nào.
- Server muốn sửa, muốn xoá dữ liệu session của ai cũng dễ.
Nhược điểm:
- Người dùng đông thì server phải nhớ nhiều, tốn tài nguyên.
- Server tắt hoặc sập là session có thể bay sạch, nếu session chỉ nằm trong RAM (mặc định của Tomcat).
- Chạy nhiều instance sau load balancer thì request sau có thể rơi vào máy không có session. Hai cách hay dùng là sticky session (load balancer luôn đẩy một người dùng về cùng một máy) hoặc cất session ra chỗ dùng chung như Redis, ví dụ dùng Spring Session.
Ví dụ:
Trong Spring Boot, cookie mang session ID mặc định tên là JSESSIONID và có cờ HttpOnly, JavaScript trên trang không đọc được. Ví dụ dưới chỉ để minh hoạ: login thật thì phải là POST, có kiểm tra mật khẩu, và nên đổi session ID ngay sau khi đăng nhập (request.changeSessionId()) để chặn kiểu tấn công session fixation. Dùng Spring Security thì phần đổi ID này nó làm sẵn.
@RestController
public class SessionController {
@GetMapping("/login")
public String login(HttpSession session) {
session.setAttribute("username", "cristiano_ronaldo");
return "User logged in and session started";
}
@GetMapping("/profile")
public String getProfile(HttpSession session) {
String username = (String) session.getAttribute("username");
return "Welcome, " + username;
}
}# cookies
Cookies là mấy mẩu dữ liệu nhỏ trình duyệt giữ giùm, có thể chứa thông tin người dùng hoặc trạng thái phiên làm việc. Cookie được dùng khắp nơi: lưu thông tin xác thực, ngôn ngữ trang, tuỳ chọn cá nhân... mà server không cần giữ gì.
Cách hoạt động của Cookies:
- Người dùng vào trang, server có thể gửi kèm một cookie về trình duyệt.
- Trình duyệt cất cookie đó và tự gửi lại trong mọi request sau tới server. Cookie có thể đặt thời hạn, hết hạn là tự mất.
Ưu điểm:
- Server nhẹ gánh vì không phải lưu gì.
- Client đọc được dữ liệu trong cookie ngay, không cần gọi lên server.
Nhược điểm:
- Cookie nằm trên máy người dùng nên họ sửa thoải mái. Ví dụ bên dưới mà dùng cookie
usernameđể biết ai đang đăng nhập thì ai cũng tự đổi thành người khác được. Dữ liệu cần tin tưởng thì phải ký (để phát hiện bị sửa) hoặc chỉ cất một ID ngẫu nhiên như session ID. - Cookie có thể bị lấy cắp. Nên bật
HttpOnlyđể JavaScript không đọc được (chống XSS lấy cookie),Secuređể chỉ gửi qua HTTPS, vàSameSiteđể hạn chế bị gửi kèm từ trang khác (CSRF). - Mỗi cookie chỉ chứa được khoảng 4KB, dữ liệu lớn thì không nhét vào được.
Ví dụ:
@RestController
public class CookieController {
@GetMapping("/set-cookie")
public String setCookie(HttpServletResponse response) {
Cookie cookie = new Cookie("username", "cristiano_ronaldo");
cookie.setMaxAge(60 * 60); // Cookie tồn tại trong 1 giờ
response.addCookie(cookie);
return "Cookie has been set";
}
@GetMapping("/get-cookie")
public String getCookie(@CookieValue(value = "username", defaultValue = "Guest") String username) {
return "Hello, " + username;
}
}# jwt - json web token
JWT (JSON Web Token) là cách xác thực và trao đổi thông tin giữa các bên, rất hợp với hệ thống phân tán. Một JWT gồm ba phần: header, payload, và signature, nối với nhau bằng dấu chấm. Header và payload chỉ là JSON mã hoá Base64URL chứ không phải mã hoá bí mật: ai cầm token cũng decode ra đọc được, nên đừng nhét mật khẩu hay dữ liệu nhạy cảm vào. Phần signature mới là thứ giúp server biết token không bị sửa. Nó hay được dùng cho RESTful API và các hệ thống phân tán, nơi nhiều service cần chia sẻ thông tin người dùng với nhau.
Cách hoạt động của JWT:
- Đăng nhập thành công thì server tạo một
JWTchứa thông tin người dùng (thường là ID và quyền). JWTđược trả về client, client tự cất (thường trong localStorage hoặc sessionStorage). Cách này tiện nhưng script nào chạy trên trang cũng đọc được, dính XSS là mất token. Nhiều hệ thống chọn để JWT trong cookieHttpOnlythay vì localStorage.- Mỗi request gửi lên server đều kèm
JWTtrong headerHTTP (Authorization: Bearer <token>). - Server kiểm tra
JWT, hợp lệ thì cho truy cập tài nguyên.
Ưu điểm:
JWTtự chứa: mọi thứ cần để xác thực và phân quyền đều nằm trong token, server đỡ phải nhớ.- Hợp với hệ phân tán hay
microservices, vì service nào cũng tự kiểm tra được token mà không cần hỏi database.
Nhược điểm:
Tokencó thể bị lấy cắp nếu không bảo vệ đúng cách.- Muốn đổi thông tin trong
JWTthì chỉ có cách tạo token mới, không sửa hay xoá tại chỗ được. - Khó thu hồi: token đã phát ra thì còn hợp lệ tới khi hết hạn, kể cả khi người dùng đã logout hay bị khoá. Cách thường làm là cho access token sống ngắn (vài phút tới vài chục phút) kèm refresh token, hoặc giữ một danh sách token bị chặn, mà làm vậy thì lại phải lưu trạng thái ở server.
Ví dụ (thư viện jjwt bản 0.12 trở lên):
@RestController
public class JwtController {
// HS256 cần secret dài ít nhất 256 bit (32 byte). Lấy từ config/biến môi trường, đừng hard-code.
private final SecretKey key;
public JwtController(@Value("${jwt.secret}") String secret) {
this.key = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
}
@PostMapping("/login")
public String login(@RequestBody User user) {
if ("cristiano_ronaldo".equals(user.getUsername()) && "password".equals(user.getPassword())) {
String jwt = Jwts.builder()
.subject(user.getUsername())
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis() + 15 * 60 * 1000)) // Hết hạn sau 15 phút
.signWith(key) // Key 32 byte thì jjwt chọn HS256
.compact();
return "Bearer " + jwt;
}
return "Invalid credentials";
}
@GetMapping("/profile")
public String getProfile(@RequestHeader("Authorization") String authorization) {
String token = authorization.replace("Bearer ", "");
try {
Claims claims = Jwts.parser()
.verifyWith(key)
.build()
.parseSignedClaims(token) // Sai chữ ký hoặc hết hạn đều ném JwtException
.getPayload();
String username = claims.getSubject();
return "Welcome, " + username;
} catch (JwtException e) {
return "Invalid or expired token";
}
}
}Bản cũ của ví dụ này dùng API kiểu signWith(SignatureAlgorithm.HS256, "CR7") và Jwts.parser().setSigningKey(...).parseClaimsJws(...). Từ jjwt 0.12 mấy hàm đó đã deprecated, và từ 0.10 thì secret ngắn như "CR7" sẽ bị từ chối luôn với WeakKeyException, vì chuẩn RFC 7518 yêu cầu key HS256 dài tối thiểu 256 bit. Code trên còn cần import io.jsonwebtoken.*, io.jsonwebtoken.security.Keys, javax.crypto.SecretKey, java.nio.charset.StandardCharsets và java.util.Date.
# so sánh session, cookies & jwt
| Đặc điểm | Session | Cookies | JWT |
|---|---|---|---|
| Lưu trữ | Server | Client (trình duyệt) | Client (localStorage hoặc cookie) |
| Bảo mật | An toàn hơn, dữ liệu nằm trên server | Người dùng sửa được, cần ký hoặc chỉ cất ID | Có chữ ký nên không sửa được, nhưng ai cũng đọc được |
| Kích thước | Không giới hạn (tuỳ bộ nhớ server) | Khoảng 4KB | Chuẩn không giới hạn, nhưng server giới hạn header |
| Tính phân tán | Cần sticky session hoặc store chung | Gửi tới mọi server cùng domain | Hợp với hệ phân tán hoặc microservices |
| Huỷ bỏ | Huỷ được (xoá session) | Xoá cookie dễ dàng | Khó thu hồi trước hạn |
# lời kết
Session, Cookies và JWT mỗi thằng có cái hay cái dở, hợp với từng hoàn cảnh. Session hợp với app web truyền thống, thông tin người dùng để trên server, muốn đá ai ra là đá được ngay; scale ra nhiều máy thì thêm Redis là xong. Cookies nhẹ nhàng, tiện để cất mấy thứ nhỏ trên client. Còn JWT thì được chuộng trong hệ phân tán hay microservices, nơi từng service cần tự xác thực, tự phân quyền cho nhanh mà không phụ thuộc nhau.
App của bạn cần gì thì chọn cái đó, cân giữa hiệu năng và bảo mật cho hợp lý.
Chỉ là những ghi chép cá nhân với hy vọng mang lại chút giá trị. Nếu thấy hữu ích, đừng ngại chia sẻ cho bạn bè & đồng nghiệp nhé!
Happy coding 😎 👍🏻 🚀 🔥.