Logo
Published on
เผยแพร่เมื่อ(แก้ไขเมื่อ 1 วันที่ผ่านมา)

JWT (JSON Web Token) คืออะไร?

JWT

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): ผู้สร้าง Token
  • sub (subject): หัวข้อของ Token
  • aud (audience): ผู้รับ Token
  • exp (expiration time): เวลาหมดอายุ
  • iat (issued at): เวลาสร้าง Token
  • jti (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

  1. Server รับข้อมูลการ Login จากผู้ใช้
  2. ตรวจสอบ ข้อมูลว่าถูกต้องหรือไม่
  3. สร้าง JWT โดยใส่ข้อมูลผู้ใช้ใน Payload
  4. ลงชื่อ Token ด้วย Secret Key
  5. ส่ง Token กลับให้ Client

การใช้ Token

  1. Client เก็บ Token ไว้ (LocalStorage/Cookie)
  2. ส่ง Token ไปกับ Request ทุกครั้งใน Header
  3. Server ตรวจสอบ Signature ของ Token
  4. ตรวจสอบ เวลาหมดอายุ
  5. ดึงข้อมูล จาก Payload หาก Token ถูกต้อง

โฟลว์การทำงานแบบเต็ม

ขั้นตอนการทำงาน:

  1. Login Request: Client ส่งข้อมูล username/password
  2. Authentication: Server ตรวจสอบข้อมูล
  3. Token Generation: Server สร้าง JWT Token
  4. Token Response: Server ส่ง JWT กลับให้ Client
  5. Storage: Client เก็บ Token ไว้
  6. API Request: Client ส่ง Request พร้อม JWT ใน Header
  7. Token Validation: Server ตรวจสอบความถูกต้องของ Token
  8. Access Granted: ส่งข้อมูลตอบกลับหาก Token ถูกต้อง

ข้อดีของ JWT

ข้อดี

  1. Stateless: Server ไม่ต้องเก็บข้อมูล Session
  2. Scalable: เหมาะกับระบบที่มีหลาย Server
  3. Cross-platform: รองรับหลายภาษาและแพลตฟอร์ม
  4. Compact: ขนาดเล็ก ส่งผ่าน HTTP ได้ง่าย
  5. Secure: มีการเข้ารหัสและลงชื่อเพื่อความปลอดภัย

ข้อควรระวัง

  1. ข้อมูลสำคัญ: อย่าใส่ข้อมูลที่ละเอียดอ่อนใน Payload (เพราะถอดรหัสได้)
  2. Token Size: หากใส่ข้อมูลเยอะจะทำให้ Token ใหญ่
  3. Revocation: ไม่สามารถยกเลิก Token ก่อนหมดอายุได้
  4. 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}`
  }
});
// Server side (Express)
res.cookie('token', token, {
  httpOnly: true,
  secure: true, // HTTPS only
  sameSite: 'strict'
});

Best Practices สำหรับ JWT

ความปลอดภัย

  1. ใช้ HTTPS เสมอเมื่อส่ง JWT
  2. Secret Key ควรยาวและซับซ้อน
  3. กำหนด Expiration สั้นๆ (15-30 นาที)
  4. ใช้ Refresh Token สำหรับการต่ออายุ
  5. ไม่ใส่ข้อมูล sensitive ใน Payload

การจัดการ Token

  1. Error Handling ตรวจสอบ Token ทุกครั้ง
  2. Token Rotation เปลี่ยน Token เป็นระยะ
  3. Logout ลบ Token จาก Client
  4. Monitoring ติดตามการใช้ Token

JWT vs Session

คุณสมบัติJWTSession
StateStatelessStateful
Scalabilityสูงจำกัด
Securityขึ้นอยู่กับการ implementสูงกว่า
Sizeใหญ่กว่าเล็กกว่า
Mobile Appเหมาะสมไม่เหมาะสม
Logoutยากง่าย

สรุป

JWT เป็นเทคโนโลยีที่มีประสิทธิภาพสำหรับการ Authentication ในระบบสมัยใหม่ โดยเฉพาะใน:

  • Microservices Architecture
  • Mobile Applications
  • Single Page Applications (SPA)
  • API-first Systems

การเลือกใช้ JWT ขึ้นอยู่กับความต้องการของระบบและการถ่วงดุลระหว่างความปลอดภัยกับประสิทธิภาพครับ


อ้างอิง

avatar
Username
@Kongkiat
Bio
I collect sparks from tech, culture, and everyday chaos — then spin them into stories with a twist.

Comment