Mình đã có một bài viết giới thiệu về Digital Identity ở đây bạn có thể đọc thêm để hiểu nó là gì và tại sao chúng ngày càng được phát triển và quan tâm trong thời gian gần đây.
Trong bài viết đầu tiên này mình muốn đề cập và tìm hiểu đến một khía cạnh khác của nó và chi tiết hơn về một số thông số kỹ thuật mà chúng ta sẽ sử dụng trong suốt series này.
What is Digital Identity?
Digital Identity là tập hợp các thuộc tính xác định một người dùng trong ngữ cảnh của một chức năng được cung cấp bởi một ứng dụng cụ thể.
Ví dụ - trong một cửa hàng sách trực tuyến, danh tính số của người dùng có thể bao gồm số thẻ tín dụng, địa chỉ giao hàng và lịch sử mua hàng. Ngược lại, trong một hệ thống thuế, danh tính số có thể bao gồm số an sinh xã hội và địa chỉ nhà.
Why is Digital Identity Complex?
Vậy vấn đề với Digital Identity là gì?
Tại sao mọi người luôn nói rằng, "Ồ, điều này rất phức tạp.". Tại sao vậy? hãy nhìn vào hình bên dưới, nó trông rất đơn giản chỉ bao gồm hai thành phần là Resource owner và Resource.
Tôi (resource owner) có một tài nguyên (resource) nào đó và tôi muốn truy cập tài nguyên của mình và sử dụng chúng bất cứ lúc nào.
Hay một ví dụ khác là tôi cần đi đến cơ quan hành chính để thực hiện một số công việc, nhưng do lịch trình bận rộn và không có khả năng thực hiện điều này. Nhưng tối có thể uỷ quyền cho người thân của mình (bất kỳ ai mà tôi tin tưởng) để thực hiện thay tôi.
Đó chính xác là những gì tôi muốn làm và nó thường xuyên xảy ra trong quá trình sử dụng internet. Nó khá đơn giản phải không? nhưng tại sao nó lại trở nên phức tạp khi chúng ta nhắc về nó?
Sự phức tạp của Digital Identity xuất phát từ nhiều yếu tố khác nhau:
- Resource types: Từ các cơ sở dữ liệu trung tâm đến các API và ứng dụng serverless, tài nguyên số ngày càng đa dạng. Mỗi khi bạn tương tác với một hệ thống máy tính, có một loại tài nguyên nào đó mà bạn phải kết nối tới. Và, từ góc độ của một lập trình viên, việc triển khai kết nối đó thực sự tốn rất nhiều công sức.
- Development stacks: Các công nghệ phát triển khác nhau yêu cầu các cách tiếp cận khác nhau để truy cập tài nguyên.
- Identities sources: Người dùng có thể có nhiều danh tính khác nhau trên các hệ thống khác nhau như mạng xã hội, email, và hệ thống quản lý công ty. Và tất cả những danh tính đó đều được biểu đạt trong một cơ sở dữ liệu ở đâu đó, và nơi đó quyết định cách bạn lấy thông tin này ra.
- Client types: Các thiết bị và ứng dụng từ điện thoại di động đến các trang web và thiết bị đeo tay cũng tạo thêm lớp phức tạp khi truy cập dữ liệu.
Ngoài ra việc một số chuyên gia về Identity ngồi lại và thống nhất tạo ra các Open standards cũng cần tốn rất nhiều thời gian để hợp thức hoá và đưa nó vào thực tiễn.
Tất nhiên, điều này phần lớn là lý thuyết và bạn cũng không cần chứng minh mà chỉ cần đọc và sử dụng chúng. Giống như những bài toán cấp hai, ở mức cơ bản giáo viên không bao giờ yêu cầu bạn chứng minh định lý Pytago mà chỉ yêu cầu bạn hiểu và áp dụng chúng vào bài toán. Nhưng nếu chúng ta hiểu sâu sắc về các khái niệm thì việc trở thành chuyên gia trong lĩnh vực đó là điều không thể tránh khỏi.
Một số open standards phổ biến hiện nay bao gồm:
- OpenID Connect, được sử dụng để đăng nhập.
- OAuth2, là cơ sở của OpenID Connect và là một giao thức ủy quyền được thiết kế để giúp bạn truy cập các API.
- JSON Web Token (JWT), là một định dạng token tiêu chuẩn. Hầu hết các token mà bạn sẽ làm việc với đều ở định dạng này.
- SAML, một giao thức đã có từ lâu nhưng vẫn rất phổ biến được sử dụng để thực hiện single sign-on (SSO) trên các domain cho các trình duyệt.
From User Passwords in Every App...
Hãy dành vài phút tiếp theo để đi qua một tour tăng tốc về cách các công nghệ xác thực đã phát triển. Vậy, Digital Identity là gì trong ngữ cảnh này?
Digital Identity (danh tính số) là tập hợp các thuộc tính xác định một người dùng cụ thể trong ngữ cảnh của một chức năng được cung cấp bởi một ứng dụng cụ thể. Điều đó có nghĩa là nếu tôi là một người bán sách, thông tin liên quan mà tôi cần về người dùng chủ yếu là tên và địa chỉ giao hàng của họ, đó là danh tính số của họ trong ngữ cảnh đó.
Nếu tôi là nhân viên soát vé ở rạp phim, thì thông tin mà tôi cần xác thực chỉ là vé xem phim và số lượng người tương ứng với số lượng vé họ có. Bạn có thể thấy rằng đối với tất cả các chức năng khác nhau, chúng ta thực sự có một tập hợp danh tính hoàn toàn hoặc gần như khác nhau.
Tuy nhiên điều đó không quan trọng, từ quan điểm của một nhà thiết kế thì nó là Digital Identity. Vì vậy có thể nói rằng danh tính số của người dùng là tập hợp các thuộc tính mà chúng ta có thể xác nhận và định danh người dùng là ai? có quyền truy cập vào tài nguyên của service hay không?
Vậy cách đơn giản nhất để đạt được thoả thuận này là gì? Đó là cùng người dùng đồng ý với một bí mật nào đó, lần tiếp theo họ đến và nói với chúng ta bí mật mà chỉ hai người biết. Ứng dụng sẽ nói rằng, "được rồi, tôi biết bạn là ai" và bạn đã xác thực thành công! Tóm lại, điều đó có nghĩa là lấy một tập hợp thông tin đăng nhập, gửi nó đi, và giả định rằng các thông tin đăng nhập đó đã được lưu trữ trước đó trong cơ sở dữ liệu. Nếu chúng khớp, người dùng đã được xác thực.
... to Directories
Hãy làm cho mọi thứ thú vị hơn một chút. Bây giờ, mở rộng kịch bản này đến tình huống trong đó người dùng là một nhân viên của một công ty nào đó. Có một tập hợp các ứng dụng đang được sử dụng bởi người dùng này trong ngữ cảnh kinh doanh của công ty. Hầu hết các ứng dụng đều là một phần của những gì người dùng làm trong ngữ cảnh công việc của họ.
Hãy tưởng tượng rằng mỗi ứng dụng người dùng đều phải tạo tài khoản và ghi nhớ nó cho mỗi lần sử dụng tiếp theo, mỗi lần thay đổi thông tin họ đều phải đi đến từng ứng dụng để thay đổi. Và bất cứ khi nào một người dùng rời khỏi công ty, quản trị viên phải đi xử lý, trên tất cả các ứng dụng khác nhau, kiểm tra tài khoản người dùng và hủy cấp phép chúng bằng tay.
Điều đó tất nhiên là một luồng công việc tẻ nhạt và dễ sai sót.
Cross-Domain SSO
Tất nhiên, từ thực tiễn việc kinh doanh hiện tại không mở rộng quy mô. Nó vẫn có thể hoạt động tốt khi bạn ở trong một công ty với những nỗ lực tối thiểu, tuy nhiên có rất nhiều quy trình kinh doanh yêu cầu có nhiều hơn một công ty.
Hãy nghĩ về một nhà cung cấp cổ điển hoặc nhà bán lẻ. Bất kỳ mối quan hệ nào trong số đó đều yêu cầu mở rộng nhiều tổ chức. Do đó, điều xảy ra là khi bạn có một người dùng trong một tổ chức cần truy cập một tài nguyên khác trong một tổ chức khác, bạn sẽ gặp một vấn đề. Thực tế, người dùng này không tồn tại trong cơ sở dữ liệu của bên còn lại.
Để giải quyết vấn đề này, cách đầu tiên họ thử là cố gắng thống nhất điều gì đó và cấp phép cho người dùng truy cập vào tài nguyên của bên còn lại, nhưng điều này không thật sự tốt, vì nó gặp phải vấn đề đã được đề cập trước đó. Khi chúng ta huỷ người dùng ở ứng dụng gốc, quyền vẫn tồn tại ở ứng dụng khác và sẽ gặp những rắc rối lớn nếu nó không được thu hồi kịp thời.
Những gì đã xảy ra là các công ty lớn vào thời điểm đó, Sun, IBM và Similar, ngồi vào một bàn và đưa ra giao thức gọi là SAML, viết tắt của Security Assertion Markup Language. Hiểu đơn giản, giao thức mô tả một giao dịch trong đó một người dùng có thể đăng nhập ở một nơi và sau đó chứng minh việc đăng nhập ở một nơi khác và có quyền truy cập.
Lúc này chúng ta cần một thứ gì đó đứng trước service có khả năng nói chuyện với giao thức đó, mà trong trường hợp cụ thể này sẽ là middleware
- Một thành phần đứng giữa application và caller, chặn request và thực hiện logic trước khi các yêu cầu đến được ứng dụng.
Để hoàn tất giao dịch này, những gì xảy ra là chúng ta cần giới thiệu một khái niệm khác: trust. Bây giờ, hãy nhìn vào kịch bản cụ thể này:
Ứng dụng trong phạm vi của Company 2
có thể được truy cập bởi bất kỳ đối tác kinh doanh nào của nó. Bây giờ có một lựa chọn về nơi mà chúng ta muốn xác nhận danh tính người dùng. Chúng ta nói rằng một resource tin tưởng một ID Provider (IdP) hoặc một Authority khi resource đó sẵn sàng tin những gì IdP nói về người dùng của nó.
Nếu IdP nói: "người dùng này là một trong những người dùng của tôi và đã xác thực thành công năm phút trước", thì resource sẽ tin tưởng điều đó. Đó là tất cả những gì trust có nghĩa.
Khi bạn thiết lập middleware trước ứng dụng của mình, bạn sẽ thường cấu hình nó một trong các IdP (Facebook, Google, Microsoft) mà bạn tin tưởng. Điều đó được thực hiện như thế nào khi bạn thực sự thực hiện một giao dịch? Hãy xem cách nó hoạt động trong một luồng thực tế bằng cách mô tả chi tiết từng bước được đánh số trong hình dưới đây:
(1): Trong bước đầu tiên, người dùng cố gắng truy cấp một resource được bảo vệ trong phạm vi Company 2
. Middleware sẽ chặn yêu cầu nếu người dùng chưa được xác thực, và gửi một yêu cầu xác thực đến IdP nằm trong phạm vi của Company 1
(2).
(3): Trong các điều khoản cụ thể, middleware sẽ tạo ra một loại thông điệp nào đó, có thể là một URL với các tham số chuỗi truy vấn cụ thể, và sẽ chuyển hướng browser đến một endpoint cụ thể liên kết với IdP.
(4): Tiếp theo, IdP xác định rằng người dùng đã được xác thực chính xác và cấp cho người dùng cái mà chúng ta gọi là security token được sử dụng để mang theo một bằng chứng rõ ràng rằng người dùng đã xác thực thành công.
- security token là một chuỗi ký tự và được ký (signed), mục đích của việc ký lên security token là để bảo vệ nó khỏi bị giả mạo. Nếu người nhận token kiểm tra và thấy chữ ký không đúng thì nó chắc chán là giả mạo hoặc bị tác động vật lý trong quá trình di chuyển, vì vậy request sẽ bị đánh dấu không an toàn và bị từ chối.
- Nó an toàn vì hai lý do:
- Chúng ta sử dụng private key để ký lên token và được cất ở một nơi an toàn, sau đó đưa public key cho RP (Relying party) để kiểm tra chữ ký. Vì cặp private/public keys là duy nhất vì vậy việc giả mạo token là không thể miễn là private key không bị rò rỉ.
- Nội dung của token không thể bị sửa đổi trong quá trình truyền mà không làm hỏng chữ ký.
- Các thuộc tính đi kèm trong token được gọi là claim. Một claim chỉ đơn giản là một thuộc tính được đóng gói trong một ngữ cảnh cho phép người nhận quyết định liệu có tin rằng người dùng thực sự sở hữu thuộc tính đó hay không.
- Hãy nghĩ về những gì xảy ra khi lên máy bay. Nếu tôi xuất trình hộ chiếu của mình cho nhân viên kiểm tra, họ sẽ có thể so sánh tên của tôi (như được xác nhận bởi hộ chiếu) với tên in trên vé máy bay của tôi và quyết định để tôi đi qua. Nhân viên kiểm tra sẽ đạt được kết luận đó vì họ tin tưởng chính phủ, thực thể đã cấp hộ chiếu cho tôi. Nếu tôi đưa ra một tờ giấy với tên của mình được viết nguệch ngoạc bằng tay thì tôi sẽ không thể lên máy bay và có thể sẽ gặp rắc rối lớn.
(5): Một khi IdP cấp một token, nó thường trả lại cho trình duyệt bên trong một form HTML, cùng với một số JavaScript được kích hoạt ngay khi trang được tải - POSTing token đến ứng dụng, nơi nó sẽ bị middleware chặn.
(6): Middleware xem xét token, xác định liệu nó có đến từ một nguồn đáng tin cậy, xác định liệu chữ ký không bị hỏng, vv. vv. và nếu nó hài lòng với tất cả điều đó, nó phát hành cái mà chúng ta gọi là session cookie.
- Session cookie diện cho thực tế rằng xác thực thành công đã xảy ra. Bằng cách đặt một cookie để đại diện cho session, ứng dụng sẽ không phải thực hiện lại các yêu cầu xác thực trong request tiếp theo, miễn là session của bạn còn hiệu lực.
- Session cookie sẽ được tự động đính kèm trong request.
Đây chính xác là những gì giao thức SAML giải quyết vấn đề của SSO (Single sign-on). Chúng ta sẽ thấy rằng mô hình này cũng tương tự xảy ra với OIDC (OpenID Connect) trong những bài tiếp theo.
The Password Sharing Anti-Pattern
Hãy dành chút thời gian để xem xét một kịch bản khác. Tôi đoán rằng nhiều người trong các bạn có LinkedIn và Gmail đúng không? hãy tưởng tượng kịch bản sau:
Giả sử rằng một người dùng hiện đang đã đăng nhập trong LinkedIn, bằng bất kỳ cách nào họ muốn (tôi không quan tâm đến việc đăng nhập trong kịch bản này). Giả sử rằng LinkedIn muốn gợi ý bạn mời tất cả các liên hệ Gmail của bạn trở thành một phần của mạng LinkedIn của bạn.
Note: việc sử dụng LinkedIn và Gmail chỉ vì chúng là những cái tên quen thuộc với các trường hợp sử dụng quen thuộc, nhưng nó không ám chỉ rằng chúng thực sự được triển khai theo cách này.
Bây giờ, LinkedIn sẽ giải quyết vấn đề này như thế nào? Hãy xem xét luồng này bằng cách theo dõi các bước trong hình sau.
(1): LinkedIn sẽ yêu cầu bạn cung cấp username
và password
Gmail của bạn.
(2): Bạn cung cấp LinkedIn thông tin đăng nhập Gmail của mình, (3) và sau đó, LinkedIn sẽ sử dụng chúng để thực sự truy cập các API Gmail được sử dụng bởi ứng dụng Gmail.
(4): Điều này sẽ đạt được những gì LinkedIn muốn, đó là gọi các API trong Gmail để liệt kê các liên hệ của bạn.
Và cuối cùng là gửi các email mời bạn bè, người thân tham gia LinkedIn thay mặt bạn.
(?): Vấn đề với kịch bản này là gì?
- Đầu tiên việc cấp thông tin đăng nhập cho một bên thứ ba luôn là một ý tưởng tồi, họ không phải người quản lý những thông tin đó và không chịu hậu quả trực tiếp nếu thông tin đăng nhập bị rò rỉ.
- Thứ hai, việc cung cấp thông tin đăng nhập cho Gmail khiến họ có quá nhiều quyền lực đối với tài khoản của bạn, trong khi chúng ta chỉ muốn Gmail làm những gì chúng ta yêu cầu là, gửi các email mời bạn bè, người thân tham gia LinkedIn. Gmail có thể vượt quá quyền hạn và sử dụng tài khoản của bạn với những mục đích xấu.
Delegated Authorization: OAuth2
Để đối phó với những thách thức nêu ra ở trên, các chuyên gia về Identity đã đưa ra một cách để làm việc xung quanh vấn đề trao quá nhiều quyền lực cho các ứng dụng.
OAuth2 được thiết kế chính xác để thực hiện kịch bản truy cập được ủy quyền đã được mô tả trước đó. OAuth2 giới thiệu một thực thể mới là authorization server, đặc biệt xử lý các hoạt động liên quan đến ủy quyền và có hai endpoint chính:
- Authorization endpoint: được thiết kế để xử lý sự tương tác với người dùng cuối. Nó được thiết kế để cho phép người dùng biểu đạt liệu họ muốn một dịch vụ nhất định có thể truy cập những tài nguyên nào.
- Token endpoint: được thiết kế để xử lý giao tiếp giữa service và service, chịu trách nhiệm thực hiện ý định mà người dùng đã biểu đạt về quyền, sự đồng ý, ủy quyền và các khái niệm tương tự.
Note: trong cuộc thảo luận sau đây, chúng ta sẽ giả định rằng người dùng đã đăng nhập vào LinkedIn trước khi kịch bản được mô tả diễn ra. Chúng ta không quan tâm cách đăng nhập xảy ra trong ngữ cảnh này; chúng ta chỉ giả định rằng nó đã xảy ra. OAuth2, như bạn sẽ nghe đi nghe lại, không phải là một giao thức đăng nhập.
Giả sử rằng, như một phần của phiên LinkedIn của mình, người dùng truy cập một chức năng trong đó LinkedIn muốn truy cập API Gmail thay mặt họ, như đã được mô tả trong phần trước cho kịch bản tương tự.
Trong cách tiếp cận OAuth2, điều đó có nghĩa là LinkedIn sẽ khiến người dùng đi đến Gmail và cấp quyền cho LinkedIn để xem các liên hệ của họ và gửi email thay mặt họ. Hãy theo dõi luồng mới này bằng cách xem hình dưới đây:
LinkedIn tuân theo đặc tả kỹ thuật OAuth2 để tạo một yêu cầu ủy quyền và chuyển hướng trình duyệt của người dùng đến authorization server của Gmail, và đặc biệt là authorization endpoint (1).
Authorization endpoint được sử dụng bởi Gmail để yêu cầu end-user (2) cung cấp thông tin đăng nhập nếu họ chưa được xác thực với ứng dụng web của Gmail. Ngay khi người dùng được xác thực, authorization server của Gmail sẽ yêu cầu người dùng cuối, nói rằng, "Này, LinkedIn cần truy cập API của Gmail. Cụ thể, họ muốn xem các liên hệ của bạn, và họ muốn gửi email thay mặt bạn. Bạn có ổn với điều đó không?"
Khi người dùng nói ok, authorization server phát hành một authorization code (3).
- Authorization code chỉ là một chuỗi ngẫu nhiên tạo thành một lời nhắc cho máy authorization server về thực tế rằng người dùng đã cấp sự đồng ý cho những quyền đó cho RP cụ thể, trong trường hợp này là LinkedIn.
Authorization code được trả lại cho LinkedIn thông qua trình duyệt (4). Từ giờ trở đi, phần còn lại của giao dịch diễn ra ở phía máy chủ.
Sau khi nhận được authorization code, LinkedIn sẽ liên hệ với token enpoint của authorization server (5) và sẽ xuất trình với các thông tin xác thực của mình (client id
và client secret
) và authorization code, "Này, người dùng này đã cấp sự đồng ý cho điều này và tôi là LinkedIn. Tôi có thể xin truy cập tài nguyên tôi muốn không?"
Sau khi hài lòng với những gì RP đưa ra, authorization server sẽ phát hành một token mới cái mà chúng ta gọi là access token (6).
- Access token là một hiện vật được sử dụng để cấp cho LinkedIn khả năng truy cập API của Gmail (7) thay mặt người dùng, chỉ trong phạm vi các quyền mà người dùng đã đồng ý (8).
Điều này giải quyết vấn đề về các quyền quá mức được đề cập trước đó. Thực tế, miễn là LinkedIn truy cập các API của Gmail chỉ cố gắng thực hiện các hoạt động mà người dùng đã đồng ý, các yêu cầu đến API sẽ thành công.
Ngay khi LinkedIn cố gắng làm điều gì đó khác với các hoạt động đã đồng ý, như, ví dụ như xóa email, Gmail sẽ từ chối quyền truy cập của LinkedIn, vì access token đi kèm với cuộc gọi API bị giới hạn trong scope các quyền mà người dùng đã đồng ý.
- Scope là từ khóa mà chúng ta sử dụng để đại diện cho các quyền mà một RP yêu cầu thay mặt người dùng.
Layering Sign In on Top of OAuth2: OpenID Connect
Trước khi OIDC (OpenID Connect) được phát triển, có rất nhiều sự lạm dụng đối với OAuth2 để cố gắng thực hiện các hoạt động đăng nhập.
Tuy nhiên, OAuth2 không được thiết kế để thực hiện các hoạt động đăng nhập. Hầu hết các nhà cung cấp chỉ mở OAuth2 như một cách để hỗ trợ ủy quyền cho API của họ, và không mở bất kỳ cơ chế đăng nhập thích hợp nào vì đó không phải là kịch bản mà họ đang hướng đến.
Vì vậy các chuyên gia về Identity lại một lần nữa ngồi vào bàn đàm phán và giới thiệu một đặc tả mới gọi là OpenID Connect, giới thiệu cách chuẩn hoá đăng nhập dựa trên OAuth2. Tóm gọn lại thì OIDC giới thiệu một khái niệm mới gọi là Token ID.
- Token ID có thể được cấp bởi một authorization server thông qua tất cả các luồng OAuth2. OIDC mô tả cách các ứng dụng có thể, thay vì yêu cầu một access token, thì có thể yêu cầu thêm id token. Hình sau đây tóm tắt một trong những luồng đó:
Token ID là một token được thiết kế để được tiêu thụ bởi chính RP (or Client) thay vì sử dụng để truy cập resource. Đặc điểm của token id là nó có một định dạng cố định mà các RP có thể tự phân tích cú pháp và xác nhận.
- Điều này có nghĩa là RP có thể tự kiểm tra và xác nhận nó giống như các ứng dụng web được bảo mật thông qua SAML. Những đặc tính này là những gì làm cho nó có thể thực hiện đăng nhập đúng cách bằng cách sử dụng OIDC dựa trên OAuth2.
Conclusion
Những gì chúng ta đã thấy trong bài này có thể được coi là một dòng thời gian cho chuỗi sự kiện dẫn đến việc tạo ra SAML, OAuth2 và OpenID Connect. Hi vọng sau bài này các bạn có thể phân biệt được giữa SAML, OAuth2 và OIDC.
Quan trọng là chúng ta nên nhớ các thuật ngữ cũng như keyword được sử dụng, nó sẽ hữu ích rất nhiều khi làm việc. Trong các bài tiếp theo chúng ta sẽ đi sâu thêm vào các giao thức mở rộng và chi tiết của chúng, đặc biệt phần thực hành chúng ta cũng sẽ tự implement các thành phần như:
- ID Provider
- Authorization Server
- Client (Relying Party)
Để hiểu rõ hơn về chúng.
Tài liệu tham khảo:
- The OAuth 2.0 Authorization Framework: RFC 6749
- OpenID Connect Core
- OAuth2 and OpenID Connect: The Professional Guide
Again, nếu bạn thấy bài viết này hữu ích, hãy cho mình một upvote và follow để mình có thêm động lực viết những bài sau tốt và chất lượng hơn! Thank you!