IBM Cloud Docs
存储和访问属性

存储和访问属性

通过 IBM Cloud® App ID,可以将有关应用程序的各个用户的信息汇集成概要文件。 通过用户与应用程序进行交互的方式或者您代表用户进行添加的方式,可了解有关用户的信息并存储在概要文件中。 通过存储这些信息,您可以对其进行访问,以帮助为用户创建应用程序的个性化体验。

了解概要文件

用户配置文件是关于特定用户的所有已知信息--编译成一个 JSON 对象并由 App ID 存储。 在概要文件中可以获取和存储两种类型的信息(或属性):predefinedcustom。 预定义属性特定于用户身份,并在用户登录到应用程序时由身份提供者返回,可能包含用户的姓名或年龄等信息。 定制属性用于存储有关用户的其他信息。 定制属性可以由您进行设置,也可以在用户与应用程序交互期间进行了解。 定制属性可能包含分配的角色、食品偏好或乘坐飞机时首选靠近过道的座位。

caption-side=bottom"
App ID 用户配置文件 用户配置文件信息流

您可以为每个用户最多存储 100 KB 信息。

如何获取用户概要文件信息?

您可以通过几种不同的方式来访问用户信息,还有几种不同的原因导致您想要访问用户信息。 根据用例,选择调用的端点可能会有所不同。

如果需要使用 API,请查看下图和相应的信息以了解如何拉取信息。

caption-side=bottom"
App ID 用户配置文件端点选项 可用于访问用户信息的端点选项

/oauth/v4/<tenantID>/token
成功认证后,您将收到访问令牌和身份令牌,其中包含最常见的用户信息,例如姓名、照片或电子邮件。 如果要添加其他信息,可以使用定制声明映射来配置 App ID,使其将相关信息注入到令牌,然后将令牌返回给您。
/oauth/v4/<tenantID>/userinfo
如果需要深入查看身份供应商返回的用户配置文件信息,可以调用 /userinfo 端点。 建议仅在信息无法映射到令牌时使用该端点,因为这需要额外的网络调用。
/api/v1/attributes
如果应用程序需要读取和更新当前已登录用户的定制概要文件属性,那么可以使用 /attributes 端点。 例如,用户希望更新食品偏好。
/management/v4/<tenantID>/users
如果构建的是可能适用于多个用户的管理界面或流程,那么可以使用 App ID 管理 API。 具体来说,您可以使用 /users 端点。

使用用户信息的最简单方法是利用 GUI 或 SDK。 有了这些选项,所有 API 调用都会在幕后为您完成。

在运行时访问属性

在用户认证成功后,应用程序会从 App ID 收到访问令牌和身份令牌。 该服务会自动将属性子集注入到访问令牌和身份令牌中。 如果令牌中没有这些信息,那么可以使用以下任一端点来查找这些信息。

访问 /userinfo 端点

要查看有关已配置身份提供者提供的用户的信息,可以访问预定义属性。

  1. 请确保您具有范围为 openid 的有效访问令牌。 您可以使用 /introspect 端点来验证令牌是否有效。

  2. /userinfo 端点发出请求。 如果未显式地将新令牌传递给 SDK,那么 App ID 使用上次收到的令牌来检索并验证响应。 传递身份令牌是可选的,但传递用于验证响应。

    GET https://<region>.appid.cloud.ibm.com/oauth/v4/<tenantID>/userinfo
    Authorization: 'Bearer <accessToken>'
    
    // iOS Swift example
    
    AppID.sharedInstance.userProfileManager.getUserInfo(accessToken: String, identityToken: String?) { (error: Error?, userInfo: [String: Any]?) in guard
       let userInfo = userInfo, err == nil {
          return // an error has occurred
       }
       // retrieved user info successfully
    }
    
    AppID appId = AppID.getInstance();
    
    appId.getUserProfileManager().getUserInfo(accessToken, identityToken, new UserProfileResponseListener() {
       @Override
       public void onSuccess(JSONObject userInfo) {
       // retrieved attribute "name" successfully
       }
    
       @Override
       public void onFailure(UserInfoException e) {
       // exception occurred
       }
    });
    
    let userProfileManager = UserProfileManager(options: options)
    
    let accessToken = req.session[WebAppStrategy.AUTH_CONTEXT].accessToken;
    let identityToken = req.session[WebAppStrategy.AUTH_CONTEXT].identityToken;
    
    // Retrieve user info and validate against the given identity token
    userProfileManager.getUserInfo(accessToken, identityToken).then(function (profile) {
       // retrieved user info successfully
    });
    
    // Retrieve user info without validation
    userProfileManager.getUserInfo(accessToken).then(function (profile) {
    // retrieved user info successfully
    });
    
    // Server-side Swift example
    
    let userProfileManager = UserProfileManager(options: options)
    let accessToken = "<accessToken>"
    let identityToken = "<identityToken>"
    
    // If identity token is provided (recommended approach), response is validated against the identity token
    
    userProfileManager.getUserInfo(accessToken: accessToken, identityToken: identityToken) { (err, userInfo) in guard
       let userInfo = userInfo, err == nil {
       return
       }
    }
    
    // Retrieve the UserInfo without any validation
    
    userProfileManager.getUserInfo(accessToken: accessToken) { (err, userInfo) in guard
       let userInfo = userInfo, err == nil {
       return
       }
    }
    

    示例输出:

    "sub": "cad9f1d4-e23b-3683-b81b-d1c4c4fd7d4c",
    "name": "John Doe",
    "email": "john.doe@gmail.com",
    "picture": "https://lh3.googleusercontent.com/-XdUIqdbhg/AAAAAAAAI/AAAAAAA/42rbcbv5M/photo.jpg",
    "gender": "male",
    "locale": "en",
    "identities": [
       {
             "provider": "google",
             "id": "104560903311317789798",
             "profile": {
                "id": "104560903311317789798",
                "email": "john.doe@gmail.com",
                "verified_email": true,
                "name": "John Doe",
                "given_name": "John",
                "family_name": "Doe",
                "link": "https://plus.google.com/104560903311317789798",
                "picture": "https://lh3.googleusercontent.com/-XdUIqdbhg/AAAAAAAAI/AAAAAAA/42rbcbv5M/photo.jpg",
                "gender": "male",
                "locale": "en",
                "idpType": "google"
             }
       }
    ]
    
  3. 验证 sub 声明是否与身份令牌中的 sub 声明完全匹配。 如果不匹配,那么请勿使用返回的信息。 要了解更多标记替换信息,请参阅 OIDC 规范

如果外部身份提供者执行更改,那么在用户再次登录时可获取更新的信息。 新令牌会检索最新的数据。

访问 /attributes 端点

根据您的配置,当用户与应用程序交互时,属性会被加密并保存为用户概要文件的一部分。 交互可能是用户登录,也可能是在应用程序中设置首选项。 要访问这些属性,请通过 API 方法来传递访问令牌。

curl -X GET 'https://<region>.appid.cloud.ibm.com/api/v1/attributes'
-H 'Accept: application/json'
-H 'Authorization: Bearer <accessToken>'
//iOS Swift example

func setAttribute(key: String, value: String, completionHandler: @escaping(Error?, [String:Any]?) -> Void)
func setAttribute(key: String, value: String, accessTokenString: String, completionHandler: @escaping(Error?, [String:Any]?) -> Void)

func getAttribute(key: String, completionHandler: @escaping(Error?, [String:Any]?) -> Void)
func getAttribute(key: String, accessTokenString: String, completionHandler: @escaping(Error?, [String:Any]?) -> Void)

func getAttributes(completionHandler: @escaping(Error?, [String:Any]?) -> Void)
func getAttributes(accessTokenString: String, completionHandler: @escaping(Error?, [String:Any]?) -> Void)

func deleteAttribute(key: String, completionHandler: @escaping(Error?, [String:Any]?) -> Void)
func deleteAttribute(key: String, accessTokenString: String, completionHandler: @escaping(Error?, [String:Any]?) -> Void)
void setAttribute(@NonNull String name, @NonNull String value, UserAttributeResponseListener listener);
void setAttribute(@NonNull String name, @NonNull String value, @NonNull AccessToken accessToken, UserAttributeResponseListener listener);

void getAttribute(@NonNull String name, UserAttributeResponseListener listener);
void getAttribute(@NonNull String name, @NonNull AccessToken accessToken, UserAttributeResponseListener listener);

void deleteAttribute(@NonNull String name, UserAttributeResponseListener listener);
void deleteAttribute(@NonNull String name, @NonNull AccessToken accessToken, UserAttributeResponseListener listener);

void getAllAttributes(@NonNull UserAttributeResponseListener listener);
void getAllAttributes(@NonNull AccessToken accessToken, @NonNull UserAttributeResponseListener listener);
const userProfileManager = require("ibmcloud-appid").UserProfileManager;
userProfileManager.init();
var accessToken = req.session[WebAppStrategy.AUTH_CONTEXT].accessToken;

// get all attributes
userProfileManager.getAllAttributes(accessToken).then(function (attributes) {

        });

// get single attribute
userProfileManager.getAttribute(accessToken, name).then(function (attributes) {

        });

// set attribute value
userProfileManager.setAttribute(accessToken, name, value).then(function (attributes) {

        });

// delete attribute
userProfileManager.deleteAttribute(accessToken, name).then(function () {

        });
//Server-side Swift example

func getAllAttributes(accessToken: String, completionHandler: (Swift.Error?, [String: Any]?) -> Void)
func getAttribute(accessToken: String, attributeName: String, completionHandler: (Swift.Error?, [String: Any]?) -> Void)
func setAttribute(accessToken: String, attributeName: String, attributeValue : "abc", completionHandler: (Swift.Error?, [String: Any]?) -> Void)
func deleteAllAttributes(accessToken: String, completionHandler: (Swift.Error?, [String: Any]?) -> Void)

设置定制属性

通过设置定制属性,可以向用户概要文件添加有关用户的信息,例如角色或首选项。 要在用户登录应用程序之前设置定制属性,请参阅预注册未来用户

缺省情况下,定制属性是可修改的,并且可以通过客户机应用程序使用 App ID 访问令牌来更新。 如果不采取适当的预防措施,用户或应用程序只要有访问令牌,就可以在首次用户登录后立即更新自定义属性。 这样做有可能导致意想不到的后果。 例如,用户可将其角色从用户更改为管理员,而这可能会向恶意用户公开管理特权。

  1. 转至 App ID 仪表板的用户概要文件 > 设置选项卡。

  2. 将定制属性切换为已启用

  3. 使用 API 获取访问令牌和身份令牌。

    1. 从凭证中获取租户标识、客户端标识、私钥和 OAuth 服务器 URL。

    2. 使用 base64 编码器对客户机标识和密钥进行编码。

    3. 使用以下代码示例来检索令牌。 您用来获取令牌的授权类型可能会有所不同,这取决于您正在使用的授权类型。 有关选项的详细列表,请查看 swagger 文档

      curl -X POST 'https://<region>.appid.cloud.ibm.com/oauth/v4/<tenantID>/token' \
      -H 'Authorization: Basic base64Encoded{<clientID>:<clientSecret>}' \
      -H 'Accept: application/json' \
      -F 'grant_type=password' \
      -F 'username=testuser@test.com' \
      -F 'password=testuser'
      
      // iOS Swift example
      
      class delegate : TokenResponseDelegate {
         public func onAuthorizationSuccess(accessToken: AccessToken?, identityToken: IdentityToken?, refreshToken: RefreshToken?, response:Response?) {
         //User authenticated
         }
      
         public func onAuthorizationFailure(error: AuthorizationError) {
         //Exception occurred
         }
      }
      
      AppID.sharedInstance.signinWithResourceOwnerPassword(username: username, password: password, delegate: delegate())
      
      AppID.getInstance().signinWithResourceOwnerPassword(getApplicationContext(), username, password, new TokenResponseListener() {
         @Override
         public void onAuthorizationFailure (AuthorizationException exception) {
            //Exception occurred
         }
      
         @Override
         public void onAuthorizationSuccess (AccessToken accessToken, IdentityToken identityToken, RefreshToken refreshToken) {
            //User authenticated
         }
      });
      
      // Declare the API you want to protect
      app.get("/api/protected",
      
         passport.authenticate(APIStrategy.STRATEGY_NAME, {
         session: false
         }),
         function(req, res) {
         // Get full appIdAuthorizationContext from request object
         var appIdAuthContext = req.appIdAuthorizationContext;
      
         appIdAuthContext.accessToken; // Raw access_token
         appIdAuthContext.accessTokenPayload; // Decoded access_token JSON
         appIdAuthContext.identityToken; // Raw identity_token
         appIdAuthContext.identityTokenPayload; // Decoded identity_token JSON
         appIdAuthContext.refreshToken; // Raw refresh_token
         ...
         }
      );
      
      // Server-side swift example
      
      let options = [
         "clientId": "<clientID>",
         "secret": "<secret>",
         "tenantId": "<tenantID>",
         "oauthServerUrl": "<oauthServerURL>",
         "redirectUri": "<appURL>" + CALLBACK_URL
      ]
      let webappKituraCredentialsPlugin = WebAppKituraCredentialsPlugin(options: options)
      let kituraCredentials = Credentials()
      kituraCredentials.register(plugin: webappKituraCredentialsPlugin)
      
  4. 使用 attributes 端点发出 PUT 请求。

    curl -X PUT "https://<region>.appid.cloud.ibm.com/api/v1/attributes/<attributeName>" \
    -H "Authorization: Bearer <token>" \
    -d "<attributeValue>"
    
    // iOS Swift example
    
    AppID.sharedInstance.userProfileManager?.setAttribute("key", "value") { (error, result) in
       guard let result = result, error == nil else {
          return // an error has occurred
       }
    // attributes recieved as a Dictionary
    })
    
    appId.getUserProfileManager().setAttribute(name, value, useThisToken, new UserProfileResponseListener() {
       @Override
       public void onSuccess(JSONObject attributes) {
       // attributes received in JSON format on successful response
       }
    
       @Override
       public void onFailure(UserAttributesException e) {
       // exception occurred
       }
    });
    
    const userProfileManager = require("ibmcloud-appid").UserProfileManager;
    userProfileManager.init();
    
    var accessToken = req.session[WebAppStrategy.AUTH_CONTEXT].accessToken;
    
    userProfileManager.setAttribute(accessToken, name, value).then(function (attributes) {
       // attributes returned as dictionary
    });
    
    // Server-side Swift
    
    let userProfileManager = UserProfileManager(options: options)
    let accesstoken = "access token"
    
    userProfileManager.setAttribute(accessToken: accessToken, attributeName: "name", attributeValue : "abc") { (error, response) in
       guard let response = response, error == error else {
       return // an error has occurred
       }
       // attributes received as a Dictionary
    }