1.1 Hạn chế những người dùng nào đó thực thi mã lệnh của bạn
V
V
Bạn cần hạn chế những người dùng nào đó truy xuất các phần tử trong mã
lệnh của bạn dựa trên tên người dùng hay các vai trò mà người dùng này là
thành viên.
#
#
Sử dụng lớp System.Security.Permissions.PrincipalPermission và bản sao đặc
tính System.Security.Permissions.PrincipalPermissionAttribute của lớp này để
bảo vệ các phần tử trong chương trình của bạn với các yêu cầu RBS.
.NET Framework hỗ trợ cả yêu cầu RBS bắt buộc (imperative RBS demand) và yêu cầu
RBS khai báo (declarative RBS demand). Lớp PrincipalPermission hỗ trợ các lệnh bảo
mật bắt buộc, và bản sao đặc tính PrincipalPermissionAttribute của lớp này hỗ trợ các
lệnh bảo m
ật khai báo. Các yêu cầu RBS sử dụng cú pháp giống như các yêu cầu CAS,
nhưng các yêu cầu RBS chỉ rõ tên mà người dùng hiện hành phải có, hoặc thông thường
hơn là các vai trò mà người dùng này là thành viên. Một yêu cầu RBS lệnh cho bộ thực
thi xét tên và các vai trò của người dùng hiện hành, và nếu chúng không đạt yêu cầu, bộ
thực thi sẽ ném ngoại lệ System.Security.SecurityException.
Đoạn mã dưới đây trình bày cú pháp của một yêu cầu bảomật bắt buộc:
// Cú pháp của một yêu cầu bảomật bắt buộc dựa-trên-vai-trò.
public static void SomeMethod() {
§
PrincipalPermission perm =
new PrincipalPermission("UserName", "RoleName");
perm.Demand();
§
}
Trước tiên, bạn phải tạo một đối tượng PrincipalPermission chỉ định tên người dùng và
tên vai trò mà bạn yêu cầu, rồi gọi phương thức Demand của nó. Bạn chỉ có thể chỉ định
một tên người dùng và tên vai trò cho mỗi yêu cầu. Nếu tên người dùng hoặc tên vai trò
là null, bất kỳ giá trị nào cũng sẽ thỏa mãn yêu cầu. Khác với các quyền truy xuất mã
lệnh, một yêu cầu RBS không cho kết quả trong một stack walk; bộ thực thi ch
ỉ đánh giá
tên người dùng và các vai trò của người dùng hiện hành.
Đoạn mã dưới đây trình bày cú pháp của một yêu cầu bảomật khai báo:
// Cú pháp của một yêu cầu bảomật khai báo dựa-trên-vai-trò.
[PrincipalPermission(SecurityAction.Demand, Name = "UserName",
Role = "RoleName")]
public static void SomeMethod() { /* */}
Bạn có thể thay các yêu cầu RBS khai báo ở mức lớp hay mức thành viên. Các yêu cầu
mức-lớp áp dụng cho tất cả các thành viên của lớp trừ khi có một yêu cầu mức-thành-
viên chép đè yêu cầu mức-lớp.
Nói chung, bạn có thể tự chọn hiện thực các yêu cầu RBS bắt buộc hay khai báo. Tuy
nhiên, các yêu cầu bảomật bắt buộc cho phép bạn tích hợp các yêu cầu RBS với logic của
mã lệnh để thự
c hiện những yêu cầu RBS phức tạp. Ngoài ra, nếu không biết vai trò hay
tên người dùng để yêu cầu lúc biên dịch, bạn phải sử dụng các yêu cầu bắt buộc. Các yêu
cầu RBS khai báo có thuận lợi là chúng độc lập với logic của mã lệnh và dễ nhận biết
hơn. Ngoài ra, bạn có thể xem các yêu cầu RBS khai báo bằng công cụ Permview.exe (đã
được thảo luận trong mục 13.6). Cho dù hiện thực yêu cầu RBS bắ
t buộc hay khai báo,
bạn cũng phải chắc rằng bộ thực thi có thể truy xuất tên và các vai trò của người dùng
hiện hành để đánh giá yêu cầu một cách phù hợp.
Lớp System.Threading.Thread mô tả một tiểu trình của hệ điều hành (chạy mã lệnh được-
quản-lý). Thuộc tính tĩnh CurrentPrincipal của lớp Thread chứa một thể hiện IPrincipal—
mô tả người dùng mà tiểu trình hiện đang chạy trên danh nghĩa của người này. Ở
mức hệ
điều hành, mỗi tiểu trình cũng có một Windows access token kết giao—mô tả tài khoản
Windows mà tiểu trình hiện đang chạy trên danh nghĩa của tài khoản này. Bạn cần hiểu
rằng thể hiện IPrincipal và Windows access token là hai thực thể riêng biệt. Windows sử
dụng access token để thực hiện cơ chế bảomật hệ điều hành, trong khi bộ thực thi .NET
sử dụng th
ể hiện IPrincipal để đánh giá các yêu cầu RBS ở mức ứng dụng. Mặc dù chúng
có thể mô tả cùng một người dùng, nhưng điều này không có nghĩa là luôn luôn như vậy.
Theo mặc định, thuộc tính Thread.CurrentPrincipal là không xác định. Vì việc thu lấy các
thông tin liên quan đến người dùng có thể mất nhiều thời gian, và chỉ một phần nhỏ trong
số các ứng dụng sử dụng thông tin này, các nhà thiết kế .NET chọn cách khởi dựng "lười"
đối với thuộc tính CurrentPrincipal. Trước tiên, mã lệnh thu lấy thuộc tính
Thread.CurrentPrincipal, bộ thực thi gán một thể hiện IPrincipal cho thuộc tính này theo
logic sau đây:
1. Nếu miền ứng dụng mà tiểu trình hiện hành đang thực thi có một principal mặc
định, thì bộ thực thi sẽ gán principal này cho thuộc tính Thread.CurrentPrincipal.
Theo mặc định, miền ứng dụng không có principal mặc định. Bạn có thể thiết lập
principal mặc định của mộ
t miền ứng dụng bằng cách gọi phương thức
SetThreadPrincipal trên một đối tượng System.AppDomain mô tả miền ứng dụng
bạn muốn cấu hình. Để gọi SetPrincipalPolicy, mã lệnh của bạn phải có phần tử
ControlPrincipal của SecurityPermission. Bạn chỉ có thể thiết lập principal mặc
định một lần cho mỗi miền ứng dụng; lời gọi SetThreadPrincipal thứ hai dẫn đến
ngoại lệ System.Security.Policy.PolicyException.
2. Nếu miền ứng dụng không có principal mặc định, chính sách principal của miền
ứng dụng sẽ xác định hiện thực IPrincipal nào sẽ được tạo ra và gán nó cho
Thread.CurrentPrincipal.
Để cấu hình chính sách principal cho một miền ứng dụng, bạn cần thu lấy đối tượng
AppDomain mô tả miền ứng dụng và gọi phương thức SetPrincipalPolicy của đối
tượng này. Phương thức SetPrincipalPolicy nhận vào một thành viên thuộc kiểu liệ
t
kê System.Security.Principal.PrincipalPolicy, giá trị này cho biết kiểu của đối tượng
IPrincipal sẽ được gán cho Thread.CurrentPrincipal. Để gọi SetPrincipalPolicy, mã
lệnh của bạn phải có phần tử ControlPrincipal của SecurityPermission. Bảng 13.4
liệt kê các giá trị của PrincipalPolicy; giá trị mặc định là UnauthenticatedPrincipal.
3. Nếu mã lệnh của bạn có phần tử ControlPrincipal của SecurityPermission, bạn có
thể tự tạo ra đối tượng IPrincipal và trực tiếp gán nó cho thuộc tính
Thread.CurrentPrincipal. Việc này sẽ ngăn bộ thực thi gán các
đối tượng IPrincipal
mặc định hoặc tạo ra các đối tượng mới dựa trên chính sách principal.
Bảng 13.4 Các thành viên thuộc kiểu liệt kê PrincipalPolicy
Tên thành viên Mô tả
NoPrincipal
Không có đối tượng IPrincipal nào
được tạo ra, Thread.CurrentPrincipal
trả về một tham chiếu null.
UnauthenticatedPrincipal
Một đối tượng
System.Security.Principal.
GenericPrincipal rỗng được tạo ra và
được gán cho Thread.CurrentPrincipal.
WindowsPrincipal
Một đối tượng WindowsPrincipal (mô
tả người dùng Windows đã đăng nhập)
được tạo ra và được gán cho
Thread.CurrentPrincipal.
Bất kể sử dụng phương pháp nào để thiết lập IPrincipal cho tiểu trình hiện hành, bạn cũng
phải làm như thế trước khi sử dụng các yêu cầu bảomật RBS, nếu không thông tin về
người dùng (IPrincipal) sẽ không có hiệu lực để bộ thực thi có thể xử lý yêu cầu. Bình
thường, khi chạy trên nền Windows, bạn thiết lập chính sách principal của một miền ứng
dụng là PrincipalPolicy.WindowsPrincipal để
thu lấy thông tin về người dùng Windows:
// Thu lấy một tham chiếu đến miền ứng dụng hiện hành.
AppDomain appDomain = System.AppDomain.CurrentDomain;
// Cấu hình miền ứng dụng hiện hành sao cho sử dụng các
// principal dựa-trên-Windows.
appDomain.SetPrincipalPolicy(
System.Security.Principal.PrincipalPolicy.WindowsPrincipal);
File RoleBasedSecurityExample.cs (trong đĩa CD đính kèm) minh họa cách sử dụng các
yêu cầu RBS bắt buộc và khai báo. Phần thứ nhất trình bày ba phương thức được bảo vệ
bằng các yêu cầu RBS bắt buộc. Nếu đối tượng Thread.CurrentPrincipal không thỏa các
đòi hỏi về tên người dùng và tư cách thành viên, yêu cầu sẽ ném ngoại lệ
SecurityException.
public static void ProtectedMethod1() {
// Một yêu cầu bảomật bắt buộc dựa-trên-vai-trò: principal
// hiện hành mô tả một định danh với tên là "nnbphuong81",
// các vai trò của principal là không quan trọng.
System.Security.Permissions.PrincipalPermission perm =
new System.Security.Permissions.PrincipalPermission
(@"MACHINE\nnbphuong81", null);
perm.Demand();
}
public static void ProtectedMethod2() {
// Một yêu cầu bảomật bắt buộc dựa-trên-vai-trò: principal
// hiện tại là một thành viên của vai trò "Managers" hay
// "Developers". Khi sử dụng PrincipalPermission, bạn chỉ có thể diễn
// tả mối quan hệ OR. Đó là vì phương thức PrincipalPolicy.Intersect
// luôn trả về một quyền rỗng trừ khi hai input là như nhau.
// Tuy nhiên, bạn có thể
sử dụng lôgic của mã lệnh để hiện thực
// các điều kiện phức tạp hơn. Trong trường hợp này, tên của định
// danh là không quan trọng.
System.Security.Permissions.PrincipalPermission perm1 =
new System.Security.Permissions.PrincipalPermission
(null, @"MACHINE\Managers");
System.Security.Permissions.PrincipalPermission perm2 =
new System.Security.Permissions.PrincipalPermission
(null, @"MACHINE\Developers");
perm1.Union(perm2).Demand();
}
public static void ProtectedMethod3() {
// Một yêu cầu bảomật bắt buộc dựa-trên-vai-trò: principal
// hiện tại mô tả một định danh với tên là "nnbphuong81" và
// là một thành viên của vai trò "Managers".
System.Security.Permissions.PrincipalPermission perm =
new System.Security.Permissions.PrincipalPermission
(@"MACHINE\nnbphuong81", @"MACHINE\Managers");
perm.Demand();
}
Phần thứ hai trình bày ba phương thức được bảo vệ bằng các yêu cầu RBS khai báo,
tương đương với các yêu cầu RBS bắt buộc vừa được trình bày ở trên:
// Một yêu cầu bảomật khai báo dựa-trên-vai-trò: principal hiện tại
// mô tả một định danh với tên là "nnbphuong81", các vai trò của
// principal là không quan trọng.
[PrincipalPermission(SecurityAction.Demand,
Name = @"MACHINE\nnbphuong81")]
public static void ProtectedMethod1() { /* */}
// Một yêu cầu bảomật khai báo dựa-trên-vai-trò: principal hiện tại
// là một thành viên của vai trò "Managers" hay "Developers". Bạn chỉ
// có thể diễn tả mối quan hệ OR (không thể diễn tả mối quan hệ AND).
// Tên của định danh là không quan trọng.
[PrincipalPermission(SecurityAction.Demand,
Role = @"MACHINE\Managers")]
[PrincipalPermission(SecurityAction.Demand,
Role = @"MACHINE\Developers")]
public static void ProtectedMethod2() { /* */}
// Một yêu cầu bảomật khai báo dựa-trên-vai-trò: principal hiện tại
// mô tả một
định danh với tên là "nnbphuong81" và là một thành viên
// của vai trò "Managers".
[PrincipalPermission(SecurityAction.Demand,
Name = @"MACHINE\nnbphuong81",
Role = @"MACHINE\Managers")]
public static void ProtectedMethod3() { /* */}
1.2 Giả nhận người dùng Windows
V
V
Bạn muốn mã lệnh của bạn chạy trong ngữ cảnh của một người dùng Windows
nào đó chứ không phải tài khoản người dùng hiện đang tích cực.
#
#
Thu lấy đối tượng System.Security.Principal.WindowsIdentity mô tả người
dùng Windows mà bạn cần giả nhận, rồi gọi phương thức Impersonate của đối
tượng WindowsIdentity.
Mỗi tiểu trình Windows đều có một access token kết giao—mô tả tài khoản Windows mà
tiểu trình hiện đang chạy trên danh nghĩa của tài khoản này. Hệ điều hành Windows sử
dụng access token để xác định một tiểu trình có các quyền thích đáng
để thực hiện các
thao tác được-bảo-vệ trên danh nghĩa của tài khoản này hay không, như đọc/ghi file, khởi
động lại hệ thống, và thay đổi thời gian hệ thống.
Theo mặc định, một ứng dụng được-quản-lý chạy trong ngữ cảnh của tài khoản Windows
đã thực thi ứng dụng. Điều này là hoàn toàn bình thường, nhưng đôi lúc bạn muốn chạy
ứng dụng trong ngữ cảnh của m
ột tài khoản Windows khác. Điều này đúng trong trường
hợp các ứng dụng phía server cần xử lý phiên giao dịch dựa trên danh nghĩa của các
người dùng kết nối đến server. Thông thường, một ứng dụng server chạy trong ngữ cảnh
của tài khoản Windows được tạo riêng cho ứng dụng—đây là tài khoản dịch vụ (service
account). Tài khoản dịch vụ này sẽ có các quyền tối thiểu để truy xuất các tài nguyên hệ
thống, làm cho ứng dụng hoạt động như thể đó là người dùng đã kết nối cho phép ứng
dụng truy xuất các hoạt động và tài nguyên phù hợp với quyền hạn của người dùng đó.
Khi một ứng dụng nắm lấy định danh của một người dùng khác, đây là sự giả nhận
(impersonation). Nếu được hiện thực đúng, sự giả nhận sẽ đơn giả
n hóa việc quản trị bảo
mật và thiết kế ứng dụng, trong khi vẫn duy trì việc giải trình người dùng.
# Như đã thảo luận trong mục 13.14, Windows access token và .NET principal của
một tiểu trình là các thực thể riêng biệt và có thể mô tả những người dùng khác
nhau. Kỹ thuật giả nhận được mô tả trong mục này chỉ thay đổi Windows
access token của tiểu trình hiện hành, chứ không thay đổi principal của tiểu
trình này. Để thay đổi principal của tiểu trình, mã lệnh của bạn phải có phần tử
ControlPrincipal của SecurityPermission và gán mộ
t đối tượng
System.Security.Principal.IPrincipal mới vào thuộc tính CurrentPrincipal của
System.Threading.Thread hiện hành.
Lớp System.Security.Principal.WindowsIdentity cung cấp các chức năng mà thông qua
đó, bạn có thể thực hiện sự giả nhận. Tuy nhiên, quá trình này tùy thuộc vào ứng dụng
của bạn đang chạy trên phiên bản Windows nào. Trên Windows Server 2003 trở về sau,
lớp WindowsIdentity hỗ trợ các phiên bản nạp của chồng phương thức khởi dựng, cho
phép tạo ra các đối tượng WindowsIdentity dựa trên tên tài khoản của người dùng cần giả
nhận. Trên tất cả các phiên bản Windows trước đó, trước hết bạn phải thu lấy
System.IntPtr chứa tham chiếu đến Windows access token mô tả người dùng cần giả
nhận. Để thu lấy tham chiếu này, bạn cần sử dụng một phương thức nguyên sinh như
LogonUser của Win32 API.
# Vấn đề chủ yếu khi thực hiện sự giả nhận trên Windows 2000 và Windows NT là
một tài khoản phải có đặc quyền SE_TCB_NAME thì mới có thể thực thi
LogonUser. Điều này đòi hỏi bạn cấu hình chính sách bảomật của Windows và
cấp cho tài khoản quyền “Act as part of operating system” (mức tin cậy rất cao).
Bạn đừng bao giờ trực tiếp cấp đặc quyền SE_TCB_NAME cho các tài khoản
ngườ
i dùng.
Một khi đã có đối tượng WindowsIdentity mô tả người dùng cần giả nhận, bạn hãy gọi
phương thức Impersonate của nó. Từ lúc này, tất cả các hành động mà mã lệnh của bạn
thực hiện đều diễn ra trong ngữ cảnh của tài khoản Windows đã được giả nhận. Phương
thức Impersonate trả về đối tượng System.Security.Principal.WindowsSecurityContext,
đối tượng này mô tả tài khoản tích cực trước khi giả nhận. Để
trở về tài khoản cũ, bạn cần
gọi phương thức Undo của đối tượng WindowsSecurityContext này.
Ứng dụng dưới đây trình bày sự giả nhận của một người dùng Windows. Ứng dụng này
cần hai đối số dòng lệnh: tên tài khoản của người dùng cần giả nhận và password của tài
khoản. Ứng dụng này sử dụng hàm LogonUser của Win32 API để thu lấy Windows
access token cho người dùng được ch
ỉ định, giả nhận người dùng này, rồi trở về ngữ cảnh
của người dùng cũ. Ví dụ, lệnh ImpersonationExample nnbphuong81 password sẽ giả
nhận người dùng nnbphuong81 nếu người dùng đó đã tồn tại trong cơ sở dữ liệu tài
khoản cục bộ.
using System;
using System.IO;
using System.Security.Principal;
using System.Security.Permissions;
using System.Runtime.InteropServices;
// Bảo đảm assembly có quyền truy xuất mã lệnh không-được-quản-lý
// và có quyền kiểm soát principal của tiểu trình.
[assembly:SecurityPermission(SecurityAction.RequestMinimum,
UnmanagedCode=true, ControlPrincipal=true)]
public class ImpersonationExample {
// Định nghĩa các hằng số được sử dụng cùng với hàm LogonUser.
const int LOGON32_PROVIDER_DEFAULT = 0;
const int LOGON32_LOGON_INTERACTIVE = 2;
// Nhập hàm Win32 LogonUser từ advapi32.dll. Chỉ định
// "SetLastError = true" để có thể truy xuất các mã lỗi của Win32.
[DllImport("advapi32.dll", SetLastError=true)]
static extern int LogonUser(string userName, string domain,
string password, int logonType, int logonProvider,
ref IntPtr accessToken);
public static void Main(string[] args) {
// Tạo một IntPtr mới để giữ lấy access token
// do hàm LogonUser trả về.
IntPtr accessToken = IntPtr.Zero;
// Gọi LogonUser để thu lấy access token cho người dùng
// được chỉ định. Biến accessToken được truyền cho LogonUser
// bằng tham chiếu và sẽ chứa tham chiếu đến Windows access token
// nếu LogonUser thành công.
int result = LogonUser(
args[0], // tên người dùng để đăng nhập
".", // sử dụng CSDL tài khoản cục bộ
args[1], // password củ
a người dùng
LOGON32_LOGON_INTERACTIVE, // tạo một interactive login
LOGON32_PROVIDER_DEFAULT, // sử dụng logon provider mặc định
ref accessToken // nhận access token handle
);
// Nếu lỗi xảy ra (LogonUser trả về zero), hiển thị lỗi và thoát.
if (result == 0) {
Console.WriteLine("LogonUser returned error {0}",
Marshal.GetLastWin32Error());
} else {
// Tạo một WindowsIdentity mới từ Windows access token.
WindowsIdentity identity = new WindowsIdentity(accessToken);
// Hiển thị định danh đang tích cực (trước khi giả nhận).
Console.WriteLine("Identity before impersonation = {0}",
WindowsIdentity.GetCurrent().Name);
// Giả nhận người dùng đã được chỉ định. Đối tượng
// WindowsImpersonationContext chứa các thông tin
// cần thiết để trở về ngữ cảnh của người dùng cũ.
WindowsImpersonationContext impContext =
identity.Impersonate();
// Hiển thị định danh đang tích cực (trong lúc giả nhận).
Console.WriteLine("Identity during impersonation = {0}",
WindowsIdentity.GetCurrent().Name);
//
***************************************************************
// Thực hiện các hành động với danh nghĩa người dùng được giả nhậ
n
//
***************************************************************
// Trở về người dùng Windows cũ bằng đối tượng
// WindowsImpersonationContext.
impContext.Undo();as
// Hiển thị định danh đang tích cực (sau khi giả nhận).
Console.WriteLine("Identity after impersonation = {0}",
WindowsIdentity.GetCurrent().Name);
}
}
}
.
Đoạn mã dưới đây trình bày cú pháp của một yêu cầu bảo mật bắt buộc:
// Cú pháp của một yêu cầu bảo mật bắt buộc dựa-trên-vai-trò.
public static void.
Đoạn mã dưới đây trình bày cú pháp của một yêu cầu bảo mật khai báo:
// Cú pháp của một yêu cầu bảo mật khai báo dựa-trên-vai-trò.
[PrincipalPermission(SecurityAction.Demand,