[Java Backend Zero to Hello] BÀI 5.9: SPRING SECURITY CƠ BẢN
📚 Bài viết thuộc series Java Backend Zero to Hello 📌 Phần: Phase 5: Spring Framework & Spring Boot | Bài 55/86
BÀI 5.9: SPRING SECURITY CƠ BẢN
Mục tiêu
- Hiểu Spring Security
- Cấu hình Authentication & Authorization
- Sử dụng JWT
- Bảo vệ REST API
1. SPRING SECURITY LÀ GÌ?
Spring Security là framework bảo mật mạnh mẽ cho Spring, cung cấp:
- Authentication - Xác thực (Bạn là ai?)
- Authorization - Phân quyền (Bạn được làm gì?)
- Protection - Chống CSRF, XSS, Session Fixation
- Integration - OAuth2, JWT, LDAP, SAML
2. DEPENDENCY
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<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>
3. SECURITY CONFIG CƠ BẢN
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
4. USER DETAILS SERVICE
@Service
@RequiredArgsConstructor
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
User user = userRepository.findByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
return org.springframework.security.core.userdetails.User.builder()
.username(user.getEmail())
.password(user.getPassword())
.roles(user.getRole().name())
.build();
}
}
5. JWT (JSON WEB TOKEN)
5.1 Cấu trúc
Header.Payload.Signature
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
5.2 JWT Service
@Service
public class JwtService {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private long expiration;
public String generateToken(UserDetails userDetails) {
return generateToken(new HashMap<>(), userDetails);
}
public String generateToken(Map<String, Object> extraClaims, UserDetails userDetails) {
return Jwts.builder()
.claims(extraClaims)
.subject(userDetails.getUsername())
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis() + expiration))
.signWith(getSigningKey())
.compact();
}
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
public boolean isTokenValid(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
}
private <T> T extractClaim(String token, Function<Claims, T> resolver) {
Claims claims = extractAllClaims(token);
return resolver.apply(claims);
}
private Claims extractAllClaims(String token) {
return Jwts.parser()
.verifyWith(getSigningKey())
.build()
.parseSignedClaims(token)
.getPayload();
}
private SecretKey getSigningKey() {
byte[] keyBytes = Decoders.BASE64.decode(secret);
return Keys.hmacShaKeyFor(keyBytes);
}
}
5.3 JWT Authentication Filter
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtService jwtService;
private final UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
String jwt = authHeader.substring(7);
String username = jwtService.extractUsername(jwt);
if (username != null && SecurityContextHolder.getContext()
.getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (jwtService.isTokenValid(jwt, userDetails)) {
UsernamePasswordAuthenticationToken authToken =
new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities()
);
authToken.setDetails(new WebAuthenticationDetailsSource()
.buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
filterChain.doFilter(request, response);
}
}
6. AUTH CONTROLLER
@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
public class AuthController {
private final AuthenticationManager authenticationManager;
private final UserService userService;
private final JwtService jwtService;
@PostMapping("/register")
public ResponseEntity<AuthResponse> register(@RequestBody @Valid RegisterRequest request) {
return ResponseEntity.ok(userService.register(request));
}
@PostMapping("/login")
public ResponseEntity<AuthResponse> login(@RequestBody @Valid LoginRequest request) {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
request.email(), request.password()
)
);
UserDetails user = userService.loadUserByUsername(request.email());
String token = jwtService.generateToken(user);
return ResponseEntity.ok(new AuthResponse(token));
}
}
7. SECURITY CONFIG VỚI JWT
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthFilter;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.addFilterBefore(jwtAuthFilter,
UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public AuthenticationManager authenticationManager(
AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
8. PHÂN QUYỀN
8.1 Trong SecurityConfig
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/api/users/**").hasAnyRole("USER", "ADMIN")
8.2 Với @PreAuthorize
@PreAuthorize("hasRole('ADMIN')")
@DeleteMapping("/{id}")
public void delete(@PathVariable Long id) { ... }
@PreAuthorize("hasAnyRole('ADMIN', 'USER')")
@GetMapping
public List<User> list() { ... }
@PreAuthorize("#id == authentication.principal.id")
@GetMapping("/{id}")
public User getById(@PathVariable Long id) { ... }
8.3 Với @Secured
@Secured("ROLE_ADMIN")
public void delete() { ... }
9. ROLE VÀ AUTHORITY
// Role
.hasRole("ADMIN") // Tự thêm prefix "ROLE_"
// Authority
.hasAuthority("ADMIN") // Không thêm prefix
// Set role cho user
.roles("ADMIN", "USER") // authorities = ["ROLE_ADMIN", "ROLE_USER"]
.authorities("READ", "WRITE") // authorities = ["READ", "WRITE"]
10. LẤY THÔNG TIN USER HIỆN TẠI
@GetMapping("/me")
public UserResponse getCurrentUser(Authentication authentication) {
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
return userService.findByEmail(userDetails.getUsername());
}
// Hoặc với @AuthenticationPrincipal
@GetMapping("/me")
public UserResponse getCurrentUser(@AuthenticationPrincipal UserDetails userDetails) {
return userService.findByEmail(userDetails.getUsername());
}
11. BÀI TẬP THỰC HÀNH
Bài 1: JWT Authentication
Xây dựng hệ thống đăng ký/đăng nhập với JWT.
Bài 2: Phân quyền
Tạo 2 role ADMIN và USER, phân quyền API.
Bài 3: Refresh Token
Implement refresh token để gia hạn session.
12. TÓM TẮT
| Khái niệm | Mô tả |
|---|---|
| Authentication | Xác thực |
| Authorization | Phân quyền |
| SecurityFilterChain | Cấu hình filter |
| UserDetailsService | Load user |
| BCryptPasswordEncoder | Mã hóa password |
| JWT | Token xác thực |
| @PreAuthorize | Phân quyền method |
| hasRole / hasAuthority | Kiểm tra quyền |
Bài tiếp theo: 5.10 Spring Boot Actuator
🧭 Điều Hướng Series
⬅️ Bài trước: BÀI 5.8: EXCEPTION HANDLING
📋 Lộ trình tổng quan: Xem Toàn Bộ Series
➡️ Bài tiếp theo: BÀI 5.10: SPRING BOOT ACTUATOR
All rights reserved