14、非微软技术的信息卡依赖方资源

非微软技术的信息卡依赖方资源

1. 信息卡交换流程

在使用信息卡登录网站时,会经历一系列的步骤,具体如下:
1. 用户访问网站的登录页面。
2. 向依赖方发送 HTTP GET 请求以获取登录页面。
3. 浏览器收到包含 OBJECT 标签的 HTML 登录页面。
4. 用户点击“使用信息卡登录”按钮,触发身份选择器评估依赖方的策略(在 OBJECT 标签中指定),并找出符合策略的信息卡子集。
5. 身份选择器显示符合条件的信息卡子集。
6. 用户选择要使用的信息卡。
7. 身份选择器向卡发行方(身份提供者)请求安全策略。
8. 收到安全策略。
9. 用户向身份提供者进行身份验证,并请求包含所需声明的安全令牌。
10. 收到安全令牌。
11. 通过 HTTPS POST 将登录页面(包含安全令牌)发送到依赖方。
12. 依赖方返回带有 cookie 的 HTTP 重定向到主页。
13. 向依赖方发送 HTTP GET 请求以获取主页 URL。
14. 浏览器收到 HTML 页面。
15. 用户已通过身份验证,可以访问网站。

以下是该流程的 mermaid 流程图:

graph LR
    A[用户访问登录页面] --> B[发送 HTTP GET 请求]
    B --> C[返回 HTML 登录页面]
    C --> D[用户点击登录按钮]
    D --> E[身份选择器评估策略]
    E --> F[显示符合条件的信息卡]
    F --> G[用户选择信息卡]
    G --> H[请求安全策略]
    H --> I[收到安全策略]
    I --> J[用户身份验证并请求令牌]
    J --> K[收到安全令牌]
    K --> L[HTTPS POST 登录页面]
    L --> M[返回重定向到主页]
    M --> N[发送 HTTP GET 请求主页]
    N --> O[返回 HTML 页面]
    O --> P[用户访问网站]

2. 创建登录页面

依赖方的第一步是创建登录页面,该页面用于指定期望的声明和令牌类型。页面包含一个类型为“application/x-informationCard”的 OBJECT 元素,其中包含 PARAM 子元素,如下表所示:
| Parameter | Definition |
| — | — |
| tokenType | 指定依赖方请求的令牌类型 |
| requiredClaims | 指定依赖方所需的声明 |
| optionalClaims | 指定依赖方可以使用的可选声明 |

以下是一个示例 HTML 页面,指定了 SAML 1.0 令牌和所需的声明:

<html>
<head>
     <title>Sample Relying Party</title>
    <style>BODY {color:#000;font-family: verdana, arial, sans-serif;}</style>
</head>
<body>
    <h2>Log In with Information Card</h2>
    <form name='infocard' method='post' action='./infocard' enctype='application/x-www-form-urlencoded'>
        <img src="img/card_off.png"
           onMouseOver="this.src='img/card_on.png';"
           onMouseOut="this.src='img/card_off.png';"
           onClick="infocard.submit()"/>
        <OBJECT type="application/x-informationCard" name="xmlToken">
              <PARAM Name="tokenType" Value="urn:oasis:names:tc:SAML:1.0:assertion">
              <PARAM Name="requiredClaims" Value="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifier http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress">
              <PARAM Name="optionalClaims" Value="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/streetaddress http://schemas.xmlsoap.org/ws/2005/05/identity/claims/locality http://schemas.xmlsoap.org/ws/2005/05/identity/claims/stateorprovince http://schemas.xmlsoap.org/ws/2005/05/identity/claims/postalcode http://schemas.xmlsoap.org/ws/2005/05/identity/claims/country http://schemas.xmlsoap.org/ws/2005/05/identity/claims/homephone http://schemas.xmlsoap.org/ws/2005/05/identity/claims/otherphone http://schemas.xmlsoap.org/ws/2005/05/identity/claims/mobilephone http://schemas.xmlsoap.org/ws/2005/05/identity/claims/dateofbirth http://schemas.xmlsoap.org/ws/2005/05/identity/claims/gender">
        </OBJECT>
    </form>
    <br>Click the image above to log in with an information card.<br>
</body>
</html>

以下是一个 PHP 脚本示例,用于生成类似的登录页面:

<div id="login" style="font-family: arial; font-size: 2em;">
<h2> Log In with an InfoCard</h2>
 <left>
   <form name="infocard" id="infocard" method="post" action="informationcard-processing.php">
        <img src="img/card_off.png"
           onMouseOver="this.src='img/card_on.png';"
           onMouseOut="this.src='img/card_off.png';"
           onClick="infocard.submit()"/>
        <OBJECT type="application/x-informationCard" name="xmlToken">
              <PARAM Name="tokenType" Value="urn:oasis:names:tc:SAML:1.0:assertion">
              <PARAM Name="requiredClaims" Value="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifier http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress">
              <PARAM Name="optionalClaims" Value="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/streetaddress http://schemas.xmlsoap.org/ws/2005/05/identity/claims/locality http://schemas.xmlsoap.org/ws/2005/05/identity/claims/stateorprovince http://schemas.xmlsoap.org/ws/2005/05/identity/claims/postalcode http://schemas.xmlsoap.org/ws/2005/05/identity/claims/country http://schemas.xmlsoap.org/ws/2005/05/identity/claims/homephone http://schemas.xmlsoap.org/ws/2005/05/identity/claims/otherphone http://schemas.xmlsoap.org/ws/2005/05/identity/claims/mobilephone http://schemas.xmlsoap.org/ws/2005/05/identity/claims/dateofbirth http://schemas.xmlsoap.org/ws/2005/05/identity/claims/gender">
        </OBJECT>
    </form>
  </left>
</div>

当用户点击提交按钮时,会触发上述流程中的步骤 4 到 11。在步骤 11 中,浏览器会提交包含安全令牌的表单,此时依赖方服务器代码开始解密令牌并提取声明。

3. Java 依赖方示例:xmldap.org 的 Java 依赖方

3.1 获取代码

首先,需要从 Google Code 下载 xmldap.org 依赖方的代码,链接为:http://code.google.com/p/openinfocard/source 。

3.2 创建 Java Servlet

以下是完整的 Servlet 代码:

package org.xmldap.rp.servlet;
import org.xmldap.exceptions.InfoCardProcessingException;
import org.xmldap.exceptions.KeyStoreException;
import org.xmldap.rp.Token;
import org.xmldap.util.KeystoreUtil;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.security.PrivateKey;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class SampleRelyingParty extends HttpServlet {
    private PrivateKey privateKey = null;

    public void init(ServletConfig config) throws ServletException {
        try {
            //Get the private key used for decryption - must correspond to the server's SSL cert
            KeystoreUtil keystore = new KeystoreUtil("xmldap.jks", "storepassword");
            privateKey = keystore.getPrivateKey("certalias", "keypassword");
        } catch (KeyStoreException e) {
            throw new ServletException("Error accessing PrivateKey", e);
        }
    }

    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        try {
            //Get the encrypted token from the request
            String encryptedXML = request.getParameter("xmlToken");
            if ((encryptedXML == null) || (encryptedXML.equals("")))
                throw new ServletException("No token provided");
            //Decryt the token
            Token token = new Token(encryptedXML, privateKey);
            //Check the token's validity
            if ((token.isSignatureValid()) && (token.isConditionsValid()) && (token.isCertificateValid())) {
                //Print out the provided claims
                PrintWriter out = response.getWriter();
                Map claims = token.getClaims();
                out.println("<h2>You provided the following claims:</h2>");
                Set keys = claims.keySet();
                Iterator keyIter = keys.iterator();
                while (keyIter.hasNext()) {
                    String name = (String) keyIter.next();
                    String value = (String) claims.get(name);
                    out.println(name + ": " + value + "<br>");
                }
                out.close();
            }
        } catch (InfoCardProcessingException e) {
            throw new ServletException(e);
        }
    }
}

3.3 代码分析

  • 命名空间引用 :除了 Java 提供的命名空间外,该 Servlet 还引用了 xmldap.org 的四个命名空间,如下表所示:
    | Namespace | Contains |
    | — | — |
    | org.xmldap.util.KeyStoreUtil | 用于创建密钥库的 KeystoreUtil 类 |
    | org.xmldap.exceptions.KeyStoreException | 可能由 KeystoreUtil 触发的 KeyStoreException 异常 |
    | org.xmldap.rp.Token | 用于在依赖方创建和查询解密令牌的 Token 类 |
    | org.xmldap.util.exceptions.InfoCardProcessingException | 在依赖方处理信息卡时可能触发的 InfoCardProcessingException 异常 |

  • 初始化方法 :在 init 方法中,使用 KeystoreUtil 类创建一个新的密钥库,并从中获取用于解密的私钥,将其赋值给成员变量 privateKey

public void init(ServletConfig config) throws ServletException {
    try {
        KeystoreUtil keystore = new KeystoreUtil("xmldap.jks", "storepassword");
        privateKey = keystore.getPrivateKey("certalias", "keypassword");
    } catch (KeyStoreException e) {
        throw new ServletException("Error accessing PrivateKey", e);
    }
}
  • 处理 POST 请求 :当表单通过 POST 提交到依赖方时,由 doPost 方法处理。首先从请求中获取加密的令牌,验证令牌是否存在,然后创建 Token 对象进行解密。接着检查令牌的有效性,如果有效,则打印出令牌中的声明。
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    try {
        String encryptedXML = request.getParameter("xmlToken");
        if ((encryptedXML == null) || (encryptedXML.equals("")))
            throw new ServletException("No token provided");
        Token token = new Token(encryptedXML, privateKey);
        if ((token.isSignatureValid()) && (token.isConditionsValid()) && (token.isCertificateValid())) {
            PrintWriter out = response.getWriter();
            Map claims = token.getClaims();
            out.println("<h2>You provided the following claims:</h2>");
            Set keys = claims.keySet();
            Iterator keyIter = keys.iterator();
            while (keyIter.hasNext()) {
                String name = (String) keyIter.next();
                String value = (String) claims.get(name);
                out.println(name + ": " + value + "<br>");
            }
            out.close();
        }
    } catch (InfoCardProcessingException e) {
        throw new ServletException(e);
    }
}

4. PHP 依赖方示例:Kim Cameron 的 PHP 简单信息卡演示

4.1 获取代码

可以从 Kim Cameron 的网站下载示例代码:http://www.identityblog.com/wp-content/resources/simple-infocard-demo/simple-infocard-demo-v3.zip 。下载后将其解压到 Web 服务器可访问的目录。

4.2 请求信息卡

infocard-demo.php 文件类似于前面介绍的登录页面,指定了所需的声明和 SAML 1.0 令牌:

<div id="login" style="font-family: arial; font-size: 2em;">
<p>Simple Login Demo</p>
 <left>
   <form name="ctl00" id="ctl00" method="post" action="infocard-demo-processing.php">
      <img src='wp-images/card.jpg' onClick='ctl00.submit()'/>
      <OBJECT type="application/x-informationCard" name="xmlToken">
        <PARAM Name="tokenType" Value="urn:oasis:names:tc:SAML:1.0:assertion">
                    <PARAM Name="requiredClaims" value="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname~CC http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress http://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifier">
      </OBJECT>
    </form>
  </left>
</div>

4.3 处理令牌

当表单提交时,会将表单内容(包含身份提供者提供的令牌)发送到 infocard-demo-processing.php 进行处理。该文件包含了 infocard-print-binary.php infocard-post-decrypt.php infocard-post-get-claims.php ,这些文件负责解密令牌和提取声明。

以下是 infocard-demo-processing.php 的代码:

include_once("infocard-print-binary.php");
include_once("infocard-post-decrypt.php");
include_once("infocard-post-get-claims.php");

$claims = array();
$token = "";
$error = "";

// Checking that there is a referrer
if (TRUE != array_key_exists('HTTP_REFERER', $_SERVER)){
    $error = "infocard-demo-processing.php cannot be accessed directly";
    break;
}
// Checking for people who don't know you need to use HTTPS at this point
if (strncmp("https:", $_SERVER["HTTP_REFERER"], 5) != 0){
    $error = "infocards currently must be invoked from an https protected page";
    break;
}
// Checking to see whether a token was produced
if (!array_key_exists("xmlToken", $_POST)){
    $error = "InfoCard not present";
    break;
}
// 处理令牌
if (!$tokenContent=stripslashes($_POST["xmlToken"]))
{
    $error = "No xml token";
    break;
}
if (array_key_exists("decrypted", $_POST) == FALSE){
    // Decrypting the token
    $error=infocard_post_decrypt($tokenContent, $token);
    if ($error != NULL)
    {
        $error = "infocard_post_decrypt returns $error";
        break;
    }
}
else{
    $token = $tokenContent;
}
// Checking the signature of what's inside - and getting the claims
if ($error = infocard_post_get_claims($token, $claims))
{
    break;
}
// Printing the claims
print "<div style=\"font-family: arial; font-size: 1.2em;\">";
foreach ($claims as $name => $value) {
    if ($name == "modulusHash")
        continue;
    print "claim:  $name -- $value <br>";
}
print "</div>";

function get_settings($key)
{
    if ($key == "infocard_key") {
$retVal = "-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,9266952B733BFBE0
Z4WmpirV4dXvYjNmfSN99Iu4iYzUWa4/CPZG0NParYSVHMOhb4lsS6iISjgniGG9
zhA862KDwsYUjgoyAIXfJAd5Z3hXiyJYdkygF/DUgeQFcwQjsWmkguq27EDHW6nS
TVq7lTGsSxKPzc6HmcR5jEupq9xFLcqrSbC3Jn3lmiRlQYOw1BLcuv3o1WUoQsqb
...........................
+j40vRpPzY6ngd1QNOfd5jkin7sjW1YlsEsRPV8OzEJvNmBZF274Cw==
-----END RSA PRIVATE KEY-----";
    }
    else if ($key == "infocard_opener"){
        $retVal = "1234567";
    }
    else {
        $retVal = NULL;
    }
    return($retVal);
}

4.4 代码分析

  • 验证请求 :首先检查请求是否有 HTTP 引用,并且引用是否通过 SSL 连接(HTTPS)发送信息。然后验证是否提供了令牌。
  • 解密令牌 :如果令牌未解密,则使用 infocard_post_decrypt 函数进行解密。
  • 提取声明 :使用 infocard_post_get_claims 函数检查签名并提取声明。
  • 打印声明 :遍历声明并打印到页面上。

5. 其他依赖方项目

5.1 Java 依赖方

  • JInformationCard :项目网站为 http://informationcard.sourceforge.net ,由德国的 Fraunhofer Institute FOKUS 开发,遵循 BSD 许可证。该项目旨在展示使用 Java 语言编写的信息卡互操作性,支持 Apache Tomcat、Sun Java 网络服务器和 WebSphere Application Server 平台。提供了一个名为 Bio Mini Shop 的示例依赖方应用程序,可在 https://zeno.fokus.fraunhofer.de/BioMiniShop/home.jsp 访问。
  • cardspaceauthn 项目 :为 OpenSSO 和 Sun Access Manager 提供信息卡身份验证的插件模块。项目主页为 https://cardspaceauthn.dev.java.net/ ,遵循 Common Development and Distribution License。

5.2 PHP 依赖方

  • Apache Authentication Module for CardSpace :由 PingIdentity 在 2007 年发布,是一个开源模块,允许使用 Apache 服务器的应用程序将信息卡作为额外的身份验证机制。可从 http://www.sourceid.org/download/list 下载,遵循 SourceID Open Software License 2.1。
  • PamelaWare Add-In for WordPress :为流行的博客引擎 WordPress 提供信息卡支持,基于 Kim Cameron 的 PHP 示例开发。项目主页为 http://pamelaproject.com/pwwp/ 。

5.3 Ruby on Rails 依赖方

  • Information Card Ruby :目前唯一的 Ruby on Rails 依赖方项目,由 Microsoft 和 ThoughtWorks 赞助,遵循 BSD 许可证。项目主页为 http://www.codeplex.com/informationcardruby/ ,最新代码可在 http://rubyforge.org/projects/informationcard/ 找到。该项目旨在为 Ruby on Rails 依赖方 Web 应用程序提供集成个人信息卡的插件和支持库。

综上所述,除了微软技术外,还有多种开发平台可以实现信息卡依赖方,每个平台都有相应的示例代码和开源项目可供参考。

6. 信息卡依赖方技术总结

6.1 不同语言依赖方对比

语言 示例项目 特点 适用场景
Java xmldap.org、JInformationCard、cardspaceauthn 有成熟的类库和工具支持,适用于大型企业级应用开发 对安全性和稳定性要求较高的企业级 Web 应用
PHP Kim Cameron 示例、Apache Authentication Module for CardSpace、PamelaWare Add - In for WordPress 代码简洁,易于上手,与 Web 开发结合紧密 快速开发的 Web 应用,如博客、小型网站
Ruby on Rails Information Card Ruby 基于 Rails 框架,开发效率高,适合快速迭代的项目 敏捷开发的 Web 应用,注重开发速度和灵活性

6.2 信息卡依赖方开发关键步骤

以下是使用信息卡实现依赖方的关键步骤 mermaid 流程图:

graph LR
    A[创建登录页面] --> B[指定所需声明和令牌类型]
    B --> C[用户操作触发身份验证流程]
    C --> D[获取加密令牌]
    D --> E[解密令牌]
    E --> F[验证令牌有效性]
    F --> G[提取声明]
    G --> H[处理声明并完成登录]

具体步骤如下:
1. 创建登录页面 :页面包含 OBJECT 元素,指定 tokenType requiredClaims optionalClaims 。示例代码如下:

<OBJECT type="application/x-informationCard" name="xmlToken">
    <PARAM Name="tokenType" Value="urn:oasis:names:tc:SAML:1.0:assertion">
    <PARAM Name="requiredClaims" Value="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifier http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress">
    <PARAM Name="optionalClaims" Value="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/streetaddress http://schemas.xmlsoap.org/ws/2005/05/identity/claims/locality http://schemas.xmlsoap.org/ws/2005/05/identity/claims/stateorprovince http://schemas.xmlsoap.org/ws/2005/05/identity/claims/postalcode http://schemas.xmlsoap.org/ws/2005/05/identity/claims/country http://schemas.xmlsoap.org/ws/2005/05/identity/claims/homephone http://schemas.xmlsoap.org/ws/2005/05/identity/claims/otherphone http://schemas.xmlsoap.org/ws/2005/05/identity/claims/mobilephone http://schemas.xmlsoap.org/ws/2005/05/identity/claims/dateofbirth http://schemas.xmlsoap.org/ws/2005/05/identity/claims/gender">
</OBJECT>
  1. 用户操作触发身份验证流程 :用户点击登录按钮,触发身份选择器评估依赖方策略,选择合适的信息卡。
  2. 获取加密令牌 :从请求中获取加密的令牌,如 Java 代码中的 request.getParameter("xmlToken")
  3. 解密令牌 :使用私钥解密令牌,不同语言有不同的实现方式,如 Java 中的 Token token = new Token(encryptedXML, privateKey)
  4. 验证令牌有效性 :检查令牌的签名、条件和证书是否有效,如 Java 中的 token.isSignatureValid() token.isConditionsValid() token.isCertificateValid()
  5. 提取声明 :从解密后的令牌中提取所需的声明,如 Java 中的 Map claims = token.getClaims()
  6. 处理声明并完成登录 :根据提取的声明进行用户身份验证和登录处理,如将声明信息存储到数据库或创建会话。

7. 开发注意事项

7.1 安全性

  • 私钥管理 :私钥用于解密令牌,必须妥善保管,避免泄露。在代码中使用私钥时,要确保其安全性,如使用加密存储和访问控制。
  • HTTPS 连接 :信息卡的使用必须通过 HTTPS 连接,确保数据传输的安全性。在开发过程中,要验证请求是否通过 HTTPS 发送,如 PHP 代码中的 strncmp("https:", $_SERVER["HTTP_REFERER"], 5) != 0

7.2 兼容性

  • 库和版本 :不同语言的依赖库有不同的版本要求,要确保使用的库版本兼容。如 PHP 示例代码依赖于 mcrypt 和 OpenSSL 库,且 OpenSSL 版本需 0.9.6 或更高。
  • 浏览器支持 :要确保信息卡功能在主流浏览器中正常工作,进行充分的兼容性测试。

7.3 性能优化

  • 缓存机制 :对于频繁使用的信息,如安全策略和令牌验证结果,可以使用缓存机制提高性能。
  • 代码优化 :优化代码逻辑,减少不必要的计算和数据库查询,提高系统响应速度。

8. 总结

通过以上介绍,我们了解了在 Java、PHP 和 Ruby on Rails 等不同开发平台上实现信息卡依赖方的方法。每个平台都有其独特的优势和适用场景,开发者可以根据项目需求选择合适的技术。在开发过程中,要注意安全性、兼容性和性能优化等方面的问题,确保信息卡依赖方系统的稳定运行。同时,开源项目和示例代码为我们提供了很好的参考,有助于快速上手和开发出高质量的应用程序。

基于数据驱动的 Koopman 算子的递归神经网络模型线性化,用于纳米定位系统的预测控制研究(Matlab代码实现)内容概要:本文围绕“基于数据驱动的Koopman算子的递归神经网络模型线性化”展开,旨在研究纳米定位系统的预测控制问题,并提供完整的Matlab代码实现。文章结合数据驱动法与Koopman算子理论,利用递归神经网络(RNN)对线性系统进行建模与线性化处理,从而提升纳米级定位系统的精度与动态响应性能。该法通过提取系统隐含动态特征,构建近似线性模型,便于后续模型预测控制(MPC)的设计与优化,适用于高精度自动化控制场景。文中还展示了相关实验验证与仿真结果,证明了该法的有效性和先进性。; 适合人群:具备一定控制理论基础和Matlab编程能力,从事精密控制、智能制造、自动化或相关领域研究的研究生、科研人员及工程技术人员。; 使用场景及目标:①应用于纳米级精密定位系统(如原子力显微镜、半导体制造设备)中的高性能控制设计;②为线性系统建模与线性化提供一种结合深度学习与现代控制理论的新思路;③帮助读者掌握Koopman算子、RNN建模与模型预测控制的综合应用。; 阅读建议:建议读者结合提供的Matlab代码逐段理解算法实现流程,重点关注数据预处理、RNN结构设计、Koopman观测矩阵构建及MPC控制器集成等关键环节,并可通过更换实际系统数据进行迁移验证,深化对法泛化能力的理解。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值