- Published on
- เผยแพร่เมื่อ(แก้ไขเมื่อ 1 วันที่ผ่านมา)
JWT (JSON Web Token) คืออะไร?
JWT (JSON Web Token) เป็นมาตรฐานการสร้าง Token สำหรับการยืนยันตัวตนและการแลกเปลี่ยนข้อมูลระหว่างระบบต่างๆ ในบทความนี้เราจะมาเรียนรู้ทุกอย่างเกี่ยวกับ JWT ตั้งแต่พื้นฐานจนถึงการนำไปใช้งานจริงครับ
JWT คืออะไร?
JWT เป็นมาตรฐานเปิด (RFC 7519) สำหรับการสร้าง Token ที่มีข้อมูลในรูปแบบ JSON โดยสามารถนำไปใช้ในการ:
- Authentication: ยืนยันตัวตนผู้ใช้
- Authorization: ตรวจสอบสิทธิ์การเข้าถึงทรัพยากร
- Information Exchange: แลกเปลี่ยนข้อมูลระหว่างระบบ
JWT ประกอบด้วย 3 ส่วนหลักคือ Header, Payload และ Signature
โครงสร้างของ JWT
1. Header
ส่วน Header บอกประเภทของ Token (JWT) และอัลกอริทึมที่ใช้ลงชื่อ
{
"alg": "HS256",
"typ": "JWT"
}
2. Payload
ส่วน Payload คือข้อมูลที่ต้องการส่ง หรือที่เรียกว่า "Claims" แบ่งได้ 3 ประเภท:
Registered Claims
iss(issuer): ผู้สร้าง Tokensub(subject): หัวข้อของ Tokenaud(audience): ผู้รับ Tokenexp(expiration time): เวลาหมดอายุiat(issued at): เวลาสร้าง Tokenjti(JWT ID): ID ของ Token
Public Claims
คือข้อมูลที่เรากำหนดเอง เช่น:
{
"userId": "12345",
"username": "john_doe",
"role": "admin"
}
Private Claims
คือข้อมูลที่ตกลงกันระหว่างผู้สร้างและผู้ใช้ Token
3. Signature
ส่วน Signature ใช้สำหรับตรวจสอบความถูกต้องของ Token สร้างจาก:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret_key
)
ลักษณะที่ปรากฏของ JWT
JWT ที่สมบูรณ์จะมีลักษณะดังนี้:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
ประกอบด้วย 3 ส่วนคั่นด้วยจุด (.):
Header.Payload.Signature
หลักการทำงานของ JWT
การสร้าง Token
- Server รับข้อมูลการ Login จากผู้ใช้
- ตรวจสอบ ข้อมูลว่าถูกต้องหรือไม่
- สร้าง JWT โดยใส่ข้อมูลผู้ใช้ใน Payload
- ลงชื่อ Token ด้วย Secret Key
- ส่ง Token กลับให้ Client
การใช้ Token
- Client เก็บ Token ไว้ (LocalStorage/Cookie)
- ส่ง Token ไปกับ Request ทุกครั้งใน Header
- Server ตรวจสอบ Signature ของ Token
- ตรวจสอบ เวลาหมดอายุ
- ดึงข้อมูล จาก Payload หาก Token ถูกต้อง
โฟลว์การทำงานแบบเต็ม
ขั้นตอนการทำงาน:
- Login Request: Client ส่งข้อมูล username/password
- Authentication: Server ตรวจสอบข้อมูล
- Token Generation: Server สร้าง JWT Token
- Token Response: Server ส่ง JWT กลับให้ Client
- Storage: Client เก็บ Token ไว้
- API Request: Client ส่ง Request พร้อม JWT ใน Header
- Token Validation: Server ตรวจสอบความถูกต้องของ Token
- Access Granted: ส่งข้อมูลตอบกลับหาก Token ถูกต้อง
ข้อดีของ JWT
ข้อดี
- Stateless: Server ไม่ต้องเก็บข้อมูล Session
- Scalable: เหมาะกับระบบที่มีหลาย Server
- Cross-platform: รองรับหลายภาษาและแพลตฟอร์ม
- Compact: ขนาดเล็ก ส่งผ่าน HTTP ได้ง่าย
- Secure: มีการเข้ารหัสและลงชื่อเพื่อความปลอดภัย
ข้อควรระวัง
- ข้อมูลสำคัญ: อย่าใส่ข้อมูลที่ละเอียดอ่อนใน Payload (เพราะถอดรหัสได้)
- Token Size: หากใส่ข้อมูลเยอะจะทำให้ Token ใหญ่
- Revocation: ไม่สามารถยกเลิก Token ก่อนหมดอายุได้
- Storage: ต้องเก็บ Token ให้ปลอดภัยในฝั่ง Client
ตัวอย่างการใช้งานใน Java Spring Boot
Dependencies ที่ต้องเพิ่ม
ใน pom.xml:
<dependencies>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.3</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.3</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.3</version>
<scope>runtime</scope>
</dependency>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
JWT Utility Class (Spring Boot 3.x)
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
@Component
public class JwtUtil {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
private Key getSigningKey() {
return Keys.hmacShaKeyFor(secret.getBytes());
}
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
public Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
private Claims extractAllClaims(String token) {
return Jwts.parser()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody();
}
private Boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
claims.put("roles", userDetails.getAuthorities());
return createToken(claims, userDetails.getUsername());
}
private String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + expiration))
.signWith(getSigningKey())
.compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
}
JWT Authentication Filter (Spring Boot 3.x)
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtUtil jwtUtil;
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
final String authorizationHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7);
username = jwtUtil.extractUsername(jwt);
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtUtil.validateToken(jwt, userDetails)) {
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authenticationToken.setDetails(
new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}
filterChain.doFilter(request, response);
}
}
Security Configuration (Spring Boot 3.x)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/public/**").permitAll()
.anyRequest().authenticated()
)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
Authentication Controller
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/auth")
public class AuthController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtUtil jwtUtil;
@PostMapping("/login")
public ResponseEntity<?> createAuthenticationToken(@RequestBody AuthenticationRequest authenticationRequest) {
try {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
authenticationRequest.getUsername(),
authenticationRequest.getPassword()
)
);
} catch (BadCredentialsException e) {
throw new BadCredentialsException("Incorrect username or password", e);
}
final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
final String jwt = jwtUtil.generateToken(userDetails);
return ResponseEntity.ok(new AuthenticationResponse(jwt));
}
}
AuthenticationRequest.java
package com.example.blog.dto;
import lombok.Data;
@Data
public class AuthenticationRequest {
private String username;
private String password;
}
AuthenticationResponse.java
package com.example.blog.dto;
import lombok.Data;
@Data
public class AuthenticationResponse {
private String jwt;
public AuthenticationResponse(String jwt) {
this.jwt = jwt;
}
}
application.properties
# JWT Configuration
jwt.secret=mySecretKey123456789012345678901234567890
jwt.expiration=86400000 # 24 hours in milliseconds
Protected Endpoint ตัวอย่าง
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/protected")
public class ProtectedController {
@GetMapping("/user")
@PreAuthorize("hasRole('USER')")
public String userEndpoint() {
return "User content";
}
@GetMapping("/admin")
@PreAuthorize("hasRole('ADMIN')")
public String adminEndpoint() {
return "Admin content";
}
@GetMapping("/profile")
public ResponseEntity<?> getUserProfile(Authentication authentication) {
UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
return ResponseEntity.ok(userDetails);
}
}
การเก็บ JWT ในฝั่ง Client
LocalStorage
// เก็บ Token
localStorage.setItem('token', jwtToken);
// ดึง Token
const token = localStorage.getItem('token');
// ส่ง Token กับ Request
fetch('/api/data', {
headers: {
'Authorization': `Bearer ${token}`
}
});
Cookie (HttpOnly)
// Server side (Express)
res.cookie('token', token, {
httpOnly: true,
secure: true, // HTTPS only
sameSite: 'strict'
});
Best Practices สำหรับ JWT
ความปลอดภัย
- ใช้ HTTPS เสมอเมื่อส่ง JWT
- Secret Key ควรยาวและซับซ้อน
- กำหนด Expiration สั้นๆ (15-30 นาที)
- ใช้ Refresh Token สำหรับการต่ออายุ
- ไม่ใส่ข้อมูล sensitive ใน Payload
การจัดการ Token
- Error Handling ตรวจสอบ Token ทุกครั้ง
- Token Rotation เปลี่ยน Token เป็นระยะ
- Logout ลบ Token จาก Client
- Monitoring ติดตามการใช้ Token
JWT vs Session
| คุณสมบัติ | JWT | Session |
|---|---|---|
| State | Stateless | Stateful |
| Scalability | สูง | จำกัด |
| Security | ขึ้นอยู่กับการ implement | สูงกว่า |
| Size | ใหญ่กว่า | เล็กกว่า |
| Mobile App | เหมาะสม | ไม่เหมาะสม |
| Logout | ยาก | ง่าย |
สรุป
JWT เป็นเทคโนโลยีที่มีประสิทธิภาพสำหรับการ Authentication ในระบบสมัยใหม่ โดยเฉพาะใน:
- Microservices Architecture
- Mobile Applications
- Single Page Applications (SPA)
- API-first Systems
การเลือกใช้ JWT ขึ้นอยู่กับความต้องการของระบบและการถ่วงดุลระหว่างความปลอดภัยกับประสิทธิภาพครับ
อ้างอิง
- Username
- @Kongkiat
- Bio
- I collect sparks from tech, culture, and everyday chaos — then spin them into stories with a twist.
