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许可证的条款获得许可的。