PHP 的身份驗證。簡單、輕巧且安全。
一次編寫,隨處使用。
完全與框架和資料庫無關。
pdo
)mysqlnd
)或PostgreSQL 驅動程式 ( pgsql
)或SQLite 驅動程式 ( sqlite
)openssl
)透過 Composer [?] 包含庫:
$ composer require delight-im/auth
包括 Composer 自動載入器:
require __DIR__ . ' /vendor/autoload.php ' ;
設定資料庫並建立所需的表:
從該專案的早期版本遷移?請參閱我們的升級指南尋求協助。
// $ db = new PDO ( ' mysql:dbname=my-database ; host = localhost ; charset = utf8mb4' , ' my-username' , ' my-password' ) ;
// or
// $ db = new PDO ( ' pgsql:dbname=my-database ; host = localhost ; port = 5432 ' , ' my-username' , ' my-password' ) ;
// or
// $ db = new PDO ( ' sqlite:../Databases/my-database.sqlite' ) ;
// or
// $ db = Delight D b P doDatabase::fromDsn ( new Delight D b P doDsn ( ' mysql:dbname=my-database ; host = localhost ; charset = utf8mb4' , ' my-username' , ' my-password' ) ) ;
// or
// $ db = Delight D b P doDatabase::fromDsn ( new Delight D b P doDsn ( ' pgsql:dbname=my-database ; host = localhost ; port = 5432 ' , ' my-username' , ' my-password' ) ) ;
// or
// $ db = Delight D b P doDatabase::fromDsn ( new Delight D b P doDsn ( ' sqlite:../Databases/my-database.sqlite' ) ) ;
$ auth = new Delight Auth Auth ( $ db );
如果您已經有一個打開的PDO
連接,只需重新使用它即可。資料庫使用者(例如my-username
)至少需要該庫(或其父資料庫)所使用的表的權限SELECT
、 INSERT
、 UPDATE
和DELETE
。
如果您的 Web 伺服器位於代理伺服器後面,且$_SERVER['REMOTE_ADDR']
僅包含代理程式的 IP 位址,則必須將使用者的真實 IP 位址傳遞給第二個參數中的建構函數,該參數名為$ipAddress
。預設值是 PHP 接收到的常用遠端 IP 位址。
如果此庫的資料庫表需要公共前綴,例如my_users
而不是users
(對於其他表也同樣),請將前綴(例如my_
)作為第三個參數傳遞給建構函數,該構造函數名為$dbTablePrefix
。這是可選的,預設情況下前綴為空。
在開發過程中,您可能想要停用此程式庫執行的請求限製或限制。為此,請將false
作為第四個參數傳遞給建構函數,該參數名為$throttling
。該功能預設啟用。
在會話的生命週期內,某些使用者資料可能會被另一個會話中的用戶端或管理員遠端變更。這意味著此資訊必須定期與其資料庫中的權威來源重新同步,該程式庫會自動執行此操作。預設情況下,這種情況每五分鐘發生一次。如果要變更此間隔,請將自訂間隔(以秒為單位)作為第五個參數傳遞給建構函數,該參數名為$sessionResyncInterval
。
如果所有資料庫表都需要公共資料庫名稱、模式名稱或其他必須明確指定的限定符,您可以選擇將該限定符作為第六個參數傳遞給建構函數,該參數名為$dbSchema
。
如果您也想獨立使用PdoDatabase
實例(例如$db
),請參閱資料庫庫的文檔。
try {
$ userId = $ auth -> register ( $ _POST [ ' email ' ], $ _POST [ ' password ' ], $ _POST [ ' username ' ], function ( $ selector , $ token ) {
echo ' Send ' . $ selector . ' and ' . $ token . ' to the user (e.g. via email) ' ;
echo ' For emails, consider using the mail(...) function, Symfony Mailer, Swiftmailer, PHPMailer, etc. ' ;
echo ' For SMS, consider using a third-party service and a compatible SDK ' ;
});
echo ' We have signed up a new user with the ID ' . $ userId ;
}
catch ( Delight Auth InvalidEmailException $ e ) {
die ( ' Invalid email address ' );
}
catch ( Delight Auth InvalidPasswordException $ e ) {
die ( ' Invalid password ' );
}
catch ( Delight Auth UserAlreadyExistsException $ e ) {
die ( ' User already exists ' );
}
catch ( Delight Auth TooManyRequestsException $ e ) {
die ( ' Too many requests ' );
}
注意:匿名回呼函數是一個閉包。因此,除了它自己的參數之外,內部只有像$_GET
、 $_POST
、 $_COOKIE
和$_SERVER
這樣的超級全域變數可用。對於父作用域中的任何其他變量,您需要透過在參數列表後面加上use
子句來明確地在內部提供副本。
第三個參數中的使用者名稱是可選的。如果您不想管理用戶名,可以在此處傳遞null
。
另一方面,如果您想強制使用唯一的使用者名,只需呼叫registerWithUniqueUsername
而不是register
,並準備好捕獲DuplicateUsernameException
。
注意:在接受和管理使用者名稱時,您可能需要排除非列印控製字符和某些可列印特殊字符,如字符類[x00-x1fx7f/:\]
中。為此,您可以將對Auth#register
或Auth#registerWithUniqueUsername
呼叫包裝在條件分支內,例如,僅在滿足以下條件時接受使用者名稱:
if ( preg_match ( ' /[x00-x1fx7f/: \\ ]/ ' , $ username ) === 0 ) {
// ...
}
對於電子郵件驗證,您應該使用選擇器和令牌建立一個 URL 並將其發送給用戶,例如:
$ url = ' https://www.example.com/verify_email?selector= ' . urlencode ( $ selector ) . ' &token= ' . urlencode ( $ token );
如果您不想執行電子郵件驗證,只需省略Auth#register
最後一個參數,即匿名函數或閉包。那麼新用戶將立即生效。
需要儲存額外的用戶資訊?請繼續閱讀此處。
注意:當傳送電子郵件給使用者時,請注意(可選)使用者名稱此時尚未被確認為(新)電子郵件地址擁有者可接受。它可能包含非實際地址所有者所選擇的冒犯性或誤導性語言。
try {
$ auth -> login ( $ _POST [ ' email ' ], $ _POST [ ' password ' ]);
echo ' User is logged in ' ;
}
catch ( Delight Auth InvalidEmailException $ e ) {
die ( ' Wrong email address ' );
}
catch ( Delight Auth InvalidPasswordException $ e ) {
die ( ' Wrong password ' );
}
catch ( Delight Auth EmailNotVerifiedException $ e ) {
die ( ' Email not verified ' );
}
catch ( Delight Auth TooManyRequestsException $ e ) {
die ( ' Too many requests ' );
}
另一方面,如果您想使用使用者名稱登錄,除了透過電子郵件地址登入或作為替代之外,這也是可能的。只需呼叫方法loginWithUsername
而不是方法login
。然後,不要捕獲InvalidEmailException
,而是確保捕獲UnknownUsernameException
和AmbiguousUsernameException
。您可能還想閱讀解釋如何註冊新用戶的部分中有關用戶名唯一性的註釋。
從使用者在驗證電子郵件中點選的 URL 中提取選擇器和令牌。
try {
$ auth -> confirmEmail ( $ _GET [ ' selector ' ], $ _GET [ ' token ' ]);
echo ' Email address has been verified ' ;
}
catch ( Delight Auth InvalidSelectorTokenPairException $ e ) {
die ( ' Invalid token ' );
}
catch ( Delight Auth TokenExpiredException $ e ) {
die ( ' Token expired ' );
}
catch ( Delight Auth UserAlreadyExistsException $ e ) {
die ( ' Email address already exists ' );
}
catch ( Delight Auth TooManyRequestsException $ e ) {
die ( ' Too many requests ' );
}
如果您希望使用者確認成功後自動登錄,只需呼叫confirmEmailAndSignIn
而不是confirmEmail
。該替代方法還透過其可選的第三個參數支援持久登入。
成功後, confirmEmail
和confirmEmailAndSignIn
兩個方法都會在索引一處傳回一個數組,其中包含剛剛經過驗證的使用者新電子郵件地址。如果確認是針對位址變更而不是簡單的位址驗證,則使用者的舊電子郵件地址將包含在陣列中的索引零處。
Auth#login
和Auth#confirmEmailAndSignIn
方法的第三個參數控制登入是否使用長期 cookie 進行持久化。透過這種持久登錄,即使瀏覽器會話已關閉並且會話 cookie 已過期,使用者也可以長時間保持身份驗證。通常,您希望使用此功能讓使用者保持登入狀態數週或數月,這稱為「記住我」或「保持登入狀態」。許多用戶會發現這更方便,但如果他們讓設備無人看管,可能會不太安全。
if ( $ _POST [ ' remember ' ] == 1 ) {
// keep logged in for one year
$ rememberDuration = ( int ) ( 60 * 60 * 24 * 365.25 );
}
else {
// do not keep logged in after session ends
$ rememberDuration = null ;
}
// ...
$ auth -> login ( $ _POST [ ' email ' ], $ _POST [ ' password ' ], $ rememberDuration );
// . . .
如果沒有預設行為的持久登錄,使用者只會保持登入狀態,直到關閉瀏覽器,或透過 PHP 中的session.cookie_lifetime
和session.gc_maxlifetime
配置。
省略第三個參數或將其設為null
以停用該功能。否則,您可以詢問用戶是否要啟用「記住我」。這通常是透過使用者介面中的複選框來完成的。使用該複選框中的輸入在null
和預先定義的持續時間(以秒為單位)之間進行決定,例如60 * 60 * 24 * 365.25
表示一年。
try {
$ auth -> forgotPassword ( $ _POST [ ' email ' ], function ( $ selector , $ token ) {
echo ' Send ' . $ selector . ' and ' . $ token . ' to the user (e.g. via email) ' ;
echo ' For emails, consider using the mail(...) function, Symfony Mailer, Swiftmailer, PHPMailer, etc. ' ;
echo ' For SMS, consider using a third-party service and a compatible SDK ' ;
});
echo ' Request has been generated ' ;
}
catch ( Delight Auth InvalidEmailException $ e ) {
die ( ' Invalid email address ' );
}
catch ( Delight Auth EmailNotVerifiedException $ e ) {
die ( ' Email not verified ' );
}
catch ( Delight Auth ResetDisabledException $ e ) {
die ( ' Password reset is disabled ' );
}
catch ( Delight Auth TooManyRequestsException $ e ) {
die ( ' Too many requests ' );
}
注意:匿名回呼函數是一個閉包。因此,除了它自己的參數之外,內部只有像$_GET
、 $_POST
、 $_COOKIE
和$_SERVER
這樣的超級全域變數可用。對於父作用域中的任何其他變量,您需要透過在參數列表後面加上use
子句來明確地在內部提供副本。
您應該使用選擇器和令牌來建立一個 URL 並將其發送給用戶,例如:
$ url = ' https://www.example.com/reset_password?selector= ' . urlencode ( $ selector ) . ' &token= ' . urlencode ( $ token );
如果密碼重設請求的預設生命週期不適合您,您可以使用Auth#forgotPassword
的第三個參數指定自訂時間間隔(以秒為單位),在此之後請求應過期。
下一步,用戶將點擊他們收到的連結。從 URL 中提取選擇器和令牌。
如果選擇器/令牌對有效,讓使用者選擇一個新密碼:
try {
$ auth -> canResetPasswordOrThrow ( $ _GET [ ' selector ' ], $ _GET [ ' token ' ]);
echo ' Put the selector into a "hidden" field (or keep it in the URL) ' ;
echo ' Put the token into a "hidden" field (or keep it in the URL) ' ;
echo ' Ask the user for their new password ' ;
}
catch ( Delight Auth InvalidSelectorTokenPairException $ e ) {
die ( ' Invalid token ' );
}
catch ( Delight Auth TokenExpiredException $ e ) {
die ( ' Token expired ' );
}
catch ( Delight Auth ResetDisabledException $ e ) {
die ( ' Password reset is disabled ' );
}
catch ( Delight Auth TooManyRequestsException $ e ) {
die ( ' Too many requests ' );
}
或者,如果您不需要任何錯誤訊息而只想檢查有效性,則可以使用稍微簡單的版本:
if ( $ auth -> canResetPassword ( $ _GET [ ' selector ' ], $ _GET [ ' token ' ])) {
echo ' Put the selector into a "hidden" field (or keep it in the URL) ' ;
echo ' Put the token into a "hidden" field (or keep it in the URL) ' ;
echo ' Ask the user for their new password ' ;
}
現在,當您擁有用戶的新密碼(並且仍然擁有其他兩個資訊)時,您可以重設密碼:
try {
$ auth -> resetPassword ( $ _POST [ ' selector ' ], $ _POST [ ' token ' ], $ _POST [ ' password ' ]);
echo ' Password has been reset ' ;
}
catch ( Delight Auth InvalidSelectorTokenPairException $ e ) {
die ( ' Invalid token ' );
}
catch ( Delight Auth TokenExpiredException $ e ) {
die ( ' Token expired ' );
}
catch ( Delight Auth ResetDisabledException $ e ) {
die ( ' Password reset is disabled ' );
}
catch ( Delight Auth InvalidPasswordException $ e ) {
die ( ' Invalid password ' );
}
catch ( Delight Auth TooManyRequestsException $ e ) {
die ( ' Too many requests ' );
}
您想讓對應的使用者在密碼重設成功後自動登入嗎?只需使用Auth#resetPasswordAndSignIn
而不是Auth#resetPassword
即可立即登入使用者。
如果您需要使用者的 ID 或電子郵件地址,例如向他們發送密碼已成功重設的通知,只需使用Auth#resetPassword
的回傳值,該值是包含兩個名為id
和email
項目的陣列。
如果用戶目前已登錄,他們可以更改密碼。
try {
$ auth -> changePassword ( $ _POST [ ' oldPassword ' ], $ _POST [ ' newPassword ' ]);
echo ' Password has been changed ' ;
}
catch ( Delight Auth NotLoggedInException $ e ) {
die ( ' Not logged in ' );
}
catch ( Delight Auth InvalidPasswordException $ e ) {
die ( ' Invalid password(s) ' );
}
catch ( Delight Auth TooManyRequestsException $ e ) {
die ( ' Too many requests ' );
}
處理密碼變更的建議方法是詢問使用者目前(以及很快舊的)密碼並要求其進行驗證。如上所示。
但是,如果您確定不需要該確認,則可以呼叫changePasswordWithoutOldPassword
而不是changePassword
,並從該方法呼叫中刪除第一個參數(否則該參數將包含舊密碼)。
無論如何,在使用者的密碼變更後,您應該向其帳戶的主電子郵件地址發送一封電子郵件作為帶外通知,告知帳戶所有者此重要變更。
如果用戶目前已登錄,他們可以更改其電子郵件地址。
try {
if ( $ auth -> reconfirmPassword ( $ _POST [ ' password ' ])) {
$ auth -> changeEmail ( $ _POST [ ' newEmail ' ], function ( $ selector , $ token ) {
echo ' Send ' . $ selector . ' and ' . $ token . ' to the user (e.g. via email to the *new* address) ' ;
echo ' For emails, consider using the mail(...) function, Symfony Mailer, Swiftmailer, PHPMailer, etc. ' ;
echo ' For SMS, consider using a third-party service and a compatible SDK ' ;
});
echo ' The change will take effect as soon as the new email address has been confirmed ' ;
}
else {
echo ' We can ' t say if the user is who they claim to be ' ;
}
}
catch ( Delight Auth InvalidEmailException $ e ) {
die ( ' Invalid email address ' );
}
catch ( Delight Auth UserAlreadyExistsException $ e ) {
die ( ' Email address already exists ' );
}
catch ( Delight Auth EmailNotVerifiedException $ e ) {
die ( ' Account not verified ' );
}
catch ( Delight Auth NotLoggedInException $ e ) {
die ( ' Not logged in ' );
}
catch ( Delight Auth TooManyRequestsException $ e ) {
die ( ' Too many requests ' );
}
注意:匿名回呼函數是一個閉包。因此,除了它自己的參數之外,內部只有像$_GET
、 $_POST
、 $_COOKIE
和$_SERVER
這樣的超級全域變數可用。對於父作用域中的任何其他變量,您需要透過在參數列表後面加上use
子句來明確地在內部提供副本。
對於電子郵件驗證,您應該使用選擇器和令牌建立一個 URL 並將其發送給用戶,例如:
$ url = ' https://www.example.com/verify_email?selector= ' . urlencode ( $ selector ) . ' &token= ' . urlencode ( $ token );
注意:當傳送電子郵件給使用者時,請注意(可選)使用者名稱此時尚未被確認為(新)電子郵件地址擁有者可接受。它可能包含非實際地址所有者所選擇的冒犯性或誤導性語言。
在提出更改電子郵件地址的請求後,或者更好的是,在用戶確認更改後,您應該向其帳戶之前的電子郵件地址發送一封電子郵件,作為帶外通知,告知帳戶所有者以下信息:這個關鍵的改變。
注意:如預期的那樣,對使用者電子郵件地址的變更會立即在本機會話中生效。不過,在其他會話中(例如在其他裝置上),變更可能需要最多五分鐘才能生效。這會提高效能並且通常不會造成問題。不過,如果您想更改此行為,只需減少(或可能增加)作為名為$sessionResyncInterval
的參數傳遞給Auth
建構函數的值即可。
如果無法將較早的確認請求發送給用戶,或者用戶錯過了該請求,或者他們只是不想再等待,您可以重新發送較早的請求,如下所示:
try {
$ auth -> resendConfirmationForEmail ( $ _POST [ ' email ' ], function ( $ selector , $ token ) {
echo ' Send ' . $ selector . ' and ' . $ token . ' to the user (e.g. via email) ' ;
echo ' For emails, consider using the mail(...) function, Symfony Mailer, Swiftmailer, PHPMailer, etc. ' ;
echo ' For SMS, consider using a third-party service and a compatible SDK ' ;
});
echo ' The user may now respond to the confirmation request (usually by clicking a link) ' ;
}
catch ( Delight Auth ConfirmationRequestNotFound $ e ) {
die ( ' No earlier request found that could be re-sent ' );
}
catch ( Delight Auth TooManyRequestsException $ e ) {
die ( ' There have been too many requests -- try again later ' );
}
如果您想透過 ID 而不是電子郵件地址來指定用戶,也可以這樣做:
try {
$ auth -> resendConfirmationForUserId ( $ _POST [ ' userId ' ], function ( $ selector , $ token ) {
echo ' Send ' . $ selector . ' and ' . $ token . ' to the user (e.g. via email) ' ;
echo ' For emails, consider using the mail(...) function, Symfony Mailer, Swiftmailer, PHPMailer, etc. ' ;
echo ' For SMS, consider using a third-party service and a compatible SDK ' ;
});
echo ' The user may now respond to the confirmation request (usually by clicking a link) ' ;
}
catch ( Delight Auth ConfirmationRequestNotFound $ e ) {
die ( ' No earlier request found that could be re-sent ' );
}
catch ( Delight Auth TooManyRequestsException $ e ) {
die ( ' There have been too many requests -- try again later ' );
}
注意:匿名回呼函數是一個閉包。因此,除了它自己的參數之外,內部只有像$_GET
、 $_POST
、 $_COOKIE
和$_SERVER
這樣的超級全域變數可用。對於父作用域中的任何其他變量,您需要透過在參數列表後面加上use
子句來明確地在內部提供副本。
通常,您應該使用選擇器和令牌來建立一個 URL 並將其發送給用戶,例如如下所示:
$ url = ' https://www.example.com/verify_email?selector= ' . urlencode ( $ selector ) . ' &token= ' . urlencode ( $ token );
注意:當傳送電子郵件給使用者時,請注意(可選)使用者名稱此時尚未被確認為(新)電子郵件地址擁有者可接受。它可能包含非實際地址所有者所選擇的冒犯性或誤導性語言。
$ auth -> logOut ();
// or
try {
$ auth -> logOutEverywhereElse ();
}
catch ( Delight Auth NotLoggedInException $ e ) {
die ( ' Not logged in ' );
}
// or
try {
$ auth -> logOutEverywhere ();
}
catch ( Delight Auth NotLoggedInException $ e ) {
die ( ' Not logged in ' );
}
此外,如果您還在會話中儲存自訂訊息,並且希望刪除該訊息,則可以透過呼叫第二種方法來銷毀整個會話:
$ auth -> destroySession ();
注意:如預期的那樣,全域註銷會立即在本機會話中生效。不過,在其他會話中(例如在其他裝置上),變更可能需要最多五分鐘才能生效。這會提高效能並且通常不會造成問題。不過,如果您想更改此行為,只需減少(或可能增加)作為名為$sessionResyncInterval
的參數傳遞給Auth
建構函數的值即可。
if ( $ auth -> isLoggedIn ()) {
echo ' User is signed in ' ;
}
else {
echo ' User is not signed in yet ' ;
}
此方法的簡寫/別名是$auth->check()
。
$ id = $ auth -> getUserId ();
如果使用者目前未登入,則傳回null
。
此方法的簡寫/別名是$auth->id()
。
$ email = $ auth -> getEmail ();
如果使用者目前未登入,則傳回null
。
$ username = $ auth -> getUsername ();
請記住,用戶名是可選的,如果您在註冊期間提供了用戶名,則只有一個用戶名。
如果使用者目前未登入,則傳回null
。
if ( $ auth -> isNormal ()) {
echo ' User is in default state ' ;
}
if ( $ auth -> isArchived ()) {
echo ' User has been archived ' ;
}
if ( $ auth -> isBanned ()) {
echo ' User has been banned ' ;
}
if ( $ auth -> isLocked ()) {
echo ' User has been locked ' ;
}
if ( $ auth -> isPendingReview ()) {
echo ' User is pending review ' ;
}
if ( $ auth -> isSuspended ()) {
echo ' User has been suspended ' ;
}
if ( $ auth -> isRemembered ()) {
echo ' User did not sign in but was logged in through their long-lived cookie ' ;
}
else {
echo ' User signed in manually ' ;
}
如果使用者目前未登入,則傳回null
。
$ ip = $ auth -> getIpAddress ();
為了保持該庫對所有用途的適用性以及其完全可重用性,它沒有附帶用於用戶資訊的附加捆綁列。但當然,您不必沒有額外的用戶資訊:
以下是如何以可維護和可重複使用的方式將此庫與您自己的表一起使用以獲取自訂使用者資訊:
新增任意數量的自訂資料庫表,用於儲存自訂使用者訊息,例如名為profiles
表。
每當您呼叫register
方法(傳回新使用者的 ID)時,請隨後新增您自己的邏輯來填入您的自訂資料庫表。
如果您很少需要自訂使用者訊息,您可以根據需要檢索它。但是,如果您更頻繁地需要它,您可能希望將其包含在會話資料中。以下方法是您如何以可靠的方式載入和存取資料:
function getUserInfo ( Delight Auth Auth $ auth ) {
if (! $ auth -> isLoggedIn ()) {
return null ;
}
if (! isset ( $ _SESSION [ ' _internal_user_info ' ])) {
// TODO : load your custom user information and assign it to the session variable below
// $ _SESSION [ ' _internal_user_info' ] = ...
}
return $ _SESSION [ ' _internal_user_info ' ];
}
每當您想再次確認使用者的身份時,例如在允許使用者執行某些「危險」操作之前,您應該再次驗證他們的密碼,以確認他們確實是他們聲稱的身份。
例如,當使用者被長期存在的 cookie 記住並因此Auth#isRemembered
傳回true
時,這表示該使用者可能已經有一段時間沒有輸入密碼了。在這種情況下,您可能需要重新確認他們的密碼。
try {
if ( $ auth -> reconfirmPassword ( $ _POST [ ' password ' ])) {
echo ' The user really seems to be who they claim to be ' ;
}
else {
echo ' We can ' t say if the user is who they claim to be ' ;
}
}
catch ( Delight Auth NotLoggedInException $ e ) {
die ( ' The user is not signed in ' );
}
catch ( Delight Auth TooManyRequestsException $ e ) {
die ( ' Too many requests ' );
}
每個使用者都可以擁有任意數量的角色,您可以使用這些角色來實施授權並優化存取控制。
使用者可能根本沒有角色(預設情況下他們有角色)、只有一個角色或角色的任意組合。
if ( $ auth -> hasRole ( Delight Auth Role:: SUPER_MODERATOR )) {
echo ' The user is a super moderator ' ;
}
// or
if ( $ auth -> hasAnyRole ( Delight Auth Role:: DEVELOPER , Delight Auth Role:: MANAGER )) {
echo ' The user is either a developer, or a manager, or both ' ;
}
// or
if ( $ auth -> hasAllRoles ( Delight Auth Role:: DEVELOPER , Delight Auth Role:: MANAGER )) {
echo ' The user is both a developer and a manager ' ;
}
雖然方法hasRole
只接受一個角色作為其參數,但hasAnyRole
和hasAllRoles
這兩個方法可以接受您想要檢查的任意數量的角色。
或者,您可以獲得已指派給使用者的所有角色的清單:
$ auth -> getRoles ();
Delight Auth Role:: ADMIN ;
Delight Auth Role:: AUTHOR ;
Delight Auth Role:: COLLABORATOR ;
Delight Auth Role:: CONSULTANT ;
Delight Auth Role:: CONSUMER ;
Delight Auth Role:: CONTRIBUTOR ;
Delight Auth Role:: COORDINATOR ;
Delight Auth Role:: CREATOR ;
Delight Auth Role:: DEVELOPER ;
Delight Auth Role:: DIRECTOR ;
Delight Auth Role:: EDITOR ;
Delight Auth Role:: EMPLOYEE ;
Delight Auth Role:: MAINTAINER ;
Delight Auth Role:: MANAGER ;
Delight Auth Role:: MODERATOR ;
Delight Auth Role:: PUBLISHER ;
Delight Auth Role:: REVIEWER ;
Delight Auth Role:: SUBSCRIBER ;
Delight Auth Role:: SUPER_ADMIN ;
Delight Auth Role:: SUPER_EDITOR ;
Delight Auth Role:: SUPER_MODERATOR ;
Delight Auth Role:: TRANSLATOR ;
您可以使用其中任何角色並忽略不需要的角色。上面的清單也可以透過程式方式檢索,採用以下三種格式之一:
Delight Auth Role:: getMap ();
// or
Delight Auth Role:: getNames ();
// or
Delight Auth Role:: getValues ();
每個使用者的權限按照在整個程式碼庫中指定的角色要求的方式進行編碼。如果使用特定使用者的角色集評估這些要求,則會得到隱式檢查的權限。
對於較大的項目,通常建議在單一位置維護權限的定義。然後,您不再檢查業務邏輯中的角色,而是檢查個人權限。您可以如下實現該概念:
function canEditArticle ( Delight Auth Auth $ auth ) {
return $ auth -> hasAnyRole (
Delight Auth Role:: MODERATOR ,
Delight Auth Role:: SUPER_MODERATOR ,
Delight Auth Role:: ADMIN ,
Delight Auth Role:: SUPER_ADMIN
);
}
// . . .
if ( canEditArticle ( $ auth )) {
echo ' The user can edit articles here ' ;
}
// . . .
if ( canEditArticle ( $ auth )) {
echo ' ... and here ' ;
}
// . . .
if ( canEditArticle ( $ auth )) {
echo ' ... and here ' ;
}
可以看到,某個使用者是否可以編輯文章的權限儲存在一個中心位置。這種實現有兩個主要優點:
如果你想知道哪些使用者可以編輯文章,你不必在各個地方檢查你的業務邏輯,而只需要看具體的權限是在哪裡定義的。如果您想更改可以編輯文章的人員,您也只需在一個位置執行此操作,而不是在整個程式碼庫中執行此操作。
但是,在第一次實施存取限制時,這也會帶來稍多的開銷,這對於您的專案來說可能值得或不值得。
如果包含的角色名稱不適合您,您可以使用自己的識別碼為任意數量的角色添加別名,例如:
namespace My Namespace ;
final class MyRole {
const CUSTOMER_SERVICE_AGENT = Delight Auth Role:: REVIEWER ;
const FINANCIAL_DIRECTOR = Delight Auth Role:: COORDINATOR ;
private function __construct () {}
}
上面的範例將允許您使用
My Namespace MyRole:: CUSTOMER_SERVICE_AGENT ;
// and
My Namespace MyRole:: FINANCIAL_DIRECTOR ;
而不是
Delight Auth Role:: REVIEWER ;
// and
Delight Auth Role:: COORDINATOR ;
請記住,不要將單一包含的角色別名為具有自訂名稱的多個角色。
雖然透過電子郵件重設密碼是一項方便的功能,大多數使用者有時會覺得很有幫助,但此功能的可用性意味著您服務上的帳戶的安全性取決於使用者的關聯電子郵件帳戶。
您可以為具有安全意識(且經驗豐富)的使用者提供停用其帳戶密碼重設(並稍後再次啟用)的可能性,以增強安全性:
try {
if ( $ auth -> reconfirmPassword ( $ _POST [ ' password ' ])) {
$ auth -> setPasswordResetEnabled ( $ _POST [ ' enabled ' ] == 1 );
echo ' The setting has been changed ' ;
}
else {
echo ' We can ' t say if the user is who they claim to be ' ;
}
}
catch ( Delight Auth NotLoggedInException $ e ) {
die ( ' The user is not signed in ' );
}
catch ( Delight Auth TooManyRequestsException $ e ) {
die ( ' Too many requests ' );
}
為了檢查此設定的目前值,請使用來自的返回值
$ auth -> isPasswordResetEnabled ();
使用者介面中正確的預設選項。您無需檢查此值以了解該功能的限制,這些限制是自動強制執行的。
該庫提供的所有方法都會自動受到保護,以防止來自客戶端的過多請求。如果需要,您可以使用傳遞給建構函數的$throttling
參數(暫時)來停用此保護。
如果您還想對外部功能或方法(例如您自己的程式碼中的功能或方法)進行節流或速率限制,則可以使用內建的輔助方法進行節流和速率限制:
try {
// throttle the specified resource or feature to * 3 * requests per * 60 * seconds
$ auth -> throttle ([ ' my-resource-name ' ], 3 , 60 );
echo ' Do something with the resource or feature ' ;
}
catch ( Delight Auth TooManyRequestsException $ e ) {
// operation cancelled
http_response_code ( 429 );
exit ;
}
如果資源或功能的保護也應依賴另一個屬性,例如要根據 IP 位址單獨追蹤某些內容,只需在資源描述中添加更多數據,例如:
[ ' my-resource-name ' , $ _SERVER [ ' REMOTE_ADDR ' ] ]
// instead of
// [ ' my-resource-name' ]
透過將突發因子指定為第四個參數,可以允許在高峰需求期間出現短暫的活動突發。例如,與普遍接受的水平相比,值5
將允許臨時爆發五倍的活動。
在某些情況下,您可能只想模擬節流或速率限制。這使您可以檢查是否允許執行某個操作,而無需實際修改活動追蹤器。為此,只需將true
作為第五個參數傳遞即可。
注意:當您在實例上停用限制(使用傳遞給建構函數的$throttling
參數)時,這會關閉自動內部保護以及您自己的應用程式程式碼中對Auth#throttle
的任何呼叫的效果- 除非您也設定了在特定的Auth#throttle
呼叫中可選的$force
參數為true
。
管理介面可透過$auth->admin()
取得。您可以在此介面上呼叫各種方法,如下所述。
在公開對此介面的存取之前,不要忘記實施安全存取控制。例如,您可以僅向具有管理員角色的登入使用者提供對此介面的存取權限,或僅在私有腳本中使用該介面。
try {
$ userId = $ auth -> admin ()-> createUser ( $ _POST [ ' email ' ], $ _POST [ ' password ' ], $ _POST [ ' username ' ]);
echo ' We have signed up a new user with the ID ' . $ userId ;
}
catch ( Delight Auth InvalidEmailException $ e ) {
die ( ' Invalid email address ' );
}
catch ( Delight Auth InvalidPasswordException $ e ) {
die ( ' Invalid password ' );
}
catch ( Delight Auth UserAlreadyExistsException $ e ) {
die ( ' User already exists ' );
}
第三個參數中的使用者名稱是可選的。如果您不想管理用戶名,可以在此處傳遞null
。
另一方面,如果您想要強制使用唯一的使用者名,只需呼叫createUserWithUniqueUsername
而不是createUser
,並準備好捕獲DuplicateUsernameException
。
透過ID刪除用戶:
try {
$ auth -> admin ()-> deleteUserById ( $ _POST [ ' id ' ]);
}
catch ( Delight Auth UnknownIdException $ e ) {
die ( ' Unknown ID ' );
}
透過電子郵件地址刪除使用者:
try {
$ auth -> admin ()-> deleteUserByEmail ( $ _POST [ ' email ' ]);
}
catch ( Delight Auth InvalidEmailException $ e ) {
die ( ' Unknown email address ' );
}
透過用戶名刪除用戶:
try {
$ auth -> admin ()-> deleteUserByUsername ( $ _POST [ ' username ' ]);
}
catch ( Delight Auth UnknownUsernameException $ e ) {
die ( ' Unknown username ' );
}
catch ( Delight Auth AmbiguousUsernameException $ e ) {
die ( ' Ambiguous username ' );
}
當獲取所有使用者的清單時,項目和用例之間的需求差異很大,並且自訂很常見。例如,您可能想要取得不同的欄位、連接相關資料表、以特定條件進行篩選、變更結果的排序方式(以不同的方向)以及限制結果的數量(同時提供偏移量)。
這就是為什麼使用單一自訂 SQL 查詢會更容易。從以下幾點開始:
SELECT id, email, username, status, verified, roles_mask, registered, last_login FROM users;
try {
$ auth -> admin ()-> addRoleForUserById ( $ userId , Delight Auth Role:: ADMIN );
}
catch ( Delight Auth UnknownIdException $ e ) {
die ( ' Unknown user ID ' );
}
// or
try {
$ auth -> admin ()-> addRoleForUserByEmail ( $ userEmail , Delight Auth Role:: ADMIN );
}
catch ( Delight Auth InvalidEmailException $ e ) {
die ( ' Unknown email address ' );
}
// or
try {
$ auth -> admin ()-> addRoleForUserByUsername ( $ username , Delight Auth Role:: ADMIN );
}
catch ( Delight Auth UnknownUsernameException $ e ) {
die ( ' Unknown username ' );
}
catch ( Delight Auth AmbiguousUsernameException $ e ) {
die ( ' Ambiguous username ' );
}
注意:使用者角色集的變更可能需要長達五分鐘才能生效。這會提高效能並且通常不會造成問題。不過,如果您想更改此行為,只需減少(或可能增加)作為名為$sessionResyncInterval
的參數傳遞給Auth
建構函數的值即可。
try {
$ auth -> admin ()-> removeRoleForUserById ( $ userId , Delight Auth Role:: ADMIN );
}
catch ( Delight Auth UnknownIdException $ e ) {
die ( ' Unknown user ID ' );
}
// or
try {
$ auth -> admin ()-> removeRoleForUserByEmail ( $ userEmail , Delight Auth Role:: ADMIN );
}
catch ( Delight Auth InvalidEmailException $ e ) {
die ( ' Unknown email address ' );
}
// or
try {
$ auth -> admin ()-> removeRoleForUserByUsername ( $ username , Delight Auth Role:: ADMIN );
}
catch ( Delight Auth UnknownUsernameException $ e ) {
die ( ' Unknown username ' );
}
catch ( Delight Auth AmbiguousUsernameException $ e ) {
die ( ' Ambiguous username ' );
}
注意:使用者角色集的變更可能需要長達五分鐘才能生效。這會提高效能並且通常不會造成問題。不過,如果您想更改此行為,只需減少(或可能增加)作為名為$sessionResyncInterval
的參數傳遞給Auth
建構函數的值即可。
try {
if ( $ auth -> admin ()-> doesUserHaveRole ( $ userId , Delight Auth Role:: ADMIN )) {
echo ' The specified user is an administrator ' ;
}
else {
echo ' The specified user is not an administrator ' ;
}
}
catch ( Delight Auth UnknownIdException $ e ) {
die ( ' Unknown user ID ' );
}
或者,您可以獲得已指派給使用者的所有角色的清單:
$ auth -> admin ()-> getRolesForUserById ( $ userId );
try {
$ auth -> admin ()-> logInAsUserById ( $ _POST [ ' id ' ]);
}
catch ( Delight Auth UnknownIdException $ e ) {
die ( ' Unknown ID ' );
}
catch ( Delight Auth EmailNotVerifiedException $ e ) {
die ( ' Email address not verified ' );
}
// or
try {
$ auth -> admin ()-> logInAsUserByEmail ( $ _POST [ ' email ' ]);
}
catch ( Delight Auth InvalidEmailException $ e ) {
die ( ' Unknown email address ' );
}
catch ( Delight Auth EmailNotVerifiedException $ e ) {
die ( ' Email address not verified ' );
}
// or
try {
$ auth -> admin ()-> logInAsUserByUsername ( $ _POST [ ' username ' ]);
}
catch ( Delight Auth UnknownUsernameException $ e ) {
die ( ' Unknown username ' );
}
catch ( Delight Auth AmbiguousUsernameException $ e ) {
die ( ' Ambiguous username ' );
}
catch ( Delight Auth EmailNotVerifiedException $ e ) {
die ( ' Email address not verified ' );
}
try {
$ auth -> admin ()-> changePasswordForUserById ( $ _POST [ ' id ' ], $ _POST [ ' newPassword ' ]);
}
catch ( Delight Auth UnknownIdException $ e ) {
die ( ' Unknown ID ' );
}
catch ( Delight Auth InvalidPasswordException $ e ) {
die ( ' Invalid password ' );
}
// or
try {
$ auth -> admin ()-> changePasswordForUserByUsername ( $ _POST [ ' username ' ], $ _POST [ ' newPassword ' ]);
}
catch ( Delight Auth UnknownUsernameException $ e ) {
die ( ' Unknown username ' );
}
catch ( Delight Auth AmbiguousUsernameException $ e ) {
die ( ' Ambiguous username ' );
}
catch ( Delight Auth InvalidPasswordException $ e ) {
die ( ' Invalid password ' );
}
該庫使用兩個 cookie 來保存客戶端狀態:第一個,您可以使用它來檢索其名稱
session_name ();
是通用(強制)會話 cookie。第二個(可選)cookie 僅用於持久登錄,其名稱可以如下擷取:
Delight Auth Auth:: createRememberCookieName ();
您可以透過以下方式之一重新命名此庫使用的會話 cookie(按建議順序):
在 PHP 配置 ( php.ini
) 中,找到包含session.name
指令的行並將其值變更為類似session_v1
值,如下所示:
session.name = session_v1
儘早在應用程式中建立Auth
實例之前,呼叫ini_set
將session.name
更改為session_v1
之類的名稱,如下所示:
ini_set ( ' session.name ' , ' session_v1 ' );
為此,必須在 PHP 配置 ( php.ini
) 中將session.auto_start
設為0
。
儘早在應用程式中建立Auth
實例之前,使用session_v1
等參數呼叫session_name
,如下所示:
session_name ( ' session_v1 ' );
為此,必須在 PHP 配置 ( php.ini
) 中將session.auto_start
設為0
。
在您變更會話 cookie 名稱後,持久登入的 cookie 名稱也會自動變更。
Cookie 的domain
屬性控制 Cookie 對哪個網域(以及哪些子網域)有效,從而控制使用者的會話和驗證狀態在何處可用。
建議的預設值是空字串,這表示 cookie 僅對目前主機有效,不包括可能存在的任何子網域。只有當您需要在不同子網域之間共用 cookie 時,才應使用不同的值。通常,您會希望在裸域和www
子網域之間共用 cookie,但您可能也希望在任何其他子網域集之間共用它們。
無論您選擇哪一組子網域,都應該將 cookie 的屬性設定為仍包含所有所需子網域的最具體的網域。例如,若要在example.com
和www.example.com
之間共用 cookie,您可以將該屬性設定為example.com
。但如果您想在sub1.app.example.com
和sub2.app.example.com
之間共用 cookie,您應該將該屬性設為app.example.com
。任何明確指定的網域名稱將始終包含可能存在的所有子網域。
您可以透過以下方式之一來變更屬性(按建議順序):
在 PHP 配置 ( php.ini
) 中,找到包含session.cookie_domain
指令的行並根據需要變更其值,例如:
session.cookie_domain = example.com
儘早在您的應用程式中,在建立Auth
實例之前,呼叫ini_set
來根據需要更改session.cookie_domain
指令的值,例如:
ini_set ( ' session.cookie_domain ' , ' example.com ' );
為此,必須在 PHP 配置 ( php.ini
) 中將session.auto_start
設為0
。
cookie 的path
屬性控制 cookie 對哪些目錄(和子目錄)有效,從而控制使用者會話和驗證狀態可用的位置。
在大多數情況下,您需要使 cookie 可用於所有路徑,即從根目錄開始的任何目錄和檔案。這就是屬性值/
的作用,這也是建議的預設值。如果您想限制您的cookie在哪些目錄中可用,例如要在不同的目錄中並排託管多個應用程序,則您應該只將此屬性更改為不同的值,例如/path/to/subfolder
。域名。
您可以透過以下方式之一來變更屬性(按建議順序):
在 PHP 配置 ( php.ini
) 中,找到包含session.cookie_path
指令的行並根據需要變更其值,例如:
session.cookie_path = /
儘早在應用程式中建立Auth
實例之前,呼叫ini_set
來根據需要更改session.cookie_path
指令的值,例如:
ini_set ( ' session.cookie_path ' , ' / ' );
為此,必須在 PHP 配置 ( php.ini
) 中將session.auto_start
設為0
。
使用httponly
屬性,您可以控制客戶端腳本(即 JavaScript)是否能夠存取您的 cookie。出於安全原因,最好拒絕腳本存取您的 cookie,這樣可以減少成功的 XSS 攻擊對您的應用程式可能造成的損害。
因此,您應該始終將httponly
設為1
,除非您確實需要從 JavaScript 存取 cookie 並且無法找到任何更好的解決方案的極少數情況。在這些情況下,將該屬性設為0
,但請注意後果。
您可以透過以下方式之一來變更屬性(按建議順序):
在 PHP 配置 ( php.ini
) 中,找到包含session.cookie_httponly
指令的行並根據需要更改其值,例如:
session.cookie_httponly = 1
在您的應用程式中,在建立Auth
實例之前,請儘早呼叫ini_set
來根據需要更改session.cookie_httponly
指令的值,例如:
ini_set ( ' session.cookie_httponly ' , 1 );
為此,必須在 PHP 配置 ( php.ini
) 中將session.auto_start
設為0
。
使用secure
屬性,您可以控制是否應透過任何連接(包括純 HTTP)發送 cookie,或者是否需要安全連接,即 HTTPS(帶有 SSL/TLS)。可以透過將該屬性設為0
來選擇前一種(不太安全)模式,可以透過將該屬性設為1
來選擇後一種(更安全)模式。
顯然,這完全取決於您是否能夠透過 HTTPS 專門提供所有頁面。如果可以,您應該將該屬性設為1
,並可能將其與 HTTP 重新導向至安全性協定和 HTTP 嚴格傳輸安全性 (HSTS) 結合。否則,您可能必須將該屬性設為0
。
您可以透過以下方式之一來變更屬性(按建議順序):
在 PHP 配置 ( php.ini
) 中,找到包含session.cookie_secure
指令的行並根據需要變更其值,例如:
session.cookie_secure = 1
在您的應用程式中,在建立Auth
實例之前,請儘早呼叫ini_set
以根據需要更改session.cookie_secure
指令的值,例如:
ini_set ( ' session.cookie_secure ' , 1 );
為此,必須在 PHP 配置 ( php.ini
) 中將session.auto_start
設為0
。
$ length = 24 ;
$ randomStr = Delight Auth Auth:: createRandomString ( $ length );
$ uuid = Delight Auth Auth:: createUuid ();
關於如何方便地讀寫Session資料的詳細信息,請參考預設包含的Session庫的文檔。
任何密碼或身份驗證令牌都會使用“bcrypt”函數自動進行雜湊處理,該函數基於“Blowfish”密碼,並且(仍然)被認為是當今最強大的密碼雜湊函數之一。 「bcrypt」使用了 1,024 次迭代,即「成本」係數為 10。
您可以透過查看資料庫表users
中的雜湊值來驗證此配置。如果您的設定符合上述情況,則users
表中的所有密碼雜湊值都應以前綴$2$10$
、 $2a$10$
或$2y$10$
開頭。
當將來可能引入新演算法(例如 Argon2)時,只要使用者登入或更改密碼,該程式庫就會自動「升級」您現有的密碼雜湊。
強制密碼的最小長度通常是一個好主意。除此之外,您可能需要查找潛在的密碼是否在某個黑名單中,您可以在資料庫或檔案中管理該黑名單,以防止在您的應用程式中使用字典單字或常用密碼。
為了實現最大的靈活性和易用性,該程式庫的設計使其本身不包含對密碼要求的任何進一步檢查,而是允許您圍繞對庫方法的相關呼叫進行自己的檢查。例子:
function isPasswordAllowed ( $ password ) {
if ( strlen ( $ password ) < 8 ) {
return false ;
}
$ blacklist = [ ' password1 ' , ' 123456 ' , ' qwerty ' ];
if ( in_array ( $ password , $ blacklist )) {
return false ;
}
return true ;
}
if ( isPasswordAllowed ( $ password )) {
$ auth -> register ( $ email , $ password );
}
您可以嘗試先載入此程式庫,然後先建立Auth
實例,然後再載入其他程式庫。除此之外,我們在這裡可能無能為力。
如果您想要讓其他人將您的網站包含在<frame>
、 <iframe>
、 <object>
、 <embed>
或<applet>
元素中,您必須停用預設的點擊劫持預防:
header_remove ( ' X-Frame-Options ' );
該庫拋出兩種類型的異常來指示問題:
AuthException
及其子類別。您應該始終捕獲這些異常,因為它們帶有您必須做出反應的正常錯誤響應。AuthError
及其子類別。您不應該捕獲這些異常。 歡迎所有貢獻!如果您想做出貢獻,請先建立一個問題,以便討論您的功能、問題或疑問。
該項目根據 MIT 許可證條款獲得許可。