Skip to content

Session 守卫

session 守卫使用 @adonisjs/session 包在 HTTP 请求期间登录和认证用户。

Session 和 cookies 在互联网上已经存在很长时间,对大多数应用程序都很有效。因此,我们建议将 session 守卫用于服务器渲染的应用程序或同一顶级域上的 SPA Web 客户端。

配置守卫

认证守卫在 config/auth.ts 文件中定义。你可以在此文件的 guards 对象下配置多个守卫。

ts
// title: config/auth.ts
import { defineConfig } from '@adonisjs/auth'
// highlight-start
import { sessionGuard, sessionUserProvider } from '@adonisjs/auth/session'
// highlight-end

const authConfig = defineConfig({
  default: 'web',
  guards: {
    // highlight-start
    web: sessionGuard({
      useRememberMeTokens: false,
      provider: sessionUserProvider({
        model: () => import('#models/user'),
      }),
    })
    // highlight-end
  },
})

export default authConfig

sessionGuard 方法创建 SessionGuard 类的实例。它接受一个可用于在认证期间查找用户的用户提供者,以及一个可选的配置对象来配置记住我令牌行为。

sessionUserProvider 方法创建 SessionLucidUserProvider 类的实例。它接受用于认证的模型引用。

执行登录

你可以使用守卫的 login 方法登录用户。该方法接受 User 模型的实例并为其创建登录会话。

在以下示例中:

  • 我们使用 AuthFinder mixin 中的 verifyCredentials 通过电子邮件和密码查找用户。

  • auth.use('web') 返回在 config/auth.ts 文件中配置的 SessionGuard 实例(web 是你配置中定义的守卫名称)。

  • 接下来,我们调用 auth.use('web').login(user) 方法为用户创建登录会话。

  • 最后,我们将用户重定向到 /dashboard 端点。可以随意自定义重定向端点。

ts
import User from '#models/user'
import { HttpContext } from '@adonisjs/core/http'

export default class SessionController {
  async store({ request, auth, response }: HttpContext) {
    // highlight-start
    /**
     * 步骤 1:从请求体获取凭据
     */
    const { email, password } = request.only(['email', 'password'])

    /**
     * 步骤 2:验证凭据
     */
    const user = await User.verifyCredentials(email, password)

    /**
     * 步骤 3:登录用户
     */
    await auth.use('web').login(user)

    /**
     * 步骤 4:将他们发送到受保护的路由
     */
    response.redirect('/dashboard')
    // highlight-end
  }
}

保护路由

你可以使用 auth 中间件保护路由免受未认证用户的访问。该中间件在 start/kernel.ts 文件的命名中间件集合中注册。

ts
import router from '@adonisjs/core/services/router'

export const middleware = router.named({
  auth: () => import('#middleware/auth_middleware')
})

auth 中间件应用于你想保护免受未认证用户访问的路由。

ts
// highlight-start
import { middleware } from '#start/kernel'
// highlight-end
import router from '@adonisjs/core/services/router'

router
 .get('dashboard', () => {})
 // highlight-start
 .use(middleware.auth())
 // highlight-end

默认情况下,auth 中间件将根据 default 守卫(如配置文件中定义的)认证用户。但是,你可以在分配 auth 中间件时指定守卫数组。

在以下示例中,auth 中间件将尝试使用 webapi 守卫认证请求。

ts
import { middleware } from '#start/kernel'
import router from '@adonisjs/core/services/router'

router
 .get('dashboard', () => {})
 // highlight-start
 .use(
   middleware.auth({
     guards: ['web', 'api']
   })
 )
 // highlight-end

处理认证异常

如果用户未认证,auth 中间件会抛出 E_UNAUTHORIZED_ACCESS 异常。该异常使用以下内容协商规则自动处理。

  • 带有 Accept=application/json 头的请求将收到带有 message 属性的错误数组。

  • 带有 Accept=application/vnd.api+json 头的请求将收到按 JSON API 规范格式化的错误数组。

  • 对于服务器渲染的应用程序,用户将被重定向到 /login 页面。你可以在 auth 中间件类中配置重定向端点。

获取已登录用户的访问权限

你可以使用 auth.user 属性访问已登录用户实例。该值仅在使用 authsilent_auth 中间件时可用,或者如果你手动调用 auth.authenticateauth.check 方法。

ts
// title: 使用 auth 中间件
import { middleware } from '#start/kernel'
import router from '@adonisjs/core/services/router'

router
  .get('dashboard', async ({ auth }) => {
    // highlight-start
    await auth.user!.getAllMetrics()
    // highlight-end
  })
  .use(middleware.auth())
ts
// title: 手动调用 authenticate 方法
import { middleware } from '#start/kernel'
import router from '@adonisjs/core/services/router'

router
  .get('dashboard', async ({ auth }) => {
    // highlight-start
    /**
     * 首先,认证用户
     */
    await auth.authenticate()

    /**
     * 然后访问用户对象
     */ 
    await auth.user!.getAllMetrics()
    // highlight-end
  })

Silent auth 中间件

silent_auth 中间件类似于 auth 中间件,但在用户未认证时不会抛出异常。相反,请求仍然照常继续。

当你想始终认证用户以执行某些操作但不想在用户未认证时阻止请求时,此中间件很有用。

如果你计划使用此中间件,则必须在路由中间件列表中注册它。

ts
// title: start/kernel.ts
import router from '@adonisjs/core/services/router'

router.use([
  // ...
  () => import('#middleware/silent_auth_middleware')
])

检查请求是否已认证

你可以使用 auth.isAuthenticated 标志检查请求是否已认证。对于已认证的请求,auth.user 的值将始终被定义。

ts
import { middleware } from '#start/kernel'
import router from '@adonisjs/core/services/router'

router
  .get('dashboard', async ({ auth }) => {
    // highlight-start
    if (auth.isAuthenticated) {
      await auth.user!.getAllMetrics()
    }
    // highlight-end
  })
  .use(middleware.auth())

获取已认证用户或失败

如果你不喜欢在 auth.user 属性上使用非空断言运算符,你可以使用 auth.getUserOrFail 方法。此方法将返回用户对象或抛出 E_UNAUTHORIZED_ACCESS 异常。

ts
import { middleware } from '#start/kernel'
import router from '@adonisjs/core/services/router'

router
  .get('dashboard', async ({ auth }) => {
    // highlight-start
    const user = auth.getUserOrFail()
    await user.getAllMetrics()
    // highlight-end
  })
  .use(middleware.auth())

在 Edge 模板中访问用户

InitializeAuthMiddleware 也与 Edge 模板共享 ctx.auth 属性。因此,你可以通过 auth.user 属性访问当前登录的用户。

edge
@if(auth.isAuthenticated)
  <p> 你好 {{ auth.user.email }} </p>
@end

如果你想在非保护路由上获取登录用户信息,你可以使用 auth.check 方法检查用户是否已登录,然后访问 auth.user 属性。一个很好的用例是在公共页面的网站头部显示登录用户信息。

edge
{{--
  这是一个公共页面;因此,它不受 auth 中间件保护。
  但是,我们仍然想在网站头部显示登录用户信息。

  为此,我们使用 `auth.check` 方法静默检查用户是否已登录,
  然后在头部显示他们的电子邮件。

  你明白了!
--}}

@eval(await auth.check())

<header>
  @if(auth.isAuthenticated)
    <p> 你好 {{ auth.user.email }} </p>
  @end
</header>

执行注销

你可以使用 guard.logout 方法注销用户。在注销期间,用户状态将从会话存储中删除。当前活动的记住我令牌也将被删除(如果使用记住我令牌)。

ts
import { middleware } from '#start/kernel'
import router from '@adonisjs/core/services/router'

router
  .post('logout', async ({ auth, response }) => {
    await auth.use('web').logout()
    return response.redirect('/login')
  })
  .use(middleware.auth())

使用记住我功能

记住我功能在用户会话过期后自动登录用户。这是通过生成加密安全令牌并将其作为 cookie 保存在用户浏览器中来完成的。

用户会话过期后,AdonisJS 将使用记住我 cookie,验证令牌的有效性,并自动为用户重新创建登录会话。

创建记住我令牌表

记住我令牌保存在数据库中,因此,你必须创建一个新迁移来创建 remember_me_tokens 表。

sh
node ace make:migration remember_me_tokens
ts
import { BaseSchema } from '@adonisjs/lucid/schema'

export default class extends BaseSchema {
  protected tableName = 'remember_me_tokens'

  async up() {
    this.schema.createTable(this.tableName, (table) => {
      table.increments()
      table
        .integer('tokenable_id')
        .notNullable()
        .unsigned()
        .references('id')
        .inTable('users')
        .onDelete('CASCADE')

      table.string('hash').notNullable().unique()
      table.timestamp('created_at').notNullable()
      table.timestamp('updated_at').notNullable()
      table.timestamp('expires_at').notNullable()
    })
  }

  async down() {
    this.schema.dropTable(this.tableName)
  }
}

配置令牌提供者

要读写令牌,你必须将 DbRememberMeTokensProvider 分配给 User 模型。

ts
import { BaseModel } from '@adonisjs/lucid/orm'
// highlight-start
import { DbRememberMeTokensProvider } from '@adonisjs/auth/session'
// highlight-end

export default class User extends BaseModel {
  // ...模型的其余属性

  // highlight-start
  static rememberMeTokens = DbRememberMeTokensProvider.forModel(User)
  // highlight-end
}

在配置中启用记住我令牌

最后,让我们在 config/auth.ts 文件中的 session 守卫配置上启用 useRememberTokens 标志。

ts
import { defineConfig } from '@adonisjs/auth'
import { sessionGuard, sessionUserProvider } from '@adonisjs/auth/session'

const authConfig = defineConfig({
  default: 'web',
  guards: {
    web: sessionGuard({
      // highlight-start
      useRememberMeTokens: true,
      rememberMeTokensAge: '2 years',
      // highlight-end
      provider: sessionUserProvider({
        model: () => import('#models/user'),
      }),
    })
  },
})

export default authConfig

在登录时记住用户

设置完成后,你可以使用 guard.login 方法按如下方式生成记住我令牌和 cookie。

ts
import User from '#models/user'
import { HttpContext } from '@adonisjs/core/http'

export default class SessionController {
  async store({ request, auth, response }: HttpContext) {
    const { email, password } = request.only(['email', 'password'])
    const user = await User.verifyCredentials(email, password)

    await auth.use('web').login(
      user,
      // highlight-start
      /**
       * 当 "remember_me" 输入存在时生成令牌
       */
      !!request.input('remember_me')
      // highlight-end
    )
    
    response.redirect('/dashboard')
  }
}

使用 guest 中间件

auth 包附带一个 guest 中间件,你可以使用它来重定向已登录用户,阻止他们访问 /login 页面。这应该可以避免在单个设备上为单个用户创建多个会话。

ts
import router from '@adonisjs/core/services/router'
import { middleware } from '#start/kernel'

router
  .get('/login', () => {})
  .use(middleware.guest())

默认情况下,guest 中间件将使用 default 守卫(如配置文件中定义的)检查用户登录状态。但是,你可以在分配 guest 中间件时指定守卫数组。

ts
router
  .get('/login', () => {})
  .use(middleware.guest({
    guards: ['web', 'admin_web']
  }))

最后,你可以在 ./app/middleware/guest_middleware.ts 文件中为已登录用户配置重定向路由。

事件

请查看事件参考指南以查看 Auth 包发出的可用事件列表。