Chào mừng bạn quay trở lại với hành trình khám phá “Java Spring Roadmap”! Trong những bài viết trước, chúng ta đã cùng nhau đặt nền móng vững chắc cho việc phát triển ứng dụng với Spring, từ những khái niệm cốt lõi như Beans và Dependency Injection, cách Spring hoạt động, cho đến việc xây dựng ứng dụng web đầu tiên với Spring MVC. Gần đây, chúng ta đã lặn sâu vào thế giới bảo mật với Spring Security 101, tìm hiểu sự khác biệt giữa Xác thực (Authentication) và Phân quyền (Authorization), và cách cấu hình xác thực cơ bản cũng như triển khai Role-Based Access Control.
Hôm nay, chúng ta sẽ nâng cấp hệ thống bảo mật của mình lên một tầm cao mới, đáp ứng nhu cầu phổ biến hiện nay: cho phép người dùng đăng nhập bằng tài khoản mạng xã hội như Google hoặc GitHub. Điều này không chỉ tiện lợi cho người dùng mà còn giúp giảm tải công việc quản lý thông tin người dùng ban đầu cho ứng dụng của bạn. Chúng ta sẽ thực hiện điều này bằng cách tích hợp OAuth2 với Spring Security.
Mục lục
OAuth2 là gì và Tại sao lại cần nó?
Trước khi đi sâu vào cách triển khai, hãy hiểu rõ về OAuth2. OAuth 2.0 là một framework ủy quyền (authorization framework), không phải một giao thức xác thực (authentication protocol), mặc dù nó thường được sử dụng như một nền tảng để xây dựng các giải pháp xác thực (như OpenID Connect, mà Google và GitHub sử dụng). Mục đích chính của OAuth2 là cho phép một ứng dụng (Client) truy cập vào tài nguyên của người dùng trên một máy chủ tài nguyên (Resource Server) thay mặt cho người dùng đó (Resource Owner), mà không cần người dùng chia sẻ thông tin đăng nhập trực tiếp với ứng dụng Client.
Ví dụ thực tế: Khi bạn đăng nhập vào một trang web hoặc ứng dụng bằng tài khoản Google, bạn đang cho phép trang web đó truy cập một số thông tin cơ bản từ tài khoản Google của bạn (như tên, email) mà không cần nhập mật khẩu Google trực tiếp vào trang web đó. Google đóng vai trò là máy chủ ủy quyền (Authorization Server) và máy chủ tài nguyên (Resource Server).
Tại sao lại cần nó?
- Tiện lợi cho người dùng: Không cần tạo và nhớ thêm một tài khoản/mật khẩu mới cho mỗi dịch vụ.
- Tăng cường bảo mật: Người dùng không chia sẻ thông tin đăng nhập nhạy cảm (mật khẩu) trực tiếp với ứng dụng bên thứ ba. Quyền truy cập có thể được thu hồi bất kỳ lúc nào.
- Mở rộng khả năng: Cho phép ứng dụng truy cập các tài nguyên khác của người dùng (nếu được cho phép), ví dụ: danh sách bạn bè, bài viết công khai,…
- Đơn giản hóa cho nhà phát triển: Giảm bớt gánh nặng quản lý tài khoản, mật khẩu, quy trình quên mật khẩu, v.v.
Tại sao sử dụng OAuth2 với Spring Security?
Spring Security cung cấp hỗ trợ mạnh mẽ và sẵn có cho OAuth2, đặc biệt là vai trò Client. Thay vì phải tự triển khai toàn bộ luồng OAuth2 phức tạp (chuyển hướng, xử lý mã ủy quyền, trao đổi mã lấy token, gọi API user info), Spring Security làm tất cả công việc nặng nhọc đó cho bạn.
Việc tích hợp OAuth2 vào Spring Security cho phép bạn:
- Tận dụng hạ tầng bảo mật sẵn có của Spring Security (quản lý phiên, xử lý request được bảo vệ, v.v.).
- Đồng nhất hóa quy trình xác thực, dù người dùng đăng nhập bằng form truyền thống hay qua OAuth2.
- Dễ dàng cấu hình và mở rộng để hỗ trợ nhiều nhà cung cấp OAuth2 khác nhau (Google, GitHub, Facebook, LinkedIn, custom providers).
Thiết lập dự án Spring Boot
Chúng ta sẽ bắt đầu với một dự án Spring Boot cơ bản. Nếu bạn chưa quen, hãy xem lại bài viết về Spring Boot Roadmap và cách bắt đầu với Spring. Để làm việc với OAuth2 Client và Spring Security, bạn cần thêm các dependency sau vào file `pom.xml` (nếu dùng Maven) hoặc `build.gradle` (nếu dùng Gradle):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
Dependency `spring-boot-starter-security` cung cấp lõi bảo mật. `spring-boot-starter-oauth2-client` chứa các lớp cần thiết để ứng dụng của bạn hoạt động như một OAuth2 Client. `spring-boot-starter-web` cho phép chúng ta xây dựng ứng dụng web. `spring-boot-starter-thymeleaf` (hoặc template engine khác) cần thiết để hiển thị trang login và trang chào mừng.
Đăng ký ứng dụng với Google và GitHub
Để ứng dụng của bạn có thể sử dụng Google hoặc GitHub làm nhà cung cấp OAuth2, bạn cần đăng ký ứng dụng của mình trên cổng developer của họ. Quá trình này sẽ cấp cho bạn một Client ID và Client Secret.
Đăng ký với Google:
- Đi tới Google API Console.
- Tạo một dự án mới hoặc chọn một dự án hiện có.
- Trong Dashboard, tìm kiếm “Credentials” ở menu bên trái.
- Nhấp vào “Create Credentials” -> “OAuth client ID”.
- Chọn “Web application”.
- Đặt tên cho ứng dụng của bạn.
- Trong phần “Authorized redirect URIs”, thêm URI chuyển hướng mà Spring Security sẽ sử dụng sau khi xác thực thành công. Mặc định là
http://localhost:8080/login/oauth2/code/google
(hoặc{baseUrl}/login/oauth2/code/{registrationId}
). Hãy đảm bảo bạn sử dụng đúng cổng và đường dẫn. - Nhấp “Create”. Bạn sẽ nhận được Client ID và Client Secret.
Đăng ký với GitHub:
- Đi tới GitHub Developer Settings.
- Chọn “OAuth Apps” ở menu bên trái.
- Nhấp vào “New OAuth App”.
- Điền “Application name” và “Homepage URL”.
- Trong “Authorization callback URL”, thêm URI chuyển hướng. Mặc định là
http://localhost:8080/login/oauth2/code/github
(hoặc{baseUrl}/login/oauth2/code/{registrationId}
). - Nhấp “Register application”. Bạn sẽ nhận được Client ID. Sau đó, bạn cần “Generate a new client secret”.
Lưu ý quan trọng: Client Secret là thông tin nhạy cảm. Không bao giờ chia sẻ nó công khai hoặc lưu trữ nó trong mã nguồn.
Cấu hình OAuth2 Clients trong Spring Boot
Spring Boot cung cấp khả năng tự động cấu hình cho các nhà cung cấp OAuth2 phổ biến như Google, GitHub, Facebook, Okta, v.v. dựa trên cấu hình trong file `application.properties` hoặc `application.yml`.
Thêm cấu hình sau vào file `application.yml` (hoặc tương đương trong `application.properties`):
spring:
security:
oauth2:
client:
registration:
google:
client-id: your-google-client-id
client-secret: your-google-client-secret
scope:
- email
- profile
github:
client-id: your-github-client-id
client-secret: your-github-client-secret
Giải thích:
spring.security.oauth2.client.registration
: Phần này cấu hình các nhà cung cấp OAuth2 mà ứng dụng của bạn sẽ làm Client.google
vàgithub
: Đây là cácregistrationId
. Spring Boot sẽ sử dụng tên này để tạo các cấu hình Client Registration. Bạn có thể đặt tên tùy ý, nhưng nên dùng tên nhà cung cấp để dễ nhận biết.client-id
vàclient-secret
: Thông tin bạn nhận được khi đăng ký ứng dụng với Google/GitHub.scope
: Các quyền mà ứng dụng của bạn yêu cầu từ người dùng. Ví dụ:email
vàprofile
cho Google để lấy thông tin cơ bản về người dùng. GitHub có các scopes riêng (ví dụ:read:user
,user:email
). Spring Boot cung cấp các scopes mặc định nếu bạn không chỉ định, nhưng tốt nhất nên chỉ định rõ những gì bạn cần.
Với cấu hình này, Spring Boot Security sẽ tự động cấu hình:
- Endpoint ủy quyền (Authorization Endpoint URI)
- Endpoint token (Token Endpoint URI)
- URI thông tin người dùng (User Info Endpoint URI)
- Các Grant Type cần thiết (thường là Authorization Code).
Điều này là nhờ Spring Boot có sẵn thông tin cho các nhà cung cấp phổ biến. Nếu bạn làm việc với nhà cung cấp tùy chỉnh, bạn sẽ cần cấu hình thêm phần provider
để chỉ định các URIs này.
spring:
security:
oauth2:
client:
registration:
my-custom-provider:
client-id: ...
client-secret: ...
scope: ...
provider: my-custom-provider
provider:
my-custom-provider:
authorization-uri: https://auth.example.com/oauth2/authorize
token-uri: https://auth.example.com/oauth2/token
user-info-uri: https://auth.example.com/userinfo
user-info-authentication-method: post # or get
jwk-set-uri: https://auth.example.com/.well-known/jwks.json # optional, for OIDC
Cấu hình Spring Security để hỗ trợ OAuth2 Login
Bây giờ, chúng ta cần cấu hình Spring Security để sử dụng cấu hình OAuth2 Client này và kích hoạt luồng đăng nhập qua OAuth2. Chúng ta sẽ tạo một lớp cấu hình bảo mật, tương tự như bài Hướng dẫn Thực Hành Cấu Hình Xác Thực trong Spring, nhưng thêm hỗ trợ OAuth2.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.web.SecurityFilterChain;
import java.util.HashSet;
import java.util.Set;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authorizeRequests ->
authorizeRequests
.requestMatchers("/", "/login**", "/webjars/**", "/error").permitAll() // Cho phép truy cập các trang này
.anyRequest().authenticated() // Các request khác yêu cầu xác thực
)
.oauth2Login(oauth2Login ->
oauth2Login
.loginPage("/login") // Chỉ định trang login tùy chỉnh (nếu có)
.defaultSuccessUrl("/dashboard", true) // Chuyển hướng sau khi đăng nhập thành công
.failureUrl("/login?error") // Chuyển hướng khi đăng nhập thất bại
// .userInfoEndpoint(userInfo -> userInfo.userService(this.oauth2UserService())) // Cấu hình custom OAuth2UserService (sẽ nói sau)
)
.logout(logout ->
logout
.logoutSuccessUrl("/").permitAll() // Chuyển hướng sau khi logout thành công
);
return http.build();
}
// Cấu hình custom OAuth2UserService (sẽ giải thích chi tiết hơn)
/*
@Bean
public OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() {
DefaultOAuth2UserService delegate = new DefaultOAuth2UserService();
return request -> {
OAuth2User user = delegate.loadUser(request);
// Add custom logic here, e.g., check if user exists in your DB,
// create if not, assign roles based on attributes, etc.
// Example: Grant a default role
Set authorities = new HashSet<>();
authorities.addAll(user.getAuthorities()); // Keep existing authorities from provider
authorities.add(new SimpleGrantedAuthority("ROLE_USER")); // Add your custom role
// Return a new OAuth2User with updated authorities or attributes
// You can also wrap the original user or create a custom user object
return new DefaultOAuth2User(
authorities,
user.getAttributes(),
"name" // The attribute key for the username/identifier
);
};
}
*/
}
Trong cấu hình này:
@Configuration
và@EnableWebSecurity
: Đánh dấu lớp này là một lớp cấu hình Spring Security.securityFilterChain
Bean: Đây là cách cấu hình SecurityFilterChain trong Spring Security 6+. Nó định nghĩa cách các request HTTP sẽ được xử lý về mặt bảo mật.authorizeHttpRequests
: Cấu hình quyền truy cập cho các URL. Chúng ta cho phép truy cập vào trang gốc, trang login, tài nguyên webjars và trang báo lỗi. Các request khác yêu cầu người dùng đã xác thực (authenticated()
).oauth2Login()
: Kích hoạt tính năng đăng nhập qua OAuth2.loginPage("/login")
: Nếu bạn có một trang login tùy chỉnh (ví dụ: có cả form login truyền thống và link/button login OAuth2), bạn chỉ định đường dẫn tới trang đó. Spring Security sẽ tự động tạo các endpoint như/oauth2/authorization/{registrationId}
(ví dụ:/oauth2/authorization/google
) mà bạn có thể liên kết tới từ trang login của mình.defaultSuccessUrl("/dashboard", true)
: Đường dẫn chuyển hướng sau khi đăng nhập OAuth2 thành công.true
ép buộc chuyển hướng đến URL này, bỏ qua URL ban đầu mà người dùng cố gắng truy cập trước khi bị chặn.failureUrl("/login?error")
: Đường dẫn chuyển hướng khi đăng nhập OAuth2 thất bại.userInfoEndpoint
: Cho phép cấu hình cách Spring Security lấy và xử lý thông tin người dùng từ nhà cung cấp OAuth2. Mặc định, Spring Security sử dụngDefaultOAuth2UserService
để fetch thông tin từ User Info Endpoint. Bạn có thể cung cấp một beanOAuth2UserService
tùy chỉnh tại đây để xử lý thông tin người dùng theo cách riêng của mình (sẽ bàn sau).
logout()
: Cấu hình xử lý logout cơ bản.
Tạo trang Login cơ bản
Nếu bạn chỉ định loginPage("/login")
, bạn cần tạo một Controller và view tương ứng.
Controller:
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class LoginController {
@GetMapping("/login")
public String login() {
return "login"; // Trả về tên file view (ví dụ: login.html)
}
@GetMapping("/")
public String home() {
return "index"; // Trang chủ
}
@GetMapping("/dashboard")
public String dashboard() {
return "dashboard"; // Trang sau khi đăng nhập thành công
}
}
View (ví dụ: `src/main/resources/templates/login.html`):
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Login</title>
</head>
<body>
<h1>Login Page</h1>
<!-- Link để đăng nhập bằng Google -->
<a href="/oauth2/authorization/google">Login with Google</a>
<br/>
<!-- Link để đăng nhập bằng GitHub -->
<a href="/oauth2/authorization/github">Login with GitHub</a>
<!-- Optional: Form login truyền thống -->
<!--
<form th:action="@{/login}" method="post">
<div><label> Username : <input type="text" name="username"/></label></div>
<div><label> Password: <input type="password" name="password"/></label></div>
<div><input type="submit" value="Sign In"/></div>
</form>
-->
<!-- Display error if login failed -->
<div th:if="${param.error}">
Invalid username, password, or OAuth2 login failed.
</div>
</body>
</html>
Các link /oauth2/authorization/google
và /oauth2/authorization/github
được Spring Security tự động tạo ra khi bạn cấu hình các registrationId
trong `application.yml` và kích hoạt oauth2Login()
.
Truy cập thông tin người dùng sau khi đăng nhập
Sau khi người dùng đăng nhập thành công qua OAuth2, Spring Security sẽ tạo một đối tượng OAuth2User
và lưu trữ nó trong SecurityContext. Bạn có thể truy cập đối tượng này trong Controller hoặc bất kỳ đâu cần thông tin người dùng hiện tại.
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class DashboardController {
@GetMapping("/dashboard")
public String dashboard(@AuthenticationPrincipal OAuth2User principal, Model model) {
if (principal != null) {
// Lấy thuộc tính người dùng từ nhà cung cấp OAuth2
String name = principal.getAttribute("name"); // Tên hiển thị (thường có trong Google/GitHub)
String email = principal.getAttribute("email"); // Email
String login = principal.getAttribute("login"); // Tên đăng nhập GitHub
model.addAttribute("name", name != null ? name : login); // Sử dụng 'name' hoặc 'login'
model.addAttribute("email", email);
model.addAttribute("attributes", principal.getAttributes()); // Xem tất cả thuộc tính
// Các quyền (Authorities) mà Spring Security gán cho người dùng
model.addAttribute("authorities", principal.getAuthorities());
}
return "dashboard";
}
}
Đối tượng OAuth2User
chứa các “attributes” (thuộc tính) được fetch từ User Info Endpoint của nhà cung cấp. Tên của các thuộc tính này có thể khác nhau giữa các nhà cung cấp (ví dụ: Google dùng “name”, “email”, “picture”, GitHub dùng “login”, “name”, “email”, “avatar_url”).
Bạn có thể hiển thị thông tin này trên view `dashboard.html`:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Dashboard</title>
</head>
<body>
<h1>Welcome to the Dashboard!</h1>
<p>Hello, <strong th:text="${name}">User</strong>!</p>
<p th:if="${email}">Your email is: <span th:text="${email}"></span></p>
<h3>All Attributes:</h3>
<ul>
<li th:each="attrEntry : ${attributes}">
<strong th:text="${attrEntry.key}"></strong>: <span th:text="${attrEntry.value}"></span>
</li>
</ul>
<h3>Authorities:</h3>
<ul>
<li th:each="auth : ${authorities}" th:text="${auth.authority}"></li>
</ul>
<br/>
<a th:href="@{/logout}">Logout</a>
</body>
</html>
Tích hợp với mô hình người dùng của ứng dụng (Custom OAuth2UserService)
Thông thường, bạn không chỉ muốn hiển thị thông tin từ nhà cung cấp OAuth2, mà còn muốn liên kết người dùng OAuth2 này với tài khoản người dùng trong cơ sở dữ liệu của ứng dụng bạn. Đây là lúc bạn cần một OAuth2UserService
tùy chỉnh.
OAuth2UserService
chịu trách nhiệm fetch thông tin người dùng và chuyển đổi nó thành đối tượng OAuth2User
của Spring Security. Bằng cách cung cấp một bean OAuth2UserService
của riêng mình, bạn có thể chặn quá trình này để:
- Kiểm tra xem người dùng với ID/Email nhận được từ OAuth2 đã tồn tại trong database của bạn chưa.
- Nếu chưa, tạo một bản ghi người dùng mới trong database.
- Nếu đã tồn tại, cập nhật thông tin nếu cần.
- Gán các vai trò (roles) hoặc quyền (authorities) tùy chỉnh cho người dùng dựa trên thông tin từ OAuth2 hoặc thông tin lưu trữ trong database của bạn.
- Trả về một đối tượng
OAuth2User
(hoặc một implementation của nó) chứa thông tin người dùng và các quyền đã gán.
Đây là ví dụ về cách cấu hình và một implementation đơn giản:
Trong SecurityConfig
, bỏ comment dòng .userInfoEndpoint(userInfo -> userInfo.userService(this.oauth2UserService()))
và sử dụng bean oauth2UserService
đã khai báo:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
// ... securityFilterChain bean như trên ...
@Bean
public OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() {
DefaultOAuth2UserService delegate = new DefaultOAuth2UserService();
return request -> {
OAuth2User oauth2User = delegate.loadUser(request);
// TODO: Custom Logic Here
// 1. Get registrationId (google, github, etc.)
String registrationId = request.getClientRegistration().getRegistrationId();
// 2. Get user attributes
java.util.Map<String, Object> attributes = oauth2User.getAttributes();
// 3. Determine unique identifier (e.g., email, id from provider)
String userIdentifier = null;
if ("google".equals(registrationId)) {
userIdentifier = (String) attributes.get("email"); // Google uses email
} else if ("github".equals(registrationId)) {
userIdentifier = ((Integer) attributes.get("id")).toString(); // GitHub uses id
// Or you might use 'login' for username, or 'email' if scope user:email is granted
}
// Add logic for other providers
if (userIdentifier == null) {
throw new OAuth2AuthenticationException("Cannot determine user identifier from OAuth2 provider: " + registrationId);
}
// 4. Check if user exists in your database
// UserEntity user = userRepository.findByProviderAndProviderId(registrationId, userIdentifier);
// UserEntity user = userRepository.findByEmail(userIdentifier); // If using email as primary key
// 5. If user does not exist, create new user record
// if (user == null) {
// user = new UserEntity();
// user.setProvider(registrationId);
// user.setProviderId(userIdentifier); // Or set email, etc.
// user.setName((String) attributes.get("name")); // Assuming 'name' exists
// userRepository.save(user);
// }
// 6. Update user info if necessary (e.g., update name, avatar_url)
// user.setLastLogin(LocalDateTime.now());
// userRepository.save(user);
// 7. Define authorities/roles for the user
Set authorities = new HashSet<>();
authorities.addAll(oauth2User.getAuthorities()); // Keep default authorities from provider
// authorities.add(new SimpleGrantedAuthority("ROLE_USER")); // Add a default role
// If you have roles in your database:
// user.getRoles().forEach(role -> authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName())));
// 8. Return a new OAuth2User or a custom implementation
// We wrap the original user with potentially updated authorities and a new name attribute key if needed
String userNameAttributeName = request.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName();
if (userNameAttributeName == null) {
// Fallback to a common attribute name if provider doesn't specify one
userNameAttributeName = "name"; // Or "login" for GitHub
}
// Note: DefaultOAuth2User constructor takes authorities, attributes, and nameAttributeKey
return new DefaultOAuth2User(
authorities,
attributes, // Use original attributes or modify them
userNameAttributeName // Key in attributes that represents the user's name/identifier
);
};
}
}
Trong ví dụ trên, phần // TODO: Custom Logic Here
là nơi bạn sẽ tương tác với lớp Repository (ví dụ: userRepository
) để lưu trữ hoặc tìm kiếm thông tin người dùng trong database của bạn. Bạn sẽ cần tạo các lớp Entity (ví dụ: UserEntity
) và Repository (ví dụ: UserRepository
) cho việc này, giống như bạn làm trong các bài học về Spring Data JPA.
So sánh cấu hình Google và GitHub
Mặc dù cấu hình chung trong `application.yml` trông giống nhau, có một số khác biệt nhỏ trong cách các nhà cung cấp này trả về thông tin người dùng:
Tính năng | GitHub | Ghi chú | |
---|---|---|---|
Registration ID (mặc định) | google |
github |
Sử dụng trong URL /oauth2/authorization/{registrationId} |
Client ID / Secret | Có | Có | Cần đăng ký ứng dụng |
Scope phổ biến | profile , email |
read:user , user:email |
Yêu cầu quyền truy cập thông tin |
Key thuộc tính tên (mặc định) | name |
login |
Có thể khác nhau tùy thuộc scope và cách provider trả về |
Key thuộc tính Email (mặc định) | email |
email |
Cần scope email (Google) hoặc user:email (GitHub) |
URI chuyển hướng (mặc định) | /login/oauth2/code/google |
/login/oauth2/code/github |
Phải khớp với cấu hình trên cổng developer |
Sự khác biệt trong tên các thuộc tính (như name
vs login
) là lý do tại sao bạn có thể cần kiểm tra registrationId
trong OAuth2UserService
tùy chỉnh để lấy đúng thông tin cần thiết.
Xử lý Logout
Spring Security tự động cấu hình một endpoint logout (mặc định là /logout
) khi bạn sử dụng .logout()
trong cấu hình bảo mật. Khi người dùng đăng nhập qua OAuth2 và truy cập endpoint này, Spring Security sẽ xóa session của họ.
Đối với các nhà cung cấp OAuth2/OIDC, việc logout hoàn toàn đôi khi cần chuyển hướng người dùng trở lại nhà cung cấp để kết thúc phiên ở đó. Điều này phức tạp hơn và nằm ngoài phạm vi bài viết cơ bản này, nhưng Spring Security cũng có hỗ trợ cho các luồng logout OIDC.
Lưu ý về bảo mật
- Client Secret: Luôn coi Client Secret là thông tin nhạy cảm. Sử dụng biến môi trường, Spring Cloud Config Server hoặc các phương thức an toàn khác để quản lý nó thay vì lưu trữ trực tiếp trong file cấu hình trong source code.
- HTTPS: Luôn sử dụng HTTPS trong môi trường production để bảo vệ quá trình trao đổi mã và token.
- State Parameter: Spring Security xử lý tự động tham số
state
trong luồng ủy quyền để ngăn chặn tấn công Cross-Site Request Forgery (CSRF). Đừng vô hiệu hóa tính năng này trừ khi bạn hiểu rõ mình đang làm gì. - Scope: Chỉ yêu cầu các scope cần thiết cho ứng dụng của bạn. Yêu cầu quá nhiều quyền có thể làm người dùng e ngại.
Kết luận
Trong bài viết này, chúng ta đã khám phá cách tích hợp OAuth2 với Spring Security để thêm tính năng đăng nhập bằng tài khoản Google và GitHub vào ứng dụng Spring Boot của mình. Chúng ta đã đi qua từ việc hiểu OAuth2 là gì, cấu hình dependency, đăng ký ứng dụng với nhà cung cấp, cấu hình trong `application.yml`, cho đến việc cấu hình SecurityFilterChain và tùy chỉnh OAuth2UserService
để tích hợp với mô hình người dùng của ứng dụng.
Việc sử dụng Spring Security giúp đơn giản hóa đáng kể quá trình triển khai OAuth2 Client, cho phép bạn tập trung vào logic kinh doanh thay vì xử lý các chi tiết phức tạp của giao thức. Đây là một bước tiến quan trọng trong hành trình “Java Spring Roadmap” của bạn, mở ra cánh cửa cho việc xây dựng các ứng dụng hiện đại, thân thiện và an toàn hơn.
Trong các bài viết tiếp theo, chúng ta có thể sẽ đi sâu hơn vào các chủ đề liên quan như triển khai Spring làm Authorization Server, bảo mật API với OAuth2 Resource Server, hoặc khám phá các khía cạnh nâng cao khác của Spring Security. Hãy tiếp tục theo dõi nhé!
Đừng quên xem lại các bài viết trước trong series để củng cố kiến thức nền tảng: