# 角色 & 权限

此插件提供了一种通过基于 JWT 的完整身份验证过程来保护你的 API 的方法。此插件还附带了 ACL 策略,允许你管理用户组之间的权限。

要访问插件管理面板,请单击左侧菜单中的 Settings 链接,然后所有内容都将位于 USERS & PERMISSIONS PLUGIN 部分下。

# 概念

安装此插件后,它会在你的应用程序上添加一个访问层。 该插件使用 jwt token (opens new window) 效验用户。

每次发送 API 请求时,服务器便会检查 Authorization 协议头是否存在,并验证发出请求的用户是否有权访问资源。

为此,你的 JWT 包含你的用户 ID,并且我们能够匹配你的用户所在的组,并在最后知道该组是否允许访问路由。

# 管理角色权限

# 公共角色

当发送没带有 Authorization 协议头的请求时,将使用此角色。 如果您允许此角色的某些权限,则每个人都可以访问您选择的选项。 当您希望前端应用程序在不开发用户身份验证和授权的情况下访问所有内容时,选择 find / findOne 选项是常见的做法。

# 经过身份验证的角色

在创建用户时未提供任何角色,则为每个新用户提供的默认角色(Authenticated role)。在此角色中,您将能够定义用户可以访问的路由。

# 权限管理

通过单击 Role 名称,您将能够看到应用程序中可用的所有功能(并且这些功数与特定路由相关)如果勾选功能名称,则会使您正在编辑的当前角色可以访问此路由。在右侧边栏上,您将能够看到与此功能相关的 URL。

# 更新默认角色

当你使用 /api/auth/local/register 路由创建一个无角色的用户时,将会为该用户分配 authenticated 角色。

要修改默认角色,请单击 高级设置(Advanced settings) 标签并且更新 经过身份验证的用户的默认角色(Default role for authenticated users) 选项。

# 认证

# Token 用法

jwt token 可用于发出受权限限制的 API 请求。要以用户身份发出 API 请求,请将 jwt token 放入 GET 请求的 Authorization 协议头中。默认情况下,没有令牌的请求将采取 公共(public) 角色权限。在管理仪表板中修改每个用户角色的权限。身份验证失败将返回 401(未经授权)错误。

# 用法

  • token 变量是在登录或注册时从响应中 data.jwt 获取。
import axios from 'axios';

const token = 'YOUR_TOKEN_HERE';

// Request API.
axios
  .get('http://localhost:1337/posts', {
    headers: {
      Authorization: `Bearer ${token}`,
    },
  })
  .then(response => {
    // Handle success.
    console.log('Data: ', response.data);
  })
  .catch(error => {
    // Handle error.
    console.log('An error occurred:', error.response);
  });

# JWT 配置

你可以使用 插件配置文件 来配置 JWT 生成。 我们使用 jsonwebtoken (opens new window) 来生成 JWT。

可用选项:

  • jwtSecret: 随机字符串生成 JWT 签名。通常使用 JWT_SECRET 环境变量 设置。
  • jwt.expiresIn: 以秒或描述时间跨度/毫秒的字符串表示。
    例如: 60, "45m", "10h", "2 days", "7d", "2y". 数值被解释为秒计数。如果使用字符串,请确保提供正确时间单位 (minutes, hours, days, years, etc),否则默认使用毫秒单位( "120" 等于 "120ms")。

️❗️ WARNING

由于大量的安全问题,绝对不建议将JWT到期时间设置为30天以上。 Setting JWT expiry for more than 30 days is absolutely not recommended due to massive security concerns.

# 注册

在数据库中创建一个默认角色为“已注册”的新用户。

# 用法

import axios from 'axios';

// Request API.
// Add your own code here to customize or restrict how the public can register new users.
axios
  .post('http://localhost:1337/api/auth/local/register', {
    username: 'Strapi user',
    email: 'user@strapi.io',
    password: 'strapiPassword',
  })
  .then(response => {
    // Handle success.
    console.log('Well done!');
    console.log('User profile', response.data.user);
    console.log('User token', response.data.jwt);
  })
  .catch(error => {
    // Handle error.
    console.log('An error occurred:', error.response);
  });

# 登录

提交用户的标识符和密码凭据进行身份验证。身份验证成功后,返回的响应数据将包含用户的信息以及 jwt 身份验证令牌。

# 本地

  • identifier 参数可以是 emailusername
import axios from 'axios';

// Request API.
axios
  .post('http://localhost:1337/api/auth/local', {
    identifier: 'user@strapi.io',
    password: 'strapiPassword',
  })
  .then(response => {
    // Handle success.
    console.log('Well done!');
    console.log('User profile', response.data.user);
    console.log('User token', response.data.jwt);
  })
  .catch(error => {
    // Handle error.
    console.log('An error occurred:', error.response);
  });

# 提供者

借助 Grant (opens new window)Purest (opens new window), 您可以轻松地使用 OAuth 和 OAuth2 提供程序在应用程序中启用身份验证。

为了更好地理解,您可能会发现登录流程的说明如下。为了简化解释,我们使用 github 作为提供者,但它对其他提供者的工作方式相同。

# 了解登录流程

假设 strapi 的后端位于:strapi.website.com。 假设 strapi 的前端端位于:website.com。

  1. 用户进入您的前端应用程序 (https://website.com) 然后单击 connect with Github 按钮。
  2. 前端将标签页重定向到后端 URL: https://strapi.website.com/api/connect/github.
  3. 后端将标签页重定向到用户登录的 GitHub 登录页面。
  4. 完成后,Github会将标签页重定向到后端 URL:https://strapi.website.com/api/connect/github/callback?code=abcdef.
  5. 后端使用给定的 code 从 Github 获取 access_token,该 access_token 可以在一段时间内用于向 Github 发出授权请求以获取用户信息(例如用户的电子邮件)。
  6. 然后,后端将选项卡重定向到您选择的URL,参数为 access_token (例如: http://website.com/connect/github/redirect?access_token=eyfvg)
  7. 前端 使用 (http://website.com/connect/github/redirect) 调用后端的 https://strapi.website.com/api/auth/github/callback?access_token=eyfvg 返回带有 jwt 的 strapi 用户配置文件。
    (在后台,后端要求Github提供用户的个人资料,并在 Github 用户的电子邮件地址和 Strapi 用户的电子邮件地址上进行匹配)
  8. 前端现在拥有用户的 jwt,这意味着用户已连接,前端可以向后端发出经过身份验证的请求!

可在此处找到处理此流的前端应用的示例: react login example app (opens new window).

# 设置服务器网址

在设置提供程序之前,您需要在 server.js 中指定后端的绝对 URL。

example - config/server.js

💡 提示

稍后,您将此 URL 提供给您的提供商。
对于开发,一些提供商接受使用本地主机网址,但许多提供商不接受。 在这种情况下,我们建议使用 ngrok (opens new window) (ngrok http 1337) 这将使代理隧道从它创建的 URL 到您的本地主机 URL(例: url: env('', 'https://5299e8514242.ngrok.io'),)。

# 设置提供程序 - 示例

为了更好地理解,我们决定为每个提供者展示一个示例,而不是一般的解释。

在以下示例中,前端应用程序将是 react login example app (opens new window)
前端应用程序将在 http://localhost:3000 上运行。
Strapi 后端将在 http://localhost:1337 上运行。

Your configuration is done. Launch the backend and the react login example app (opens new window), go to http://localhost:3000 and try to connect to the provider your configured. It should work 🎉

# What you have to do in your frontend

Once you have configured strapi and the provider, in your frontend app you have to :

  • Create a button that links to GET STRAPI_BACKEND_URL/api/connect/${provider} (ex: https://strapi.mywebsite/api/connect/github).
  • Create a frontend route like FRONTEND_URL/connect/${provider}/redirect that have to handle the access_token param and that have to request STRAPI_BACKEND_URL/auth/${provider}/callback with the access_token param.
    The JSON request response will be { "jwt": "...", "user": {...} }.

Now you can make authenticated requests 🎉 More info here: token usage.

✋ Troubleshooting

  • Error 429: It's most likely because your login flow fell into a loop. To make new requests to the backend, you need to wait a few minutes or restart the backend.
  • Grant: missing session or misconfigured provider: It may be due to many things.
    • The redirect url can't be built: Make sure you have set the backend url in config/server.js: Setting up the server url
    • A session/cookie/cache problem: You can try again in a private tab.
    • The incorrect use of a domain with ngrok: Check your urls and make sure that you use the ngrok url instead of http://localhost:1337. Don't forget to check the backend url set in the example app at src/config.js.
  • You can't access your admin panel: It's most likely because you built it with the backend url set with a ngrok url and you stopped/restarted ngrok. You need to replace the backend url with the new ngrok url and run yarn build or npm run build again.

# 重置密码

只能用于使用电子邮件提供商注册的用户。

# 邮件验证

✏️ NOTE

在生产环境中,请确保设置了 url 配置属性。否则,验证链接将重定向到 localhost。有关配置的更多信息点 这里

注册后,如果将 启用电子邮件确认 设置为 ON,则用户将通过电子邮件收到确认链接。用户必须单击它以验证他/她的注册。

确认链接的示例: https://yourwebsite.com/api/auth/email-confirmation?confirmation=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MywiaWF0IjoxNTk0OTgxMTE3LCJleHAiOjE1OTc1NzMxMTd9.0WeB-mvuguMyr4eY8CypTZDkunR--vZYzZH6h6sChFg

如果需要,您可以通过提出以下请求来重新发送确认电子邮件。

import axios from 'axios';

// Request API.
axios
  .post(`http://localhost:1337/api/auth/send-email-confirmation`, {
    email: 'user@strapi.io', // user's email
  })
  .then(response => {
    console.log('Your user received an email');
  })
  .catch(error => {
    console.error('An error occurred:', error.response);
  });

# Strapi 上下文中的用户对象

user 对象可用于成功通过身份验证的请求。

# 用法

  • 经过身份验证的 user 对象是 ctx.state 的属性。
create: async ctx => {
  const { id } = ctx.state.user;

  const depositObj = {
    ...ctx.request.body,
    depositor: id,
  };

  const data = await strapi.services.deposit.add(depositObj);

  // Send 201 `created`
  ctx.created(data);
};

# 添加新的提供程序(添加到项目中)

✋ CAUTION

本文档不是 Strapi v4 的最新文档,是一项正在进行的工作。同时欢迎贡献 (opens new window)

Grant (opens new window) supplies configuration for a number of commonly used OAuth providers. Custom (opens new window) providers are also supported.
You can view and try out the 200+ supported providers here: OAuth Playground (opens new window).

# Prepare your files

To add a new provider on Strapi, you will need to perform changes onto the following files:

extensions/users-permissions/services/Providers.js
extensions/users-permissions/config/functions/bootstrap.js

If these files don't exist you will need to copy from your node_modules or the Strapi mono-repo. You can see plugin extensions for more information on how it works.

We will go step by step.

# Configure your Provider Request

Configure the new provider in the Provider.js file at the getProfile function.

The getProfile takes three params:

  • provider: The name of the used provider as a string.
  • query: The query is the result of the provider callback.
  • callback: The callback function who will continue the internal Strapi login logic.

Here is an example that uses the discord provider.

# Configure your oauth generic information

case 'discord': {
  const discord = new Purest({
    provider: 'discord',
    config: {
      'discord': {
        'https://discordapp.com/api/': {
          '__domain': {
            'auth': {
              'auth': {'bearer': '[0]'}
            }
          },
          '{endpoint}': {
            '__path': {
              'alias': '__default'
            }
          }
        }
      }
    }
  });
}

This code creates a Purest object that gives us a generic way to interact with the provider's REST API.

For more specs on using the Purest module, please refer to the Official Purest Documentation (opens new window)

You may also want to take a look onto the numerous already made configurations here (opens new window).

# Retrieve your user's information:

For our discord provider it will look like:

  discord.query().get('users/@me').auth(access_token).request((err, res, body) => {
    if (err) {
      callback(err);
    } else {
      // Combine username and discriminator because discord username is not unique
      const username = `${body.username}#${body.discriminator}`;
      callback(null, {
        username,
        email: body.email
      });
    }
  });
  break;
}

Here is the next part of our switch. Now that we have properly configured our provider, we want to use it to retrieve user information.

Here you see the real power of purest, you can simply make a get request on the desired URL, using the access_token from the query parameter to authenticate.

That way, you should be able to retrieve the user info you need.

Now, you can simply call the callback function with the username and email of your user. That way, Strapi will be able to retrieve your user from the database and log you in.

# Configure the new provider model onto database

Now, we need to configure our 'model' for our new provider. That way, our settings can be stored in the database, and managed from the admin panel.

Open the file packages/strapi-plugin-users-permissions/config/functions/bootstrap.js

Add the fields your provider needs into the grantConfig object. For our discord provider it will look like:

discord: {
  enabled: false,  // make this provider disabled by default
  icon: 'comments', // The icon to use on the UI
  key: '',  // our provider app id (leave it blank, you will fill it with the Content Manager)
  secret: '', // our provider secret key (leave it blank, you will fill it with the Content Manager)
  callback: '/auth/discord/callback', // the callback endpoint of our provider
  scope: [  // the scope that we need from our user to retrieve information
    'identify',
    'email'
  ]
},

# Templating emails

By default, this plugin comes with only two templates (reset password and email address confirmation) at the moment. More templates will come later. The templates use Lodash's template() method to populate the variables.

You can update these templates under Plugins > Roles & Permissions > Email Templates tab in the admin panel.

# Reset Password

  • USER (object)
    • username
    • email
  • TOKEN corresponds to the token generated to be able to reset the password.
  • URL is the link where the user will be redirected after clicking on it in the email.

# Email address confirmation

  • USER (object)
    • username
    • email
  • CODE corresponds to the CODE generated to be able confirm the user email.
  • URL is the Strapi backend URL that confirms the code (by default /auth/email-confirmation).

# Security configuration

JWT tokens can be verified and trusted because the information is digitally signed. To sign a token a secret is required. By default Strapi generates one that is stored in ./extensions/users-permissions/config/jwt.js. This is useful during development but for security reasons it is recommended to set a custom token via an environment variable JWT_SECRET when deploying to production.

By default you can set a JWT_SECRET environment variable and it will be used as secret. If you want to use another variable you can update the configuration file.

💡 TIP

You can learn more on configuration in the documentation here.