认证
Starlette 为处理身份验证和权限提供了一个简单但强大的接口。一旦您使用适当的身份验证后端安装了 AuthenticationMiddleware
, request.user
和 request.auth
接口将在您的端点中可用。
from starlette.applications import Starlette
from starlette.authentication import (
AuthCredentials, AuthenticationBackend, AuthenticationError, SimpleUser
)
from starlette.middleware import Middleware
from starlette.middleware.authentication import AuthenticationMiddleware
from starlette.responses import PlainTextResponse
from starlette.routing import Route
import base64
import binascii
class BasicAuthBackend(AuthenticationBackend):
async def authenticate(self, conn):
if "Authorization" not in conn.headers:
return
auth = conn.headers["Authorization"]
try:
scheme, credentials = auth.split()
if scheme.lower() != 'basic':
return
decoded = base64.b64decode(credentials).decode("ascii")
except (ValueError, UnicodeDecodeError, binascii.Error) as exc:
raise AuthenticationError('Invalid basic auth credentials')
username, _, password = decoded.partition(":")
# TODO: You'd want to verify the username and password here.
return AuthCredentials(["authenticated"]), SimpleUser(username)
async def homepage(request):
if request.user.is_authenticated:
return PlainTextResponse('Hello, ' + request.user.display_name)
return PlainTextResponse('Hello, you')
routes = [
Route("/", endpoint=homepage)
]
middleware = [
Middleware(AuthenticationMiddleware, backend=BasicAuthBackend())
]
app = Starlette(routes=routes, middleware=middleware)
用户
一旦 AuthenticationMiddleware
安装完成, request.user
接口将可用于端点或其他中间件。
此接口应继承 BaseUser
的子类,该子类提供两个属性,以及您的用户模型所包含的任何其他信息。
.is_authenticated
.display_name
Starlette 提供了两个内置的用户实现: UnauthenticatedUser()
,和 SimpleUser(username)
。
身份验证凭据
重要的是,认证凭据应被视为与用户分开的概念。认证方案应能够独立于用户身份限制或授予特定权限。
AuthCredentials
类提供了 request.auth
所暴露的基本接口:
.scopes
权限
权限被实现为端点装饰器,用于强制传入的请求包含所需的身份验证范围。
from starlette.authentication import requires
@requires('authenticated')
async def dashboard(request):
...
您可以包含一个或多个所需的范围:
from starlette.authentication import requires
@requires(['authenticated', 'admin'])
async def dashboard(request):
...
默认情况下,当未授予权限时,将返回 403 响应。在某些情况下,您可能想要对此进行自定义,例如向未经验证的用户隐藏有关 URL 布局的信息。
from starlette.authentication import requires
@requires(['authenticated', 'admin'], status_code=404)
async def dashboard(request):
...
!!! 注意 WebSockets 不支持 status_code
参数。对于这些情况,将始终使用 403(禁止)状态码。
或者您可能想要将未经验证的用户重定向到其他页面。
from starlette.authentication import requires
async def homepage(request):
...
@requires('authenticated', redirect='homepage')
async def dashboard(request):
...
当重定向用户时,您将他们重定向到的页面会在 next
查询参数中包含他们最初请求的 URL:
from starlette.authentication import requires
from starlette.responses import RedirectResponse
@requires('authenticated', redirect='login')
async def admin(request):
...
async def login(request):
if request.method == "POST":
# Now that the user is authenticated,
# we can send them to their original request destination
if request.user.is_authenticated:
next_url = request.query_params.get("next")
if next_url:
return RedirectResponse(next_url)
return RedirectResponse("/")
对于基于类的端点,您应该将装饰器包装在类的方法周围。
from starlette.authentication import requires
from starlette.endpoints import HTTPEndpoint
class Dashboard(HTTPEndpoint):
@requires("authenticated")
async def get(self, request):
...
自定义身份验证错误响应
您可以自定义当身份验证后端引发 AuthenticationError
时发送的错误响应:
from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.authentication import AuthenticationMiddleware
from starlette.requests import Request
from starlette.responses import JSONResponse
def on_auth_error(request: Request, exc: Exception):
return JSONResponse({"error": str(exc)}, status_code=401)
app = Starlette(
middleware=[
Middleware(AuthenticationMiddleware, backend=BasicAuthBackend(), on_error=on_auth_error),
],
)