手册:SessionManager和AuthManager

This page is a translated version of the page Manual:SessionManager and AuthManager and the translation is 12% complete.

SessionManagerAuthManagerMediaWiki 1.27 中引入的两个与身份验证相关的框架,旨在取代认证插件 的“只能有一个!”的特性(之前的认证系统),以及在没有使用各种钩子绕过 MediaWiki 对基于密码的认证的专注性时,无法进行联合登录(例如,“使用您的 Google 帐户登录”),以及在没有再次使用各种钩子绕过 MediaWiki 对基于 PHP 的基于 cookie 的会话依赖性时,无法使用除 cookie 以外的任何东西进行会话管理。

这实现了什么?

SessionManager专注于将传入请求与用户会话连接起来,即该请求是否来自已登录用户(或未登录),以及任何应跨请求保持持久性的数据,就像 PHP 的$_SESSION一样。 通常这意味着使用cookies来保存“会话密钥”、用户的ID和令牌,但也可以基于OAuth标头中的凭据或TLS客户端证书来实现。

AuthManager专注于通过提供凭据登录到Special:UserLogin或类似页面,并创建带有新凭据的帐户。 这包括常见的用户名和密码表单,还包括通过 Google、Facebook 或其他第三方认证服务进行登录的机制;多因素身份验证;以及其他登录时检查,如密码重置、CAPTCHA(人机验证)等。

如何开始使用?

面向系统管理员

可用的会话保存方式是通过$wgSessionProviders 进行配置的。 有关详细信息,请参阅该全局变量以及各个会话保存方式的文档。

可用的认证方式是通过$wgAuthManagerConfig 进行配置的。 有关详细信息,请参阅该全局变量以及各个认证方式的文档。

为了处理传统上需要用户输入当前密码以进行确认的操作,AuthManager要求用户在当前会话中最近已经进行了活动登录。 要配置“最近”具体指的是多长时间,可以使用 $wgReauthenticateTime 来为每个操作设置时间限制。 To determine what happens when the user is using something like OAuth where each request is individually authenticated and "log in" isn't possible, use $wgAllowSecuritySensitiveOperationIfCannotReauthenticate .

As a consumer

Sessions

To access the current session from your code, the most straightforward mechanism is to use the getSession() method on your WebRequest object. The resulting MediaWiki\Session\Session object has methods to access the data that, in the past, you'd have accessed using $webRequest->getSessionData() and ->setSessionData() or by directly accessing $_SESSION.

To test if a session is currently active where you would have previously used code like session_id() !== '', fetch the Session object and use its ->isPersistent() method. To ensure that the session is persistent, fetch it and use its ->persist() method instead of using wfSetupSession().

If you need to access session data in a context other than a client request, e.g. if you saved the session ID to run a background job and need to update status in the session data, use MediaWiki\Session\SessionManager::singleton()->getSessionById().

Authentication

If you are performing a security-sensitive action that would traditionally request the user's password for verification, use MediaWiki\Auth\AuthManager::singleton()->securitySensitiveOperationStatus() to check whether the user has authenticated recently enough, and if necessary direct them through Special:UserLogin to re-authenticate. SpecialPage can handle this for you if you return non-false from SpecialPage::getLoginSecurityLevel().

Other than that, most "consumer" code won't need to interact with AuthManager directly, since you really shouldn't be implementing your own authentication flow. To determine if a user is logged in, use the usual methods such as the getUser() on an IContextSource. To see that a user is authenticated, have them log in via the usual method.

But if you really insist: Authentication in AuthManager is a multi-step process that goes something like this:

  1. Call $authManager->canAuthenticateNow() to determine if authentication is even possible.
  2. Call $authManager->getAuthenticationRequests( AuthManager::ACTION_LOGIN ) to determine which AuthenticationRequest subclasses are needed, convert them to form fields, and present them to the user.
  3. Once the user submits the form, use AuthenticationRequest::loadRequestsFromSubmission() to populate the requests with data and pass them to $authManager->beginAuthentication().
  4. Take action depending on the status of the AuthenticationResponse:
    • For status UI, convert the needed AuthenticationRequests to form fields, present to the user, and on submission use AuthenticationRequest::loadRequestsFromSubmission() to populate the new requests with data and pass them to $authManager->continueAuthentication() and handle the new response.
    • For status REDIRECT, redirect the user to the specified URL. Eventually the user should be redirected back, at which point the needed AuthenticationRequests should be populated with data and passed to $authManager->continueAuthentication().
    • For status PASS, authentication has succeeded.
    • For status RESTART, authentication with a third-party service has succeeded but those credentials aren't associated with a local account. Treat this as either UI or FAIL.
    • For status FAIL, authentication has failed. The response might contain an additional AuthenticationRequest that may be passed into $authManager->beginAuthentication() or $authManager->beginAccountCreation() to preserve state.

The processes for account creation and linking of third-party credentials are similar.

As a provider

If you are writing code that is going to provide new functionality for SessionManager or AuthManager, first you need to decide what kind of thing you are providing:

  • If you are implementing a new way for taking a WebRequest and determining the authenticated user and session data from it, you want to implement MediaWiki\Session\SessionProvider.
    • If you are doing something that seems like logging in, a SessionProvider is not what you want. You probably want a PrimaryAuthenticationProvider instead.
  • If you want to generically add additional checks to the process of getting a Session from a WebRequest, you will likely want to look at the SessionMetadata and SessionCheckInfo hooks. The first allows adding additional metadata to be saved with the session, while the second allows you to look at the metadata when a Session is being loaded and if necessary veto its use.
  • If you are implementing a new way to log in, you want to implement MediaWiki\Auth\PrimaryAuthenticationProvider.
    • If your thing isn't so much a way to log in as a new way to just "be" logged in such as OAuth or TLS client certificates, you don't want this. You probably want a SessionProvider instead.
  • If you are implementing some new check that someone has to pass before being able to log in, such as a CAPTCHA, you want to implement MediaWiki\Auth\PreAuthenticationProvider.
  • If you are implementing something that should happen after logging in, including testing of a second factor, you want to implement MediaWiki\Auth\SecondaryAuthenticationProvider.

In all cases, providers are constructed using ObjectFactory from $wgSessionProviders or $wgAuthManagerAutoConfig and then have dependencies injected by setter methods.

SessionProvider

Class documentation: MediaWiki\Session\SessionProvider

There are broadly three types of session providers, based on two abilities:

  • The ability to save any randomly-generated session ID, versus the ID being based on properties of the request that cannot be changed.
  • The ability to be associated with any arbitrary user who logs in, versus having a user that is intrinsically associated with the request.
    • This latter property is sometimes referred to as making the session "mutable" versus "immutable".

The resulting three types are:

  • Providers that can save any randomly-generated session ID, and can also be associated with any arbitrary user. A prime example of these are the traditional cookie-based sessions.
    • It's not necessary that the provider actually does anything to save the user's identity; as long as the session ID is provided, the user can be determined from the saved metadata.
  • Providers that have a user and session ID intrinsically associated with the request. Good examples here are OAuth and TLS client certificates: the OAuth headers or TLS client certificate are associated with a particular user and you can't just arbitrarily change them to be associated with a different user.
  • Providers that have a user intrinsically associated with the request, but can save any randomly-generated session ID. An example might be HTTP Digest authentication using the "opaque" field to hold the session ID.
    • You can often take a method with an intrinsic user and no ability to save randomly-generated session IDs and use a cookie to give it that ability; the MediaWiki\Session\ImmutableSessionProviderWithCookie base class implements the cookie portion of such a provider.

It's also possible to have a provider with the ability to be associated with any arbitrary user but lacking the ability to save a randomly-generated session ID. This doesn't seem to make any sense, though.

A session provider "provides" sessions by returning a MediaWiki\Session\SessionInfo object from either provideSessionInfo() (for the session associated with a WebRequest) or newSessionInfo() (for a new session not yet associated with any WebRequest). The properties of the SessionInfo are:

  • The priority of the SessionInfo: if by some chance multiple providers find session data in the same request, the one with the highest priority is used. If there is a tie, SessionManager will throw an error. Generally a provider will either always use SessionInfo::MAX_PRIORITY (i.e. "you must use this SessionInfo") or will take the desired priority as a parameter to its constructor.
  • The session ID. It's ok for a provider to provide a SessionInfo without providing an ID if it does identify a user.
  • Whether the request identifies a user, and if that user is authenticated or just named. Using cookie-based sessions as an example,
    • "No user" results when the UserID or UserName cookie isn't present in the request. The user will be determined from the saved session metadata.
    • "Unauthenticated user" results when the UserID or UserName cookie is present, but the "Token" cookie is missing. Any user's ID or name is publicly available, so without the token we can't know that this isn't a spoofing attempt.
      • This is still worthwhile, since we can check that the saved session metadata identifies the same user to prevent someone from using a session ID that has expired and been reassigned to a different user.
    • "Authenticated user" results when the UserID or UserName cookie is present and the "Token" cookie is also present, and hash_equals( $user->getToken(), $cookieToken ) returns true. Since the token is supposed to be secret, we can assume that a correct token means this is the known user.
      • Tokens that come in from the WebRequest must always be checked using hash_equals to avoid timing attacks.
    • There's no case where the UserID or UserName cookie and the "Token" cookie are present, but hash_equals( $user->getToken(), $cookieToken ) returns false. If this happens, the provider must not provide any SessionInfo.
  • Whether various bits were actually found in the WebRequest: the session ID, the user's token, and the forceHTTPS flag.
  • Any additional metadata the provider wants to associate with the session. This is often useful if the provider does authorisation (e.g. OAuth grants) in addition to authentication.

The session provider is also responsible for "persisting" the session to a WebResponse, which is done on a call to persistSession(). If the provider has the ability to save randomly-generated session IDs, it does so here. If it has the ability to independently save the user identity, it should always save the user's ID or name but must only save the user's token if the session's ->shouldRememberUser() method returns true.

For proper caching behavior, the session provider needs to use getVaryHeaders() and getVaryCookies() to indicate which request headers and cookies it uses to determine the SessionInfo. It also needs to provide messages to identify its sessions in error messages, and to provide a hint as to why sessions aren't being persisted when they should be (e.g. that the browser is set to reject cookies).

PreAuthenticationProvider

Class documentation: MediaWiki\Auth\PreAuthenticationProvider, MediaWiki\Auth\AbstractPreAuthenticationProvider

A "pre"-authentication provider can provide fields for the login or account creation form, and perform checks on a login or account creation attempt before any actual authentication happens. For example, it might throttle login attempts by IP address or present a CAPTCHA that must be solved in order to create the account.

PrimaryAuthenticationProvider

Class documentation: MediaWiki\Auth\PrimaryAuthenticationProvider, MediaWiki\Auth\AbstractPrimaryAuthenticationProvider

A "primary" authentication provider is responsible for actual user authentication:

  • Taking input from the login form, determining which user is trying to log in, and actually authenticating that user.
  • Ensuring that the user being created will be able to log in later.
  • Testing whether a user name already exists in the authentication backend storage.
  • Changing the authentication data associated with an existing user, or adding and removing associations with third-party authentication provider accounts.

AuthManager may be configured to use multiple primary authentication providers. On the initial form submission each primary provider in turn is tried, and if it returns an ABSTAIN result the next in line is tried until either one returns a non-ABSTAIN response or it runs out of providers (in which case it generates a FAIL).

The authentication process for a provider is also multi-step: non-ABSTAIN responses include the usual PASS and FAIL, but also UI to present an additional input form for the user to fill out and REDIRECT to send the browser to some third party which should eventually redirect back to continue the authentication process.

SecondaryAuthenticationProvider

Class documentation: MediaWiki\Auth\SecondaryAuthenticationProvider, MediaWiki\Auth\AbstractSecondaryAuthenticationProvider

A "secondary" authentication provider is called after the user has been authenticated by a "primary". For example, it might request a second factor for multi-factor authentication, prompt the user to change their password, or check that the user isn't blocked. Secondary providers are also called on account creation so they can do things such as prompt to set up multi-factor authentication.

AuthManager may be configured with multiple secondary authentication providers, in which case all are run and must PASS or ABSTAIN for the authentication process to succeed. Like primary providers, they may respond with UI and REDIRECT to interact with the user.

Inter-authentication-provider communication

AuthManager provides support for communication between provider modules during the authentication process.

For example, password-based primary authentication providers that have the concept of password expiry can call $this->getManager()->setAuthenticationSessionData( 'reset-pass', $data ), which will cause the secondary authentication provider MediaWiki\Auth\ResetPasswordSecondaryAuthenticationProvider to prompt the user to change their password. It's up to each provider to document which authentication data "keys" it uses and what data it needs to be provided to do its job.

See also

Code stewardship