Trong bài hướng dẫn này, chúng ta sẽ tìm hiểu và lập trình cơ chế xác thực Basic Authentication dựa trên kiến trúc RESTful Web Services, trong đó, yêu cầu xác thực (Request) sẽ được gửi thông qua API-URI.
Công cụ
Để hiện thực được bài hướng dẫn này, chúng ta cần chuẩn bị (tối thiểu) những công cụ sau:
- Eclipse IDE for Enterprise Java Developers
- Apache Tomcat 8.5
- JAX-RS RI 2.30
- Postman
- Java JDK 1.8
- XAMPP 5.6.40
TL;DR
Các bước cấu hình môi trường lập trình, cấu hình Java Servlet, triển khai API theo kiến trúc RESTful Web Services, kiểm thử API, chúng ta có tham khảo trong video tại đây và tải về mã nguồn mẫu tại đây.
Basic HTTP authentication scheme
Tiêu chuẩn RFC-7235 định nghĩa cơ chế xác thực dựa trên giao thức HTTP, cơ chế này cho phép phía máy chủ yêu cầu phía máy khách cung cấp các thông tin xác thực (authentication information). Một cách vắn tắt, cơ chế hoạt động như sau:
- Máy chủ phản hồi về máy khách mã lỗi 401 (Unauthorized), cùng với thông tin về cách xác thực dựa trên WWW-Authenticate header;
- Tiếp theo, nếu máy khách muốn xác thực với máy chủ, máy khách cần phải thêm thông tin Authorization vào trường header của HTTP. Thông thường, thông tin dùng để xác thực là tên-truy-cập (Username) và mâ-khẩu (Password).
Trong phạm vi của bài viết, chúng ta sẽ xem cơ chế xác thực này (sau đây gọi tắt là Basic Auth) là một trong những giải pháp dùng để xác thực một người dùng (User/Client) khi gửi truy cập đến API, chúng ta sẽ không thảo luận nhiều về ưu-khuyết điểm của nó. Trong thực tế, bên cạnh cơ chế này, chúng ta còn có những cơ chế khác như (có thể dùng kết hợp với Basic Auth): API Key, Bearer Token, Digest Auth, OAuth 1.0, OAuth 2.0, Hawk Authentication, AWS Signature, NTLM Authentication, Akamai EdgeGrid.
Xét về độ bảo mật, cơ chế Basic Auth dùng mã Base64 để mã hóa và giải mã thông tin xác thực, do vậy, thường không được đánh giá cao về độ bảo mật. Trong thực tế, nếu dùng Basic Auth, chúng ta thường kết hợp với giao thức SSL (HTTPS) để có thêm một lớp mã hóa thông tin.
Xét về mặt kỹ thuật của Basic Auth, để gửi thông tin xác thực, phía máy khách cần phải “đính kèm” thông tin cần xác thực vào trường header của HTTP. Cấu trúc của Authorization header như sau:
- Thông tin Username và Password được nối lại thành một chuỗi có định dạng
username:password
; - Tiếp theo, chuỗi này được mã hóa (encode) theo chuẩn Base64;
- Sau cùng, nối thêm từ khóa
Basic
(cùng với 1 khoảng trắng) vào phía trước chuỗi “username:password” vừa được mã hóa.
Ví dụ, nếu username là newai
và password là vietnam
, thì giá trị Authorization header sẽ như sau:
Authorization: Basic bmV3YWk6dmlldG5hbQ==
Có một lưu ý với Base64 trong một số ngôn ngữ lập trình, thông thường sẽ bổ sung thêm (padding) ký tự =
(dấu bằng) sau chuỗi mã hóa. Tùy thuộc vào ngôn ngữ lập trình, chúng ta cần lưu ý điểm này khi mã hóa một chuỗi. Chẳng hạn, trong Java, thư viện java.util.Base64.Encoder
thông thường sẽ padding thêm ký tự =
vào cuối chuỗi mã hóa, ngược lại, chúng ta có thể sử dụng phương thức withoutPadding().
Java Service sử dụng GET với Basic Auth
Trong đoạn chương trình bên dưới, chúng ta sẽ tiến hành hiện thực lớp Authentication, trong đó, bao gồm phương thức auth(...):boolean
với tham số là chuỗi mã hóa Authorization header theo chuẩn Base64, kết quả trả về là true
nếu thông tin xác thực đúng, và ngược lại.
package com.soa.auth; import java.util.Base64; import javax.ws.rs.*; import javax.ws.rs.core.*; @Path("/auth") public class Authentication { @GET @Path("/v1") @Produces(MediaType.APPLICATION_JSON) public boolean auth(@HeaderParam("authorization") String authString) { // extract data before decoding String[] authParts = authString.split("\\s+"); String authInfo = authParts[1]; // decode the data back to original string byte[] bytes = null; bytes = Base64.getDecoder().decode(authInfo); String decodedAuth = new String(bytes); // extract username/password if(decodedAuth.contains(":")) { String username = decodedAuth.split(":")[0]; String password = decodedAuth.split(":")[1]; // change this line to your logic if(username.equals("admin") && password.equals("123456")) return true; } return false; } }
Nếu chúng ta hiện thực các Services trong nhiều Package khác nhau, thì thẻ <param-value>...</param-value>
trong tập tin web.xml
cần phải ghi nhận tất cả những Package đó, mỗi tên Package cần phải đầy đủ và cách nhau bằng dấu ;
(semi-colon).
Ví dụ Project có 2 Package (com.soa.demo
và com.soa.auth
), thì tập tin web.xml
cần phải thể hiện rõ thông tin cả 2 Package này:
<servlet> <servlet-name>Jersey REST Service</servlet-name> <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class> <init-param> <param-name>jersey.config.server.provider.packages</param-name> <param-value>com.soa.demo;com.soa.auth</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet>
Kiểm thử API bằng Postman
Trước khi kiểm thử API, chúng ta cần phải khởi động Servlet và triển khai (Deploy) dịch vụ lên Server (localhost:8080
). [Xem video hướng dẫn]
Các bước thực hiện như sau:
- Trong phần mềm Postman, chọn thẻ Authorization;
- Chọn Basic Auth trong trường Type;
- Nhập thông tin Username và Password;
- Kiểm tra có phải giao thức GET và bấm Send.
Lập trình Web PHP gửi HTTP Authentication
Đến đây, chúng ta đã lập trình và kiểm thử API đã chạy đúng với logic đặt ra. Tại bước này, chúng ta sẽ lập trình một giao diện Web (HTML & PHP) để thể hiện giao diện đăng nhập (HTML) và gửi (PHP) gói tin chứa Authentication header đến máy chủ chứa API (localhost:8081
) và nhận (PHP) kết quả xác nhận thông tin người dùng.
Chúng ta sẽ cần hiện thực tập tin login.php
chứa phần mã HTML thể hiện giao diện đăng nhập, và mã PHP để gửi thông tin xác nhận đến API và nhận kết quả xác thực từ API.
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Log In</title> </head> <body style="font-size:125%;"> <?php date_default_timezone_set('Asia/Ho_Chi_Minh'); ?> <h1>Log In</h1> <div id="InputForm"> <form id="submitForm" method="POST"> Username:<br> <input type="text" class="form-control" id="username" name="username" value="<?php echo isset($_POST['username']) ? $_POST['username'] : '' ?>" /><br><br> Password:<br> <input type="password" class="form-control" id="password" name="password" /><br><br> <button id="submitBtn" type="submit">Log In</button> </form> </div> <?php if (isset($_POST['username']) && isset($_POST['password'])) { $url = "http://localhost:8080/SOA-2/soa/auth/v1/"; //Initialize cURL. $ch = curl_init(); //Prepare HTTP-HEADER $username = $_POST['username']; $password = $_POST['password']; $headers = array( 'Content-Type:application/json', 'Authorization: Basic '. base64_encode($username . ":" . $password) ); curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); //Set the URL that you want to GET by using the CURLOPT_URL option. curl_setopt($ch, CURLOPT_URL, $url); //Set CURLOPT_RETURNTRANSFER so that the content is returned as a variable. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); //Set CURLOPT_FOLLOWLOCATION to true to follow redirects. curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); //Execute the request. $data = curl_exec($ch); //Close the cURL handle. curl_close($ch); //Print the data out onto the page. echo "<p>Result: <strong>" . $data . "</strong></p>"; } ?> </body>
Để thực thi được đoạn chương trình trên, chúng ta cần phải có một Web Engine trên máy tính. Bài viết này sử dụng XAMPP 5.6.40 trên hệ điều hành MacOS 10.15.3. Hình bên dưới là kết quả khi thực thi tập tin login.php
trên trình duyệt Web.
Tổng kết
Trong bài hướng dẫn này, chúng ta đã hoàn thành được những việc sau:
- Hiện thực được một Java Project dựa trên RESTful Web Services
- Triển khai được một API xác thực dựa trên cơ chế HTTP Basic Authentication
- Hiện thực giao diện Web (HTML & PHP) để gửi yêu cầu xác thực đến API và nhận kết quả xác thực từ API. Chúng ta có thể tiếp tục mở rộng phần giao diện Web theo hướng sau: nếu thông tin xác thực từ API gửi về là
true
, thì sẽ chuyển hướng (redirect) người dùng vào các giao diện Web/Services khác và quản lý bằng cơ chế Cookies hoặc Session-ID. Trong bài viết tiếp theo, chúng ta sẽ cùng tìm hiểu Cookies để lưu trữ Session của người dùng.