zoukankan      html  css  js  c++  java
  • vertx 异步编程指南 step7-保护和控制访问

    保护和控制访问与Vert.x很容易。在本节中,我们将:

    1. 从HTTP转移到HTTPS,以及

    2. 使用基于组的权限将用户身份验证添加到Web应用程序,以及

    3. 使用JSON Web令牌(JWT)控制对Web API的访问。

    证书可以存储在Java KeyStore文件中。您可能需要用于测试目的的自签名证书,以下是如何在server-keystore.jksKeyStore中创建一个密码为secret

    1.  
      keytool -genkey
    2.  
      -alias test
    3.  
      -keyalg RSA
    4.  
      -keystore server-keystore.jks
    5.  
      -keysize 2048
    6.  
      -validity 360
    7.  
      -dname CN=localhost
    8.  
      -keypass secret
    9.  
      -storepass secret

    然后,我们可以更改HTTP服务器创建,以传递一个HttpServerOptions对象来指定我们需要SSL,并指向我们的KeyStore文件:

    1.  
      HttpServer server = vertx.createHttpServer(new HttpServerOptions()
    2.  
      .setSsl(true)
    3.  
      .setKeyStoreOptions(new JksOptions()
    4.  
      .setPath("server-keystore.jks")
    5.  
      .setPassword("secret")));

    我们可以将浏览器指向https:// localhost:8080 /,但由于证书是自签名的,所以任何优秀的浏览器都会正确地产生安全警告:

    最后但并非最不重要的是,我们需要更新测试用例,ApiTest因为原始代码是用于通过Web客户端发出HTTP请求的:

    1.  
      webClient = WebClient.create(vertx, new WebClientOptions()
    2.  
      .setDefaultHost("localhost")
    3.  
      .setDefaultPort(8080)
    4.  
      .setSsl(true) (1)
    5.  
      .setTrustOptions(new JksOptions().setPath("server-keystore.jks").setPassword("secret")));
    1. 确保SSL。

    2. 由于证书是自签名的,我们需要明确信任它,否则Web客户端连接将失败,就像Web浏览器一样。

    访问控制和认证

    Vert.x为执行身份验证和授权提供了广泛的选项。官方支持的模块涵盖了JDBC,MongoDB,Apache Shiro,OAuth2以及众所周知的提供者和JWT(JSON Web令牌)。

    虽然下一部分将介绍JWT,但本部分重点介绍如何使用Apache Shiro,这在验证必须由LDAP或Active Directory服务器支持时特别有用。在我们的例子中,我们只是将凭据存储在属性文件中以保持简单,但对LDAP服务器的API使用保持不变。

    目标是要求用户使用wiki进行身份验证,并拥有基于角色的权限:

    • 没有角色只允许阅读页面,

    • 具有作家角色允许编辑页面,

    • 具有编辑角色允许创建,编辑和删除页面,

    • 具有管理角色相当于具有所有可能的角色。

    警告
    由于Apache Shiro的内部运作,Vert.x Shiro集成有一些问题。有些部分阻塞会影响性能,有些数据是使用线程本地状态存储的。您不应该尝试滥用暴露的内部状态API。

    将Apache Shiro身份验证添加到路由

    第一步是将vertx-auth-shiro模块添加到Maven依赖关系列表中:

    1.  
      <dependency>
    2.  
      <groupId>io.vertx</groupId>
    3.  
      <artifactId>vertx-auth-shiro</artifactId>
    4.  
      </dependency>

    我们使用的属性文件定义如下,位于src/main/resources/wiki-users.properties

    1.  
      user.root=w00t,admin
    2.  
      user.foo=bar,editor,writer
    3.  
      user.bar=baz,writer
    4.  
      user.baz=baz
    5.  
       
    6.  
      role.admin=*
    7.  
      role.editor=create,delete,update
    8.  
      role.writer=update

    user前缀的条目是一个用户帐户,其中第一个值条目是密码可能跟随的角色。在这个例子中,用户bar有密码baz,是一个writer,并且writerupdate权限。

    回到HttpServerVerticle课程代码,我们使用Apache Shiro创建认证提供者:

    1.  
      AuthProvider auth = ShiroAuth.create(vertx, new ShiroAuthOptions()
    2.  
      .setType(ShiroAuthRealmType.PROPERTIES)
    3.  
      .setConfig(new JsonObject()
    4.  
      .put("properties_path", "classpath:wiki-users.properties")));

    ShiroAuth对象实例然后用于处理服务器端用户会话:

    1.  
      Router router = Router.router(vertx);
    2.  
       
    3.  
      router.route().handler(CookieHandler.create());
    4.  
      router.route().handler(BodyHandler.create());
    5.  
      router.route().handler(SessionHandler.create(LocalSessionStore.create(vertx)));
    6.  
      router.route().handler(UserSessionHandler.create(auth)); (1)
    7.  
       
    8.  
      AuthHandler authHandler = RedirectAuthHandler.create(auth, "/login"); (2)
    9.  
      router.route("/").handler(authHandler); (3)
    10.  
      router.route("/wiki/*").handler(authHandler);
    11.  
      router.route("/action/*").handler(authHandler);
    12.  
       
    13.  
      router.get("/").handler(this::indexHandler);
    14.  
      router.get("/wiki/:page").handler(this::pageRenderingHandler);
    15.  
      router.post("/action/save").handler(this::pageUpdateHandler);
    16.  
      router.post("/action/create").handler(this::pageCreateHandler);
    17.  
      router.get("/action/backup").handler(this::backupHandler);
    18.  
      router.post("/action/delete").handler(this::pageDeletionHandler);
    1. 我们为所有路由安装用户会话处理程序。

    2. 这会自动将请求重定向到/login没有用户会话的请求时。

    3. 我们安装authHandler了所有需要身份验证的路由。

    最后,我们需要创建3条路线来显示登录表单,处理登录表单提交和注销用户:

    1.  
      router.get("/login").handler(this::loginHandler);
    2.  
      router.post("/login-auth").handler(FormLoginHandler.create(auth)); (1)
    3.  
       
    4.  
      router.get("/logout").handler(context -> {
    5.  
      context.clearUser(); (2)
    6.  
      context.response()
    7.  
      .setStatusCode(302)
    8.  
      .putHeader("Location", "/")
    9.  
      .end();
    10.  
      });
    1. FormLoginHandler是处理登录提交请求的助手。默认情况下,它希望HTTP POST请求具有:username作为登录名,password密码以及return_url成功时重定向到的URL。

    2. 注销用户很简单,就像从当前清除它一样RoutingContext

    loginHandler方法的代码是:

    1.  
      private void loginHandler(RoutingContext context) {
    2.  
      context.put("title", "Login");
    3.  
      templateEngine.render(context, "templates", "/login.ftl", ar -> {
    4.  
      if (ar.succeeded()) {
    5.  
      context.response().putHeader("Content-Type", "text/html");
    6.  
      context.response().end(ar.result());
    7.  
      } else {
    8.  
      context.fail(ar.cause());
    9.  
      }
    10.  
      });
    11.  
      }

    HTML模板位于src/main/resources/templates/login.ftl

    1.  
      <#include "header.ftl">
    2.  
       
    3.  
      <div class="row">
    4.  
       
    5.  
      <div class="col-md-12 mt-1">
    6.  
      <form action="/login-auth" method="POST">
    7.  
      <div class="form-group">
    8.  
      <input type="text" name="username" placeholder="login">
    9.  
      <input type="password" name="password" placeholder="password">
    10.  
      <input type="hidden" name="return_url" value="/">
    11.  
      <button type="submit" class="btn btn-primary">Login</button>
    12.  
      </div>
    13.  
      </form>
    14.  
      </div>
    15.  
       
    16.  
      </div>
    17.  
       
    18.  
      <#include "footer.ftl">

    登录页面如下所示:

    支持基于角色的功能

    只有当用户拥有足够的权限时才能激活功能。在以下屏幕截图中,管理员可以创建一个页面并执行备份:

    相比之下,没有角色的用户不能执行这些操作:

    为此,我们可以访问RoutingContext用户参考,并查询权限。以下是indexHandler处理器方法的实现方式:

    1.  
      private void indexHandler(RoutingContext context) {
    2.  
      context.user().isAuthorised("create", res -> { (1)
    3.  
      boolean canCreatePage = res.succeeded() && res.result(); (2)
    4.  
      dbService.fetchAllPages(reply -> {
    5.  
      if (reply.succeeded()) {
    6.  
      context.put("title", "Wiki home");
    7.  
      context.put("pages", reply.result().getList());
    8.  
      context.put("canCreatePage", canCreatePage); (3)
    9.  
      context.put("username", context.user().principal().getString("username")); (4)
    10.  
      templateEngine.render(context, "templates", "/index.ftl", ar -> {
    11.  
      if (ar.succeeded()) {
    12.  
      context.response().putHeader("Content-Type", "text/html");
    13.  
      context.response().end(ar.result());
    14.  
      } else {
    15.  
      context.fail(ar.cause());
    16.  
      }
    17.  
      });
    18.  
      } else {
    19.  
      context.fail(reply.cause());
    20.  
      }
    21.  
      });
    22.  
      });
    23.  
      }
    1. 这是如何进行权限查询的。请注意,这是一个异步操作。

    2. 我们使用结果...

    3. ...在HTML模板中利用它。

    4. 我们也可以访问用户登录。

    模板代码已被修改为仅基于以下值来呈现特定片段canCreatePage

    1.  
      <#include "header.ftl">
    2.  
       
    3.  
      <div class="row">
    4.  
       
    5.  
      <div class="col-md-12 mt-1">
    6.  
      <#if context.canCreatePage>
    7.  
      <div class="float-xs-right">
    8.  
      <form class="form-inline" action="/action/create" method="post">
    9.  
      <div class="form-group">
    10.  
      <input type="text" class="form-control" id="name" name="name" placeholder="New page name">
    11.  
      </div>
    12.  
      <button type="submit" class="btn btn-primary">Create</button>
    13.  
      </form>
    14.  
      </div>
    15.  
      </#if>
    16.  
      <h1 class="display-4">${context.title}</h1>
    17.  
      <div class="float-xs-right">
    18.  
      <a class="btn btn-outline-danger" href="/logout" role="button" aria-pressed="true">Logout (${context.username})</a>
    19.  
      </div>
    20.  
      </div>
    21.  
       
    22.  
      <div class="col-md-12 mt-1">
    23.  
      <#list context.pages>
    24.  
      <h2>Pages:</h2>
    25.  
      <ul>
    26.  
      <#items as page>
    27.  
      <li><a href="/wiki/${page}">${page}</a></li>
    28.  
      </#items>
    29.  
      </ul>
    30.  
      <#else>
    31.  
      <p>The wiki is currently empty!</p>
    32.  
      </#list>
    33.  
       
    34.  
      <#if context.canCreatePage>
    35.  
      <#if context.backup_gist_url?has_content>
    36.  
      <div class="alert alert-success" role="alert">
    37.  
      Successfully created a backup:
    38.  
      <a href="${context.backup_gist_url}" class="alert-link">${context.backup_gist_url}</a>
    39.  
      </div>
    40.  
      <#else>
    41.  
      <p>
    42.  
      <a class="btn btn-outline-secondary btn-sm" href="/action/backup" role="button" aria-pressed="true">Backup</a>
    43.  
      </p>
    44.  
      </#if>
    45.  
      </#if>
    46.  
      </div>
    47.  
       
    48.  
      </div>
    49.  
       
    50.  
      <#include "footer.ftl">

    该代码类似于确保更新或删除页面仅限于某些角色,并可从指南Git存储库中获得。

    确保检查也是在HTTP POST请求处理程序上完成,而不仅仅是在呈现HTML页面时进行。事实上,恶意攻击者仍然可以制作请求并执行操作,而无需进行身份验证。以下是如何通过将pageDeletionHandler代码包装在最上面的权限检查中来保护页面删除:

    1.  
      private void pageDeletionHandler(RoutingContext context) {
    2.  
      context.user().isAuthorised("delete", res -> {
    3.  
      if (res.succeeded() && res.result()) {
    4.  
       
    5.  
      // Original code:
    6.  
      dbService.deletePage(Integer.valueOf(context.request().getParam("id")), reply -> {
    7.  
      if (reply.succeeded()) {
    8.  
      context.response().setStatusCode(303);
    9.  
      context.response().putHeader("Location", "/");
    10.  
      context.response().end();
    11.  
      } else {
    12.  
      context.fail(reply.cause());
    13.  
      }
    14.  
      });
    15.  
       
    16.  
      } else {
    17.  
      context.response().setStatusCode(403).end();
    18.  
      }
    19.  
      });
    20.  
      }

    使用JWT验证Web API请求

    JSON Web TokensRFC 7519)是发布包含声明的 JSON编码标记的标准,通常标识用户和权限,但声明可以是任何事情。

    令牌由服务器发出,并使用服务器密钥进行签名。客户端可以将令牌发送回以及随后的请求:客户端和服务器都可以检查令牌是否真实且未改变。

    警告
    JWT令牌签名时,其内容未加密。它必须通过安全通道(例如HTTPS)进行传输,并且它不应该有敏感数据作为声明(例如,密码,私人API密钥等)。

    添加JWT支持

    我们首先将vertx-auth-jwt模块添加到Maven依赖关系中:

    1.  
      <dependency>
    2.  
      <groupId>io.vertx</groupId>
    3.  
      <artifactId>vertx-auth-jwt</artifactId>
    4.  
      </dependency>

    我们将有一个JCEKS密钥库来保存我们测试的密钥。以下是如何keystore.jceks使用各种长度的适当键生成一个文件:

    1.  
      keytool -genseckey -keystore keystore.jceks -storetype jceks -storepass secret -keyalg HMacSHA256 -keysize 2048 -alias HS256 -keypass secret
    2.  
      keytool -genseckey -keystore keystore.jceks -storetype jceks -storepass secret -keyalg HMacSHA384 -keysize 2048 -alias HS384 -keypass secret
    3.  
      keytool -genseckey -keystore keystore.jceks -storetype jceks -storepass secret -keyalg HMacSHA512 -keysize 2048 -alias HS512 -keypass secret
    4.  
      keytool -genkey -keystore keystore.jceks -storetype jceks -storepass secret -keyalg RSA -keysize 2048 -alias RS256 -keypass secret -sigalg SHA256withRSA -dname "CN=,OU=,O=,L=,ST=,C=" -validity 360
    5.  
      keytool -genkey -keystore keystore.jceks -storetype jceks -storepass secret -keyalg RSA -keysize 2048 -alias RS384 -keypass secret -sigalg SHA384withRSA -dname "CN=,OU=,O=,L=,ST=,C=" -validity 360
    6.  
      keytool -genkey -keystore keystore.jceks -storetype jceks -storepass secret -keyalg RSA -keysize 2048 -alias RS512 -keypass secret -sigalg SHA512withRSA -dname "CN=,OU=,O=,L=,ST=,C=" -validity 360
    7.  
      keytool -genkeypair -keystore keystore.jceks -storetype jceks -storepass secret -keyalg EC -keysize 256 -alias ES256 -keypass secret -sigalg SHA256withECDSA -dname "CN=,OU=,O=,L=,ST=,C=" -validity 360
    8.  
      keytool -genkeypair -keystore keystore.jceks -storetype jceks -storepass secret -keyalg EC -keysize 256 -alias ES384 -keypass secret -sigalg SHA384withECDSA -dname "CN=,OU=,O=,L=,ST=,C=" -validity 360
    9.  
      keytool -genkeypair -keystore keystore.jceks -storetype jceks -storepass secret -keyalg EC -keysize 256 -alias ES512 -keypass secret -sigalg SHA512withECDSA -dname "CN=,OU=,O=,L=,ST=,C=" -validity 360

    我们需要在API路由上安装JWT令牌处理程序:

    1.  
      Router apiRouter = Router.router(vertx);
    2.  
       
    3.  
      JWTAuth jwtAuth = JWTAuth.create(vertx, new JsonObject()
    4.  
      .put("keyStore", new JsonObject()
    5.  
      .put("path", "keystore.jceks")
    6.  
      .put("type", "jceks")
    7.  
      .put("password", "secret")));
    8.  
       
    9.  
      apiRouter.route().handler(JWTAuthHandler.create(jwtAuth, "/api/token"));

    我们通过/api/token作为JWTAuthHandler对象创建的参数来指定该URL将被忽略。的确,这个URL被用来生成新的JWT令牌:

    1.  
      apiRouter.get("/token").handler(context -> {
    2.  
       
    3.  
      JsonObject creds = new JsonObject()
    4.  
      .put("username", context.request().getHeader("login"))
    5.  
      .put("password", context.request().getHeader("password"));
    6.  
      auth.authenticate(creds, authResult -> { (1)
    7.  
       
    8.  
      if (authResult.succeeded()) {
    9.  
      User user = authResult.result();
    10.  
      user.isAuthorised("create", canCreate -> { (2)
    11.  
      user.isAuthorised("delete", canDelete -> {
    12.  
      user.isAuthorised("update", canUpdate -> {
    13.  
       
    14.  
      String token = jwtAuth.generateToken( (3)
    15.  
      new JsonObject()
    16.  
      .put("username", context.request().getHeader("login"))
    17.  
      .put("canCreate", canCreate.succeeded() && canCreate.result())
    18.  
      .put("canDelete", canDelete.succeeded() && canDelete.result())
    19.  
      .put("canUpdate", canUpdate.succeeded() && canUpdate.result()),
    20.  
      new JWTOptions()
    21.  
      .setSubject("Wiki API")
    22.  
      .setIssuer("Vert.x"));
    23.  
      context.response().putHeader("Content-Type", "text/plain").end(token);
    24.  
      });
    25.  
      });
    26.  
      });
    27.  
      } else {
    28.  
      context.fail(401);
    29.  
      }
    30.  
      });
    31.  
      });
    1. 我们预计登录名和密码信息已通过HTTP请求标头传递,我们使用上一节的Apache Shiro身份验证提供程序进行身份验证。

    2. 一旦成功,我们可以查询角色。

    3. 我们生成令牌usernamecanCreatecanDeletecanUpdate索赔。

    每个API处理程序方法现在可以查询当前的用户主体和声明。这是如何apiDeletePage做到的:

    1.  
      private void apiDeletePage(RoutingContext context) {
    2.  
      if (context.user().principal().getBoolean("canDelete", false)) {
    3.  
      int id = Integer.valueOf(context.request().getParam("id"));
    4.  
      dbService.deletePage(id, reply -> {
    5.  
      handleSimpleDbReply(context, reply);
    6.  
      });
    7.  
      } else {
    8.  
      context.fail(401);
    9.  
      }
    10.  
      }

    使用JWT令牌

    为了说明如何使用JWT令牌,让我们为root用户创建一个新的令牌:

    $ http --verbose --verify no GET https://localhost:8080/api/token login:root password:w00t
    GET /api/token HTTP/1.1
    Accept: */*
    Accept-Encoding: gzip, deflate
    Connection: keep-alive
    Host: localhost:8080
    User-Agent: HTTPie/0.9.8
    login: root
    password: w00t
    
    
    
    HTTP/1.1 200 OK
    Content-Length: 242
    Content-Type: text/plain
    Set-Cookie: vertx-web.session=8cbb38ac4ce96737bfe31cc0ceaae2b9; Path=/
    
    eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6InJvb3QiLCJjYW5DcmVhdGUiOnRydWUsImNhbkRlbGV0ZSI6dHJ1ZSwiY2FuVXBkYXRlIjp0cnVlLCJpYXQiOjE0ODk0NDE1OTAsImlzcyI6IlZlcnQueCIsInN1YiI6Ildpa2kgQVBJIn0=.RmtJb81QKVUFreXL-ajZ8ktLGasoKEqG8GSQncRWrN8=

    响应文本是令牌值并应保留。

    我们可以检查执行不带令牌的API请求会导致拒绝访问:

    $ http --verbose --verify no GET https://localhost:8080/api/pages
    GET /api/pages HTTP/1.1
    Accept: */*
    Accept-Encoding: gzip, deflate
    Connection: keep-alive
    Host: localhost:8080
    User-Agent: HTTPie/0.9.8
    
    
    
    HTTP/1.1 401 Unauthorized
    Content-Length: 12
    
    Unauthorized

    发送JWT令牌与请求一起使用AuthorizationHTTP请求头,其值必须是Bearer <token value>。以下是如何通过传递已发布给我们的JWT令牌来修复上面的API请求:

    $ http --verbose --verify no GET https://localhost:8080/api/pages 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6InJvb3QiLCJjYW5DcmVhdGUiOnRydWUsImNhbkRlbGV0ZSI6dHJ1ZSwiY2FuVXBkYXRlIjp0cnVlLCJpYXQiOjE0ODk0NDE1OTAsImlzcyI6IlZlcnQueCIsInN1YiI6Ildpa2kgQVBJIn0=.RmtJb81QKVUFreXL-ajZ8ktLGasoKEqG8GSQncRWrN8='
    GET /api/pages HTTP/1.1
    Accept: */*
    Accept-Encoding: gzip, deflate
    Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6InJvb3QiLCJjYW5DcmVhdGUiOnRydWUsImNhbkRlbGV0ZSI6dHJ1ZSwiY2FuVXBkYXRlIjp0cnVlLCJpYXQiOjE0ODk0NDE1OTAsImlzcyI6IlZlcnQueCIsInN1YiI6Ildpa2kgQVBJIn0=.RmtJb81QKVUFreXL-ajZ8ktLGasoKEqG8GSQncRWrN8=
    Connection: keep-alive
    Host: localhost:8080
    User-Agent: HTTPie/0.9.8
    
    
    
    HTTP/1.1 200 OK
    Content-Length: 99
    Content-Type: application/json
    Set-Cookie: vertx-web.session=0598697483371c7f3cb434fbe35f15e4; Path=/
    
    {
        "pages": [
            {
                "id": 0,
                "name": "Hello"
            },
            {
                "id": 1,
                "name": "Apple"
            },
            {
                "id": 2,
                "name": "Vert.x"
            }
        ],
        "success": true
    }

    调整API测试夹具

    ApiTest类需要进行更新,以支持JWT令牌。

    我们添加一个新字段来检索要在测试用例中使用的令牌值:

    private String jwtTokenHeaderValue;

    我们添加第一步来检索经过身份验证的JTW令牌foo

    1.  
      @Test
    2.  
      public void play_with_api(TestContext context) {
    3.  
      Async async = context.async();
    4.  
       
    5.  
      Future<String> tokenRequest = Future.future();
    6.  
      webClient.get("/api/token")
    7.  
      .putHeader("login", "foo") (1)
    8.  
      .putHeader("password", "bar")
    9.  
      .as(BodyCodec.string()) (2)
    10.  
      .send(ar -> {
    11.  
      if (ar.succeeded()) {
    12.  
      tokenRequest.complete(ar.result().body()); (3)
    13.  
      } else {
    14.  
      context.fail(ar.cause());
    15.  
      }
    16.  
      });
    17.  
      // (...)
    1. 凭证作为标题传递。

    2. 响应有效载荷是text/plain类型的,因此我们将其用于身体解码编解码器。

    3. 一旦成功,我们tokenRequest用令牌值完成未来。

    现在使用JWT令牌将其作为头传递给HTTP请求:

    1.  
      Future<JsonObject> postRequest = Future.future();
    2.  
      tokenRequest.compose(token -> {
    3.  
      jwtTokenHeaderValue = "Bearer " + token; (1)
    4.  
      webClient.post("/api/pages")
    5.  
      .putHeader("Authorization", jwtTokenHeaderValue) (2)
    6.  
      .as(BodyCodec.jsonObject())
    7.  
      .sendJsonObject(page, ar -> {
    8.  
      if (ar.succeeded()) {
    9.  
      HttpResponse<JsonObject> postResponse = ar.result();
    10.  
      postRequest.complete(postResponse.body());
    11.  
      } else {
    12.  
      context.fail(ar.cause());
    13.  
      }
    14.  
      });
    15.  
      }, postRequest);
    16.  
       
    17.  
      Future<JsonObject> getRequest = Future.future();
    18.  
      postRequest.compose(h -> {
    19.  
      webClient.get("/api/pages")
    20.  
      .putHeader("Authorization", jwtTokenHeaderValue)
    21.  
      .as(BodyCodec.jsonObject())
    22.  
      .send(ar -> {
    23.  
      if (ar.succeeded()) {
    24.  
      HttpResponse<JsonObject> getResponse = ar.result();
    25.  
      getRequest.complete(getResponse.body());
    26.  
      } else {
    27.  
      context.fail(ar.cause());
    28.  
      }
    29.  
      });
    30.  
      }, getRequest);
    31.  
      // (...)
    1. 我们将带有Bearer前缀的令牌存储在下一个请求的字段中。

    2. 我们将令牌作为头部传递。

  • 相关阅读:
    老王讲架构:负载均衡
    支付宝系统架构内部剖析
    Effective Java 第三版——61. 基本类型优于装箱的基本类型
    Effective Java 第三版——60. 需要精确的结果时避免使用float和double类型
    Effective Java 第三版——59. 熟悉并使用Java类库
    Effective Java 第三版——58. for-each循环优于传统for循环
    Effective Java 第三版——57. 最小化局部变量的作用域
    Effective Java 第三版——56. 为所有已公开的API元素编写文档注释
    Effective Java 第三版——55. 明智而审慎地返回Optional
    Effective Java 第三版——54. 返回空的数组或集合不要返回null
  • 原文地址:https://www.cnblogs.com/endv/p/11956473.html
Copyright © 2011-2022 走看看