🛡️ Spring Security

ការការពារ Application ជាមួយ Spring Security

មេរៀននេះនឹងណែនាំអ្នកពីការអនុវត្ត Spring Security ចាប់ពីការដំឡើង រហូតដល់ JWT Authentication ពេញលេញ។

Spring Boot 3.x
Java 17+
JWT / OAuth2
ระดับ Intermediate
1

ទិដ្ឋភាពទូទៅ (Overview)

Spring Security គឺជា Framework ដ៏មានអានុភាពបំផុតសម្រាប់ការការពារ Java Application ។ វាផ្តល់នូវ Authentication (ការផ្ទៀងផ្ទាត់អ្នកប្រើ) និង Authorization (ការអនុញ្ញាតការចូលប្រើ) ។

🔐
Authentication
ការផ្ទៀងផ្ទាត់អ្នកប្រើថាជានរណា (Who are you?)
🎫
Authorization
ការអនុញ្ញាតថាអ្នកប្រើអាចធ្វើអ្វីបាន (What can you do?)
🛡️
CSRF Protection
ការការពារការវាយប្រហារ Cross-Site Request Forgery
🔑
Session Management
ការគ្រប់គ្រង Session របស់អ្នកប្រើ

Security Filter Chain

Spring Security ដំណើរការតាមរយៈ Filter Chain ។ Request ពី Browser ចូលមក ហើយត្រូវ Filter ផ្សេងៗ ដំណើរការ:

HTTP Request
SecurityFilterChain
Authentication
Authorization
Controller
2

ការដំឡើង (Setup)

បន្ថែម Dependency ទៅក្នុង pom.xml:

XML pom.xml
<dependencies>
    <!-- Spring Security -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

    <!-- JWT Library -->
    <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>
</dependencies>
ℹ️
បន្ទាប់ពីបន្ថែម Spring Security ស្វ័យប្រវត្តិ Application នឹងការពារ Endpoint ទាំងអស់ ហើយ Default Login Page នឹងបង្ហាញ។ Password ស្វ័យប្រវត្តិនឹងបង្ហាញក្នុង Console ។
3

Security Configuration

ការកំណត់ Security Configuration ដោយ Custom តាម @Configuration Class:

Java SecurityConfig.java
@Configuration
@EnableWebSecurity
@EnableMethodSecurity  // អនុញ្ញាតការប្រើ @PreAuthorize
public class SecurityConfig {

    @Autowired
    private JwtAuthenticationFilter jwtAuthFilter;

    @Autowired
    private UserDetailsService userDetailsService;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http)
            throws Exception {

        return http
            // បិទ CSRF ព្រោះប្រើ JWT
            .csrf(csrf -> csrf.disable())

            // កំណត់ Authorization Rules
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/auth/**").permitAll()
                .requestMatchers("/api/public/**").permitAll()
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .anyRequest().authenticated()
            )

            // Stateless Session (ប្រើ JWT)
            .sessionManagement(session -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            )

            // បន្ថែម JWT Filter
            .addFilterBefore(jwtAuthFilter,
                UsernamePasswordAuthenticationFilter.class)

            .build();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManager(
            AuthenticationConfiguration config) throws Exception {
        return config.getAuthenticationManager();
    }
}
4

Authentication (ការផ្ទៀងផ្ទាត់)

ការបង្កើត Auth Controller សម្រាប់ Login និង Register:

Java AuthController.java
@RestController
@RequestMapping("/api/auth")
public class AuthController {

    @Autowired
    private AuthenticationManager authManager;

    @Autowired
    private JwtService jwtService;

    @Autowired
    private UserService userService;

    // Login Endpoint
    @PostMapping("/login")
    public ResponseEntity<AuthResponse> login(
            @RequestBody LoginRequest request) {

        // ផ្ទៀងផ្ទាត់ username + password
        Authentication auth = authManager.authenticate(
            new UsernamePasswordAuthenticationToken(
                request.getUsername(),
                request.getPassword()
            )
        );

        // បង្កើត JWT Token
        UserDetails user = (UserDetails) auth.getPrincipal();
        String token = jwtService.generateToken(user);

        return ResponseEntity.ok(
            new AuthResponse(token, "Login successful")
        );
    }

    // Register Endpoint
    @PostMapping("/register")
    public ResponseEntity<String> register(
            @RequestBody RegisterRequest request) {

        userService.createUser(request);
        return ResponseEntity.ok("User registered successfully!");
    }
}

Request/Response Models

Java Models.java
// Login Request
public record LoginRequest(
    String username,
    String password
) {}

// Auth Response ផ្ញើ Token ត្រឡប់
public record AuthResponse(
    String token,
    String message
) {}

// Register Request
public record RegisterRequest(
    String username,
    String email,
    String password,
    String role
) {}
5

Authorization (ការអនុញ្ញាត)

Spring Security ផ្តល់ Annotation ច្រើនសម្រាប់ Authorization:

Annotation ការប្រើប្រាស់ ឧទាហរណ៏
@PreAuthorize ពិនិត្យ Permission មុន Method ដំណើរការ @PreAuthorize("hasRole('ADMIN')")
@PostAuthorize ពិនិត្យ Permission ក្រោយ Method ត្រឡប់ @PostAuthorize("returnObject.owner == auth.name")
@Secured ដាក់ Role ដែលអனុញ្ញាត @Secured({"ROLE_ADMIN","ROLE_USER"})
hasAuthority() ពិនិត្យ Authority ជាក់លាក់ hasAuthority('READ_USERS')
Java ProductController.java
@RestController
@RequestMapping("/api/products")
public class ProductController {

    // អ្នកប្រើគ្រប់គ្នា (Authenticated) អាចមើលបាន
    @GetMapping
    public List<Product> getAllProducts() {
        return productService.findAll();
    }

    // តែ ADMIN ប៉ុណ្ណោះអាចបន្ថែម
    @PostMapping
    @PreAuthorize("hasRole('ADMIN')")
    public Product createProduct(@RequestBody Product product) {
        return productService.save(product);
    }

    // User ដែល Login ហើយ ឬ ADMIN
    @DeleteMapping("/{id}")
    @PreAuthorize("hasRole('ADMIN') or #id == authentication.principal.id")
    public void deleteProduct(@PathVariable Long id) {
        productService.delete(id);
    }

    // ពិនិត្យ Permission ច្រើន
    @PutMapping("/{id}")
    @PreAuthorize("hasAuthority('WRITE_PRODUCTS') or hasRole('ADMIN')")
    public Product updateProduct(@PathVariable Long id,
            @RequestBody Product product) {
        return productService.update(id, product);
    }
}
6

JWT Token Authentication

JSON Web Token (JWT) ប្រើក្នុង Stateless Authentication ។ Token ផ្ទុកព័ត៌មានអ្នកប្រើ ហើយ Server មិនត្រូវការ Session ទេ។

JWT Token មានបីផ្នែក: Header (algorithm) . Payload (data/claims) . Signature (verification) ។ ផ្នែកទាំងបីត្រូវបែកដោយ dot (.) ។
Java JwtService.java
@Service
public class JwtService {

    @Value("${jwt.secret}")
    private String secretKey;

    private static final long EXPIRATION = 86400000; // 24 ម៉ោង

    // បង្កើត Token ថ្មី
    public String generateToken(UserDetails userDetails) {
        return Jwts.builder()
            .subject(userDetails.getUsername())
            .claim("roles", userDetails.getAuthorities())
            .issuedAt(new Date())
            .expiration(new Date(System.currentTimeMillis() + EXPIRATION))
            .signWith(getSigningKey())
            .compact();
    }

    // ទាញ Username ពី Token
    public String extractUsername(String token) {
        return extractClaims(token, Claims::getSubject);
    }

    // ពិនិត្យ Token ស្ថិតស្ថេរ
    public boolean isTokenValid(String token, UserDetails user) {
        final String username = extractUsername(token);
        return username.equals(user.getUsername())
            && !isTokenExpired(token);
    }

    private boolean isTokenExpired(String token) {
        return extractExpiration(token).before(new Date());
    }

    private SecretKey getSigningKey() {
        byte[] bytes = Decoders.BASE64.decode(secretKey);
        return Keys.hmacShaKeyFor(bytes);
    }

    private <T> T extractClaims(String token,
            Function<Claims, T> resolver) {
        final Claims claims = Jwts.parser()
            .verifyWith(getSigningKey())
            .build()
            .parseSignedClaims(token)
            .getPayload();
        return resolver.apply(claims);
    }
}
Properties application.properties
jwt.secret=404E635266556A586E3272357538782F413F4428472B4B6250645367566B5970
jwt.expiration=86400000
7

UserDetailsService

Spring Security ត្រូវការ UserDetailsService ដើម្បីទាញ User ពី Database:

Java CustomUserDetailsService.java
@Service
public class CustomUserDetailsService
        implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String username)
            throws UsernameNotFoundException {

        User user = userRepository.findByUsername(username)
            .orElseThrow(() ->
                new UsernameNotFoundException(
                    "User not found: " + username
                )
            );

        // បំប្លែង User Entity ទៅ UserDetails
        return org.springframework.security.core.userdetails.User
            .withUsername(user.getUsername())
            .password(user.getPassword())
            .roles(user.getRole().name())
            .build();
    }
}

// User Entity
@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true)
    private String username;

    private String email;

    private String password; // BCrypt encoded

    @Enumerated(EnumType.STRING)
    private Role role;
}
8

JWT Authentication Filter

Filter នេះដំណើរការក្នុង Request ចូលមក ដើម្បីពិនិត្យ JWT Token:

Java JwtAuthenticationFilter.java
@Component
public class JwtAuthenticationFilter
        extends OncePerRequestFilter {

    @Autowired
    private JwtService jwtService;

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain)
            throws ServletException, IOException {

        // ទាញ Authorization Header
        final String authHeader = request.getHeader("Authorization");

        // ពិនិត្យថា Header ចាប់ផ្តើមជា "Bearer "
        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            filterChain.doFilter(request, response);
            return;
        }

        // ទាញ Token (លុប "Bearer " ចេញ)
        final String jwt = authHeader.substring(7);
        final String username = jwtService.extractUsername(jwt);

        // ពិនិត្យថា User មិនទាន់ authenticated
        if (username != null &&
                SecurityContextHolder.getContext()
                    .getAuthentication() == null) {

            UserDetails user =
                userDetailsService.loadUserByUsername(username);

            if (jwtService.isTokenValid(jwt, user)) {
                // Set Authentication ក្នុង SecurityContext
                var authToken = new
                    UsernamePasswordAuthenticationToken(
                        user, null, user.getAuthorities()
                    );
                authToken.setDetails(
                    new WebAuthenticationDetailsSource()
                        .buildDetails(request)
                );
                SecurityContextHolder.getContext()
                    .setAuthentication(authToken);
            }
        }

        filterChain.doFilter(request, response);
    }
}
⚠️
Filter OncePerRequestFilter ធានាថាដំណើរការ ម្តងប៉ុណ្ណោះ ក្នុង Request មួយ ទោះបីមាន Forward ឬ Include ក៏ដោយ។
9

Roles & Authorities

ការបែងចែក Role (ADMIN, USER) និង Authority (READ_PRODUCTS, WRITE_PRODUCTS):

Java Role.java
public enum Role {
    USER(
        Set.of(
            Permission.READ_OWN_PROFILE,
            Permission.READ_PRODUCTS
        )
    ),
    ADMIN(
        Set.of(
            Permission.READ_OWN_PROFILE,
            Permission.READ_PRODUCTS,
            Permission.WRITE_PRODUCTS,
            Permission.DELETE_PRODUCTS,
            Permission.MANAGE_USERS
        )
    );

    private final Set<Permission> permissions;

    Role(Set<Permission> permissions) {
        this.permissions = permissions;
    }

    public List<SimpleGrantedAuthority> getAuthorities() {
        var authorities = permissions.stream()
            .map(p -> new SimpleGrantedAuthority(p.name()))
            .collect(Collectors.toList());

        // បន្ថែម ROLE_ prefix
        authorities.add(new SimpleGrantedAuthority(
            "ROLE_" + this.name()
        ));
        return authorities;
    }
}

// Permission Enum
public enum Permission {
    READ_OWN_PROFILE,
    READ_PRODUCTS,
    WRITE_PRODUCTS,
    DELETE_PRODUCTS,
    MANAGE_USERS
}
Role Permissions Endpoint ចូលបាន
USER READ_OWN_PROFILE, READ_PRODUCTS GET /api/products, GET /api/profile
ADMIN ទាំងអស់ រាល់ Endpoint
10

សង្ខេបស្ថាបត្យកម្ម

📋 Flow: Login → Token → Protected API
POST /api/auth/login
AuthController
AuthenticationManager
UserDetailsService
JWT Token
Client ផ្ទុក Token
Request + Bearer Token
JwtAuthFilter
SecurityContext
SecurityConfig
កំណត់ Rules, Filter Chain, Session Policy
🔑
JwtService
Generate, Validate, Extract Token
🔍
JwtFilter
Intercept Request, Set Authentication
👤
UserDetailsService
Load User from Database
🎓
សំណួររំឭក: Spring Security ប្រើ Filter Chain → ពិនិត្យ Authentication (JWT) → ពិនិត្យ Authorization (Role/Permission) → បើ Pass ទាំងអស់ ទើប Controller ដំណើរការ។