🥬
FreshForFresh
  • ✌️Xin chào!
  • Overview
    • 💡Data structures and Algorithms
    • 👀Programming
    • 🧠Database
    • 🦴Back-End
    • ✨Front-End
    • 💐Spring Framework
    • 🛠️Tool
  • Data structures and Algorithms
    • 🎰Data Type
    • 🗑️Garbage collection
    • 🧩Data structures
    • 🎲Algorithms
  • Programming
    • 🧮Compiler
      • 🤖Tại sao phải cần compiler?
      • 📠Java Virtual Machine
  • 📈Thread
  • 🔐Lock & Deadlock
  • 🏇Race condition & Data Race
  • 🧯Synchronized
  • 🗜️Blocking IO và Non Blocking IO Client Server Socket
  • 🌐Languages
    • 🎛️Programming Language
    • 📑Client-Side & Server-Side
    • ⁉️Why java? Why javascript?
  • 🗼Design pattern
    • 🎨Front-End Design Pattern
    • 🏗️Back End Design Pattern
      • 🐴with Java
      • 🦄with Spring Boot
      • 🐖with Modern Backend Development
      • 🐁with Microservice
  • 🍀Clean Code
    • 🌊Chương 1: Code sạch
    • 📐Chương 2: Quy tắc đặt tên rõ nghĩa
    • 🚧Chương 3: Cách viết hàm
    • 👻Chương 4: Comments thế nào cho chuẩn?
    • 🥳Chương 5: Định dạng code.
    • 😈Chương 6: Đối tượng và cấu trúc dữ liệu
    • 🐛Chương 7: Xử lí lỗi
    • ☦️Chương 8: Ranh giới code
    • 🧪Chương 9: Unit test
    • 🥂Chương 10: Lớp đối tượng
    • 🥡Chương 11: Code sạch cấp hệ thống
  • Database
    • 🐔Giới thiệu
    • 📰Các loại cơ sở dữ liệu
    • 🐘Các loại DBMS phổ biến
    • 🕵️‍♀️SQL và cách sử dụng với relational database
      • 👺Lệnh SQL
      • 🛢️JOIN trong SQL
      • 🛕GROUP BY trong SQL
      • 🐼ORDER BY trong SQL
      • 🐣Truy vấn con SUBQUERY trong SQL Server
      • 🏦Transaction Trong SQL
      • 🇲🇰ACID
    • ☢️Thiết kế cơ sở dữ liệu
    • 🕍Consistency and concurrency handling
      • 🔒Database locking
      • 🎮Concurrency Control
      • 🙀Isolation Level
    • 🎨Performance & Scability
      • ☝️Indexing
      • 👯‍♂️Partitioning
      • 💅Các tips tăng hiệu suất SQL
      • 🇲🇬Kiểm tra và Xác thực việc Sao lưu và Phục hồi CSDL
      • 🌠Database sharding
      • 🧘‍♂️REPLICATION
      • 😁Các vấn đề ảnh hưởng đến performance.
  • Back-End
    • 🔫API?
    • 🐕‍🦺Web Services
    • 💾Phân biệt API và web service
    • 🙆‍♂️Tìm hiểu về RestAPI
    • 🧦SOAP
    • ✈️GraphQL API
    • 📊Compared GraphQL & REST
    • 📄Phân biệt HTTP & HTTPS
    • 🚵Client & Server
    • 🇩🇲DOM
  • Front-End
    • 🦑User Interface (UI)
    • 🛰️State Management
  • Spring Framework
    • 🔐Spring Security
      • 🌅Đơn giản hoá Spring Security
      • 🌸Spring Security: Authentication and Authorization In-Depth
      • 🚻OAuth2 Basic
      • 🀄JWT + Spring Security Oauth
      • 📤Logout trong ứng dụng sử dụng bảo mật Oauth
      • 🔂Reset Password
      • 🎗️OAuth2 Remember Me với Refresh Token
      • ⛓️OAuth2 cho một Spring REST API
  • Network
    • 💸Mạng máy tính căn bản
  • Tool
    • 🦏Các câu lệnh Docker cơ bản
    • 🦧Github
      • 🐰Các lệnh Git cơ bản
      • 🐇Một số trường hợp khi sử dụng Git
    • 🏚️WebServer
      • 🪂Tổng quan về kiến trúc hệ thống
      • 🔃Cơ bản về WebServer Nginx
  • Tài nguyên
    • 👨‍🏫Course
    • 📖Docs
Powered by GitBook
On this page
  • Tổng quan
  • Logout Using Front-End Application
  • Logout Using Zuul Proxy
  • Define Route for Logout
  • POST to Authorization Server's /logout
  • Remove the Refresh Token
  • Remove the Access Token from the Angular Client
  1. Spring Framework
  2. Spring Security

Logout trong ứng dụng sử dụng bảo mật Oauth

PreviousJWT + Spring Security OauthNextReset Password

Last updated 1 year ago

Tổng quan

Trong hướng dẫn nhanh này, chúng ta sẽ thấy cách thêm chức năng đăng xuất vào ứng dụng Spring Security sử dụng OAuth.

Chúng ta sẽ xem một số cách để làm điều này. Đầu tiên, chúng ta sẽ thấy cách đăng xuất người dùng Keycloak khỏi ứng dụng OAuth như đã mô tả trong hướng dẫn "Tạo một REST API với OAuth2", và sau đó, sử dụng proxy Zuul mà chúng ta đã thấy trước đó.

Chúng ta sẽ sử dụng ngăn xếp OAuth trong Spring Security 5. Nếu bạn muốn sử dụng ngăn xếp OAuth cũ của Spring Security, hãy xem bài viết trước đây này: "".

Logout Using Front-End Application

Vì Access Tokens được quản lý bởi Authorization Server, chúng cần được vô hiệu hóa ở mức đó. Các bước chính xác để làm điều này sẽ khác nhau một chút tùy thuộc vào Authorization Server bạn đang sử dụng.

Trong ví dụ của chúng ta, theo tài liệu Keycloak, để đăng xuất trực tiếp từ ứng dụng trình duyệt, chúng ta có thể chuyển hướng trình duyệt đến .

Bên cạnh việc gửi URI chuyển hướng, chúng ta cũng cần truyền id_token_hint cho API đăng xuất của Keycloak. Điều này nên chứa giá trị id_token đã được mã hóa.

Hãy nhớ lại cách chúng ta đã lưu trữ access_token, chúng ta cũng sẽ lưu trữ id_token tương tự:

saveToken(token) {
  var expireDate = new Date().getTime() + (1000 * token.expires_in);
  Cookie.set("access_token", token.access_token, expireDate);
  Cookie.set("id_token", token.id_token, expireDate);
  this._router.navigate(['/']);
}

Quan trọng nhất là, để nhận được ID Token trong phần tử payload của Authorization Server, chúng ta nên bao gồm openid trong tham số scope.

Bây giờ hãy xem quá trình đăng xuất hoạt động.

Chúng ta sẽ chỉnh sửa hàm logout trong App Service:

logout() {
  let token = Cookie.get('id_token');
  Cookie.delete('access_token');
  Cookie.delete('id_token');
  let logoutURL = "http://localhost:8083/auth/realms/baeldung/protocol/openid-connect/logout?
    id_token_hint=" + token + "&post_logout_redirect_uri=" + this.redirectUri;

  window.location.href = logoutURL;
}

Ngoài việc chuyển hướng, chúng ta cũng cần loại bỏ Access và ID Tokens mà chúng ta đã nhận từ Authorization Server.

Vì vậy, trong đoạn mã trên, trước tiên chúng ta đã xóa các token, sau đó mới chuyển hướng trình duyệt đến API đăng xuất của Keycloak.

Việc xóa Access, ID, và Refresh Tokens tương ứng với phiên làm việc hiện tại được thực hiện tại phía Authorization Server. Trong trường hợp này, ứng dụng trình duyệt của chúng ta không lưu trữ Refresh Token.

Logout Using Zuul Proxy

Trong một bài viết trước về Xử lý Refresh Token, chúng ta đã thiết lập ứng dụng để có thể làm mới Access Token bằng cách sử dụng Refresh Token. Hiện thực này sử dụng một proxy Zuul với các bộ lọc tùy chỉnh.

Ở đây, chúng ta sẽ xem cách thêm chức năng đăng xuất vào đoạn mã trên.

Lần này, chúng ta sẽ sử dụng một API Keycloak khác để đăng xuất người dùng. Chúng ta sẽ gọi POST lên đích đến đăng xuất để đăng xuất một phiên thông qua việc gọi không thông qua trình duyệt, thay vì sử dụng chuyển hướng URL chúng ta đã sử dụng trong phần trước.

Define Route for Logout

Để bắt đầu, hãy thêm một đường dẫn khác vào proxy trong file application.yml của chúng ta:

zuul:
  routes:
    //...
    auth/refresh/revoke:
      path: /auth/refresh/revoke/**
      sensitiveHeaders:
      url: http://localhost:8083/auth/realms/baeldung/protocol/openid-connect/logout
    
    //auth/refresh route

Thực tế, chúng ta đã thêm một đường dẫn con vào đường dẫn auth/refresh đã tồn tại. Quan trọng là chúng ta phải thêm đường dẫn con trước đường dẫn chính, nếu không, Zuul sẽ luôn ánh xạ URL của đường dẫn chính.

Chúng ta đã thêm một đường dẫn con thay vì một đường dẫn chính để có quyền truy cập vào cookie refreshToken chỉ bằng HTTP, được thiết lập với một đường dẫn rất hạn chế là /auth/refresh (và các đường dẫn con của nó). Chúng ta sẽ thấy tại sao chúng ta cần cookie trong phần tiếp theo.

POST to Authorization Server's /logout

Bây giờ, hãy tăng cường việc thực hiện của CustomPreZuulFilter để chặn URL /auth/refresh/revoke và thêm thông tin cần thiết để truyền cho Authorization Server.

Các tham số form yêu cầu cho việc đăng xuất tương tự như yêu cầu Refresh Token, trừ việc không có grant_type:

@Component 
public class CustomPostZuulFilter extends ZuulFilter { 
    //... 
    @Override 
    public Object run() { 
        //...
        if (requestURI.contains("auth/refresh/revoke")) {
            String cookieValue = extractCookie(req, "refreshToken");
            String formParams = String.format("client_id=%s&client_secret=%s&refresh_token=%s", 
              CLIENT_ID, CLIENT_SECRET, cookieValue);
            bytes = formParams.getBytes("UTF-8");
        }
        //...
    }
}

Ở đây, chúng ta đơn giản là trích xuất cookie refreshToken và gửi nó trong required formParams.

Remove the Refresh Token

Khi vô hiệu hóa Access Token bằng cách sử dụng chuyển hướng đăng xuất như chúng ta đã thấy trước đó, Refresh Token liên quan đến nó cũng bị vô hiệu hóa bởi Authorization Server.

Tuy nhiên, trong trường hợp này, cookie httpOnly vẫn được đặt trên Client. Vì chúng ta không thể xóa nó bằng JavaScript, chúng ta cần xóa nó từ phía máy chủ.

Vì vậy, hãy thêm vào việc thực hiện của CustomPostZuulFilter để chặn URL /auth/refresh/revoke để nó sẽ xóa cookie refreshToken khi gặp URL này:

@Component
public class CustomPostZuulFilter extends ZuulFilter {
    //...
    @Override
    public Object run() {
        //...
        String requestMethod = ctx.getRequest().getMethod();
        if (requestURI.contains("auth/refresh/revoke")) {
            Cookie cookie = new Cookie("refreshToken", "");
            cookie.setMaxAge(0);
            ctx.getResponse().addCookie(cookie);
        }
        //...
    }
}

Remove the Access Token from the Angular Client

Ngoài việc vô hiệu hóa Refresh Token, cookie access_token cũng cần được xóa khỏi phía máy khách.

Hãy thêm một phương thức vào Angular controller của chúng ta để xóa cookie access_token và gọi đến /auth/refresh/revoke POST mapping:

logout() {
  let headers = new HttpHeaders({
    'Content-type': 'application/x-www-form-urlencoded; charset=utf-8'});
  
  this._http.post('auth/refresh/revoke', {}, { headers: headers })
    .subscribe(
      data => {
        Cookie.delete('access_token');
        window.location.href = 'http://localhost:8089/';
        },
      err => alert('Could not logout')
    );
}

Hàm này sẽ được gọi khi nhấp vào nút Đăng xuất (Logout):

<a class="btn btn-default pull-right"(click)="logout()" href="#">Logout</a>

Đáng chú ý, chúng ta đã truyền vào redirect URI là - URI mà chúng ta sử dụng trong toàn bộ ứng dụng - vì vậy chúng ta sẽ đến trang đích sau khi đăng xuất.

🔐
📤
Đăng xuất trong một ứng dụng được bảo mật bằng OAuth (sử dụng ngăn xếp cũ)
http://auth-server/auth/realms/{realm-name}/protocol/openid-connect/logout?redirect_uri=encodedRedirectUri
http://localhost:8089/ ↗