Vert.x Java开发指南——第八章 安全和访问控制

Vert.x,认证与授权 - 基于JWT的身份认证 JSON Web Token(JWT)是一种用于安全传输(JSON格式)信息的开放标准(RFC7519),JWT常用于身份验证和信息交换,JWT通过数字签名确保信息的安全性和完整性。JWT可以使用对称密钥(HMAC算法),也可以使用非对称加密的密钥对(RSA或ECDSA)对要传递的信息进行签名和验证。推荐使用非对称加密算法进行签名,这种方式更安全(有利于保护密钥),非对称方式只有在生成JWT时候需要私钥加签,验证JWT时候只需要公钥。 阅读详情

第八章 安全和访问控制

版权声明:本文为博主自主翻译,转载请标明出处。 https://blog.youkuaiyun.com/elinespace/article/details/80663688
相应代码位于本指南仓库的step-7目录下

使用Vert.x,安全和访问控制非常容易实现。在本章中,我们将介绍:

  1. 迁移HTTP到HTTPS
  2. 添加用户认证以及基于分组的权限到Web应用中
  3. 使用JWT对Web API进行访问控制

8.1 在Vert.x中支持HTTPS

Vert.x为SSL加密网络链接提供了支持。在生产环境中暴露HTTP服务器比较常见的方式是通过前端HTTP服务器或代理如Nginx,前端服务器或者代理对于输入请求采用HTTPS。Vert.x还可以自己暴露HTTPS,以便提供端到端的加密。

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

keytool -genkey \
    -alias test \
    -keyalg RSA \
    -keystore server-keystore.jks \
    -keysize 2048 \
    -validity 360 \
    -dname CN=localhost \
    -keypass secret \
    -storepass secret

接下来我们可以修改HTTP服务器的创建,传递一个HttpServerOptions对象,以明确说明我们需要支持SSL,并且指明KeyStore文件:

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

我们可以将浏览器指向https://localhost:8080,由于证书是一个自包含证书,任一标准的浏览器都将理所当然地发出安全警告:

这里写图片描述

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

webClient = WebClient.create(vertx, new WebClientOptions()
        .setDefaultHost("localhost")
        .setDefaultPort(8080).setSsl(true) ①
        .setTrustOptions(new JksOptions().setPath("server-keystore.jks").setPassword("secret"))); ②

① 启用SSL

② 由于证书是自包含的,我们需要显式信任它,否则Web客户端链接将失败,正如一个Web浏览器一样。

8.2 访问控制与认证

Vert.x为认证和授权提供了各种选项。官方支持模块包括JDBC、MongoDB、Apache Shiro、知名提供商的OAuth2授权以及JWT(JSON Web 令牌)。

下一节涵盖JWT,本节集中使用Apache Shiro,当认证必须由LDAP或者活动目录服务器支持时,它非常有趣。在我们的例子中,我们将凭证简单的存储在一个properties文件中,以便确保尽可能简单,但是依靠一个LDAP服务器的API用法仍然相同。

本节目标是要求用户使用Wiki时进行认证,并且还有基于角色的许可:

  1. 没有角色只允许读页面
  2. 拥有writer角色允许编辑页面
  3. 拥有editor角色允许创建、编辑和删除页面
  4. 拥有admin角色相当于拥有所有可能的角色

由于Apache Shiro的内部工作机制,Vert.x Shiro集成有一些问题。它的某些部分是阻塞的,可能妨碍性能,并且某些数据使用本地线程状态存储。你不应该试图滥用公开的内部状态API。

8.2.1 添加Apache Shiro认证到路由

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

<dependency>
    <groupId>io.vertx</groupId>
    <artifactId>vertx-auth-shiro</artifactId>
</dependency>

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

user.root=w00t,admin
user.foo=bar,editor,writer
user.bar=baz,writer
user.baz=baz
role.admin=*
role.editor=create,delete,update
role.writer=update

user前缀的记录是用户账户,第一个值是密码,后面跟着的是角色。在这个示例中,用户bar密码为baz,是一个writer。一个writer拥有update许可。

回到HttpServerVerticle类代码,我们使用Apache Shiro创建一个认证提供器:

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

ShiroAuth对象实例接下来用于处理服务端用户会话:

Router router = Router.router(vertx);
router.route().handler(CookieHandler.create()); 
router.route().handler(BodyHandler.create()); 
router.route().handler(SessionHandler.create(LocalSessionStore.create(vertx)));
router.route().handler(UserSessionHandler.create(auth)); ①
AuthHandler authHandler = RedirectAuthHandler.create(auth, "/login"); ② 
router.route("/").handler(authHandler); ③ 
router.route("/wiki/*").handler(authHandler); 
router.route("/action/*").handler(authHandler);
router.get("/").handler(this::indexHandler); 
router.get("/wiki/:page").handler(this::pageRenderingHandler); 
router.post("/action/save").handler(this::pageUpdateHandler); 
router.post("/action/create").handler(this::pageCreateHandler); 
router.get("/action/backup").handler(this::backupHandler); 
router.post("/action/delete").handler(this::pageDeletionHandler);

① 我们为所有路由安装了一个用户会话处理器。

② 在请求没有用户会话时,自动重定向请求到/login。

③ 我们为需要认证的所有路由安装authHandler。

最后,我们需要创建3个路由,用于显示登录表单、登录表单提交处理和用户注销:

router.get("/login").handler(this::loginHandler); 
router.post("/login-auth").handler(FormLoginHandler.create(auth)); ①
router.get("/logout").handler(context -> { context.clearUser(); ② 
    context.response().setStatusCode(302).putHeader("Location", "/").end();
});

① FormLoginHander是一个登录提交请求处理的助手。默认情况下,它期望HTTP POST请求包含:username作为登录用户,password作为登录密码,return_url作为成功时的重定向URL。

② 注销用户只需要简单的从当前RoutingContext中清除它即可。

loginHandler方法的代码如下:

private void loginHandler(RoutingContext context) { 
    context.put("title", "Login");
    templateEngine.render(context, "templates", "/login.ftl", ar -> {
        if (ar.succeeded()) { 
            context.response().putHeader("Content-Type", "text/html");
            context.response().end(ar.result());
        }else{
            context.fail(ar.cause());
        }
    }); 
}

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

<#include "header.ftl"> 
<div class="row">
    <div class="col-md-12 mt-1">
        <form action="/login-auth" method="POST">
            <div class="form-group">
                <input type="text" name="username" placeholder="login"> 
                <input type="password" name="password" placeholder="password"> 
                <input type="hidden" name="return_url" value="/">
                <button type="submit" class="btn btn-primary">Login</button>
            </div>
        </form>
    </div>
</div>
<#include "footer.ftl">

登录页面看起来如下:

这里写图片描述

8.2.2 基于角色的特征支持

只有当用户具有足够的权限时,才可以激活这些特征。在下面的截图中,一个管理员可以创建一个页面并执行一个备份:

这里写图片描述

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

这里写图片描述

为了实现该功能,我们可以访问RoutingContext中的用户引用,查询许可。以下是indexHandler处理器方法的实现:

private void indexHandler(RoutingContext context) { 
    context.user().isAuthorised("create", res -> { ①
        boolean canCreatePage = res.succeeded() && res.result(); ② 
        dbService.fetchAllPages(reply -> {
            if (reply.succeeded()) {
                context.put("title", "Wiki home");
                context.put("pages", reply.result().getList());
                context.put("canCreatePage", canCreatePage); ③
                context.put("username", context.user().principal().getString("username")); ④
                templateEngine.render(context, "templates", "/index.ftl", ar -> {
                    if (ar.succeeded()) { 
                        context.response().putHeader("Content-Type", "text/html");
                        context.response().end(ar.result());
                    }else{
                        context.fail(ar.cause());
                    }
                }); 
            }else{
                context.fail(reply.cause());
            }
        }); 
    });
}

① 此处进行许可查询,注意这是一个异步操作。

② 我们使用结果来……

③ ……在HTML模板中使用它。

④ 我们还需要访问用户登录信息。

模板代码已被修改为基于canCreatePage来判断只渲染某一片段:

<#include "header.ftl"> 
<div class="row">
    <div class="col-md-12 mt-1">
        <#if context.canCreatePage>
            <div class="float-xs-right">
                <form class="form-inline" action="/action/create" method="post">
                    <div class="form-group">
                        <input type="text" class="form-control" id="name" name="name" placeholder="New page name">
                    </div>
                    <button type="submit" class="btn btn-primary">Create</button>
                </form>
            </div>
        </#if>
        <h1 class="display-4">${context.title}</h1> 
        <div class="float-xs-right">
            <a class="btn btn-outline-danger" href="/logout" role="button" aria-pressed="true">Logout (${context.username})</a> 
        </div>
    </div>
    <div class="col-md-12 mt-1"> 
        <#list context.pages>
            <h2>Pages:</h2> 
            <ul>
                <#items as page>
                <li><a href="/wiki/${page}">${page}</a></li>
                </#items>
            </ul>
        <#else>
            <p>The wiki is currently empty!</p>
        </#list>
        <#if context.canCreatePage>
            <#if context.backup_gist_url?has_content>
                <div class="alert alert-success" role="alert">
                    Successfully created a backup:
                    <a href="${context.backup_gist_url}" class="alert-link">${context.backup_gist_url}</a>
                </div>
            <#else> 
                <p>
                    <a class="btn btn-outline-secondary btn-sm" href="/action/backup" role="button" aria-pressed="true">Backup</a>
                </p>
            </#if> 
        </#if>
    </div> 
</div>
<#include "footer.ftl">

使更新页面和删除页面受限于特定角色的代码也是类似的,可以从本指南的Git仓库中获取。

同样确保在HTTP POST请求处理器中检查许可而不止当渲染页面时检查是非常重要的。实际上,恶意攻击者仍然可以在不进行身份验证的情况下处理请求并执行操作。以下通过包装pageDeletionHandler代码到一个高层级的许可检查中以保护页面删除操作:

private void pageDeletionHandler(RoutingContext context) { 
    context.user().isAuthorised("delete", res -> {
        if (res.succeeded() && res.result()) {
            // Original code:
            dbService.deletePage(Integer.valueOf(context.request().getParam("id")), reply -> {
                if (reply.succeeded()) {
                    context.response().setStatusCode(303);
                    context.response().putHeader("Location", "/");
                    context.response().end();
                }else{
                    context.fail(reply.cause());
                }
            });
        }else{
            context.response().setStatusCode(403).end();
        }
    }); 
}

8.3 使用JWT进行Web API认证

JSON Web令牌(RFC 7519)是一项用户发出基于JSON编码令牌(包含claims)的标准,通常用于识别用户和许可,虽然claims可以是任何东西。

令牌由服务器发出并且使用服务器密钥签名。客户端可以与随后的请求一起发回一个令牌:客户端和服务器均可以检查令牌是可信的且未被改变。

当JWT令牌签名时,它的内容未被加密。它必须通过一个安全的通道传输(如HTTPS),并且永远不要将敏感信息作为claim(如密码、私有API密钥等)。

8.3.1 添加JWT支持

我们开始需要添加vertx-auth-jwt模块到Maven依赖:

<dependency>
    <groupId>io.vertx</groupId> 
    <artifactId>vertx-auth-jwt</artifactId>
</dependency>

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

keytool -genseckey -keystore keystore.jceks -storetype jceks -storepass secret -keyalg HMacSHA256 -keysize 2048 -alias HS256 -keypass secret
keytool -genseckey -keystore keystore.jceks -storetype jceks -storepass secret -keyalg HMacSHA384 -keysize 2048 -alias HS384 -keypass secret
keytool -genseckey -keystore keystore.jceks -storetype jceks -storepass secret -keyalg HMacSHA512 -keysize 2048 -alias HS512 -keypass secret
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
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
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
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
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
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令牌处理器:

Router apiRouter = Router.router(vertx);
JWTAuth jwtAuth = JWTAuth.create(vertx, new JsonObject().put("keyStore", new JsonObject()
      .put("path", "keystore.jceks")
      .put("type", "jceks")
      .put("password", "secret")));
apiRouter.route().handler(JWTAuthHandler.create(jwtAuth, "/api/token"));

我们传递/api/token作为JWTAuthHandler对象创建的一个参数,以明确指定忽略这个URL。实际上,这个URL用于生成新的JWT令牌:

apiRouter.get("/token").handler(context -> {
    JsonObject creds = new JsonObject()
        .put("username", context.request().getHeader("login"))
        .put("password", context.request().getHeader("password"));
    auth.authenticate(creds, authResult -> { ①
        if (authResult.succeeded()) {
            User user = authResult.result(); 
            user.isAuthorised("create", canCreate -> { ②
            user.isAuthorised("delete", canDelete -> {
                    user.isAuthorised("update", canUpdate -> {
                        String token = jwtAuth.generateToken( ③
                            new JsonObject()
                            .put("username", context.request().getHeader("login"))
                            .put("canCreate", canCreate.succeeded() && canCreate.result())
                            .put("canDelete", canDelete.succeeded() && canDelete.result())
                            .put("canUpdate", canUpdate.succeeded() && canUpdate.result()),
                        new JWTOptions().setSubject("Wiki API").setIssuer("Vert.x"));
                        context.response().putHeader("Content-Type", "text/plain").end(token);
                    });
                }); 
            });
        }else{
            context.fail(401); 
        }
    }); 
});

① 我们希望登录和密码信息依旧通过HTTP请求头传递,我们使用上一节的Apache Shiro认证提供器进行认证。

② 成功时我们可以查询角色。

③ 我们生成一个令牌包含username、canCreate、canDelete和canUpdate claim。

每个API处理器方法现在可以查询当前用户信息以及claim。以下是apiDeletePage的处理方式:

private void apiDeletePage(RoutingContext context) {
    if (context.user().principal().getBoolean("canDelete", false)) {
        int id = Integer.valueOf(context.request().getParam("id")); 
        dbService.deletePage(id, reply -> {
            handleSimpleDbReply(context, reply);
        });
    }else{
        context.fail(401); 
    }
}

8.3.2 使用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.eyJ1c2VybmFtZSI6InJvb3QiLCJjYW5DcmVhdGUiOnRydWUsImNhbkRlbGV0ZSI6dHJ1ZSwiY2FuVXBkYXRl Ijp0cnVlLCJpYXQiOjE0ODk0NDE1OTAsImlzcyI6IlZlcnQueCIsInN1YiI6Ildpa2kgQVBJIn0=.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

通过使用Authorization HTTP请求头,与请求一起发送一个JWT令牌,它的值必须是Bearer <令牌值>。以下是如何修正上面的API请求,通过传递已经颁发给我们的JWT令牌:

http --verbose --verify no GET https://localhost:8080/api/pages 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6InJvb3QiLCJjYW5DcmVhdGUiOnRydWUsImNhbkRlbGV0ZSI6dHJ1ZSwiY2FuVXBkYXRl Ijp0cnVlLCJpYXQiOjE0ODk0NDE1OTAsImlzcyI6IlZlcnQueCIsInN1YiI6Ildpa2kgQVBJIn0=.RmtJb81QKVUFreXL- ajZ8ktLGasoKEqG8GSQncRWrN8='
GET /api/pages HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6InJvb3QiLCJjYW5DcmVhdGUiOnRydWUsImNhbkRlbGV0ZSI6dHJ1ZSwiY2FuVXBkYXRl Ijp0cnVlLCJpYXQiOjE0ODk0NDE1OTAsImlzcyI6IlZlcnQueCIsInN1YiI6Ildpa2kgQVBJIn0=.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 
}

8.3.3 适配API测试装备

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

我们添加一个属性用于获取测试场景使用的令牌值:

private String jwtTokenHeaderValue;

我们添加第一步来为用户foo获取一个JWT令牌认证:

@Test
public void play_with_api(TestContext context) { 
    Async async = context.async();
    Future<String> tokenRequest = Future.future();
    webClient.get("/api/token")
        .putHeader("login", "foo") ①
        .putHeader("password", "bar") 
        .as(BodyCodec.string()) ②
        .send(ar -> {
            if (ar.succeeded()) { 
                tokenRequest.complete(ar.result().body()); ③
            }else{
                context.fail(ar.cause());
            }
        });
    // (...)

① 凭证作为请求头传递。

② 响应载荷是text/plain类型,因此我们使用BodyCodec进行响应体解码。

③ 成功时,我们使用token值完成tokenRequest的future。

现在,使用JWT令牌只需要将它作为报头传递给HTTP请求:

Future<JsonObject> postRequest = Future.future();
tokenRequest.compose(token -> {
    jwtTokenHeaderValue = "Bearer " + token; ①
    webClient.post("/api/pages")
        .putHeader("Authorization", jwtTokenHeaderValue) ②
        .as(BodyCodec.jsonObject())
        .sendJsonObject(page, ar -> {
            if (ar.succeeded()) {
                HttpResponse<JsonObject> postResponse = ar.result();                
                postRequest.complete(postResponse.body());
            }else{
                context.fail(ar.cause());
            }
      });
    }, postRequest);
Future<JsonObject> getRequest = Future.future();
postRequest.compose(h -> {
    webClient.get("/api/pages")
        .putHeader("Authorization", jwtTokenHeaderValue)
        .as(BodyCodec.jsonObject())
        .send(ar -> {
            if (ar.succeeded()) {
                HttpResponse<JsonObject> getResponse = ar.result();                 
                getRequest.complete(getResponse.body());
            }else{
                context.fail(ar.cause());
          }
      });
}, getRequest);
// (...)

① 我们存储令牌及Bearer前缀到这个属性,以便用于下次请求。
② 我们传递令牌作为头信息。

【LOGO动态替换】记录一次关于LOGO动态替换引发的uboot代码分析及其实现 文章目录1.前言2.实现2.1 新增logo分区2.2 制作logo分区镜像2.2.1 查看logo.bmp和logo_kernel.bmp的文件大小。2.2.2 按规则将bmp写入logo.img3.分析3.1 uboot的启动分析3.2 uboot bmp load 显示部分3.2.1 rockchip_show_logo3.2.2 load_bmp_logo3.2.3 load_bmp_logo3.3 uboot bmp read部分3.3.1 read_logo_bmps3.3.1 read_bmp 阅读详情

相关推荐

【论文阅读】High-Resolution Image Synthesis with Latent Diffusion Models

为了降低训练扩散模型的算力,LDMs使用一个Autoencoder去学习能尽量表达原始image space的低维空间表达(latent embedding),大大减少需要的算力。在RGB空间:编码器encoder:,将x压缩成低维表示解码器decoder: D,将低维表示z还原成原始图像空间。1. 加入Autoencoder(上图中左侧红色部分),使得扩散过程在latent space下,提高图像生成的效率;

weixin_57974242的博客 1万+

Vertx3 Cookie操作 官网Demo Java版

package io.vertx.example.web.cookie; import io.vertx.core.AbstractVerticle; import io.vertx.example.util.Runner; import io.vertx.ext.web.Cookie; import io.vertx.ext.web.Router; import io.vert...

Chung 473

物联网定位技术|实验报告|实验一 Wi-Fi指纹定位

全球定位系统是迄今为止应用最为成功的地位系统,具备高精度的定位能力,但在室内和高楼密布的城区等信号遮挡严重的密集环境中,由于接受机捕获不到足够强度的卫星信号,GPS的定位质量难以保证甚至无法完成定位,使其可用性受到严重制约。由于室内环境下都普及了WiFi,因此利用WiFi进行定位无需额外部署硬件设备,是一个非常节省成本的方法。

m0_57656758的博客 3356

Vert.x 关于权限授权和认证(基于JWT)

vertx也用了有一段时间,起初对其使用都基于固有的模块在用,官网的教程也比较详细,虽然使用过程中有些问题还需要自己研究尝试,整体来说还算顺利,但是最近在使用其授权、认证的模块,官方的文档还是保持着原有的言简意赅,有些东西看了实例代码还是一头雾水,尤其是权限认证的方式,特将研究的内容发出来,一来可以共享信息,方便大家斧正,二来也做为记录 package zzy.verticles; import io.vertx.core.*; import io.vertx.core.buffer.Buffer;.

yushan126的专栏 1553

pagerank算法详解

目录一、pagerank定义两个重要假设二、pagerank算法公式定义计算演示矩阵化计算三、存在的两个问题问题1.Dead Ends问题2.Spider Traps 一、pagerank定义 入链数:指向该节点的链接数 出链数:由该节点指出的链接数 以上图为例:A的入链数为1,出链数为3,所以将由A指向其他节点的边权重设置为1/3,表示A访问B、C、D节点的概率均为1/3 两个重要假设 数量假设:在Web图模型中,如果一个页面节点接收到的其他网页指向的入链数量越多,那么这个页面越重要。 质量假设:指向

gary101818的博客 4万+

vrep和matlab,使用Matlab与V-REP联合仿真 - Play V-REP with Matlab

OS: Win10 x64Matlab: R2016bV-REP: V-REP PRO EDU v3.5.0 下载、安装与支持V-REP操作基础V-REP仿真基础Matlab环境准备一个简单的例子对V-REP有一定了解的读者可以直接跳到第三节Matlab环境准备。下载、安装与支持V-REP提供Windows、Linux和MacOS三个平台的应用程序支持,任意平台都提供教育版和专业版两个版本。教育版...

weixin_42660494的博客 3211

Vert.x Web模块(二)

 上下文数据 可以用RoutingContext维护和使用上下文数据,此数据将在请求生命周期的所有处理器之间共享。这是一个例子描述了,在某个处理器里向上下文中设置了数据,在接下来的处理器中接收数据。可用RoutingContext中的put方法添加一些对象,get方法获取一些对象。发送到/some/path/other路径的请求将匹配两个路由。 router.get("/so

JflyDragon的博客 3991

微信小程序:errno: 1500104, errCode: 10008, errMsg: “writeBLECharacteristicValue:fail:system error, status

微信小程序errno: 1500104, errCode: 10008, errMsg: "writeBLECharacteristicValue:fail:system error, status: UNKNOWN (239)"微信小程序调用蓝牙API,wx.writeBLECharacteristicValue()报错,微信小程序蓝牙写入错误,在开发uniapp微信小程序蓝牙通信功能时报错,蓝牙开发设备控制核心api,微信小程序调用蓝牙API “wx.writeBLECharacteristicValu

✅ 精选推荐文章 3930

Vert.x-Web的讲解和使用(二)

接上章《Vert.x-Web的讲解和使用(一)》 错误处理程序 你不但可以设置处理程序去处理请求,同样你也可以设置一个错误处理程序在你的Router中。 错误处理程序被使用在精确的有精确的匹配规则的普通Router上。 例如:你可以提供一个错误处理程序在你认为有可能发生错误的请求路径或者Http方法上。 这一点允许你在应用程序的不同部分设置不同的错误处理程序。 这里有一个例子:当路

ShareBoy 1万+

vue3 - 最新详细实现高德地图绘制动态热力图详细教程,在某区域或城市地图上做“热力图层“展示人流量、旅游人口密度、热量图、全国地区地理热力图,利用颜色的深浅表示密度或强度等场景(详细示例代码教程)

vue3,nuxt3,高德地图,热力图,绘制热力图层,人流量,vue3使用高德地图实现某个区域热力图,vue3高德地图api加载热力图及关闭,接收热力图数据并渲染热力图,关掉热力图,Vue使用高德API制作热力图,热力图加载不出来,电子围栏围住热力图,vue3中使用高德地图渲染热力图组件,高德地图使用热力图详细教程,报错,卡顿,出不来怎么办,无法关闭,怎么解决这个问题,出错,城市名称,城市某个街道,城区,省份,市区,县城,乡镇,村,各省份的热力图,交通流量,人口流量,密度,旅游出行,人口数量,颜色介绍,深浅

✅ 精选推荐文章 5205

地平线旭日X3开发板--使用Opencv显示CSI摄像头例程

但是从libsrcampy获取的摄像头图像格式是NV12,无法直接在opencv上显示或者处理,调试过程发现旭日派无法直接用opencv的VideoCapture()调用CSI摄像头,需要使用opencv来做开发的小伙伴可以做个参考,效果如下图所示。需要使用libsrcampy库来获取CSI摄像头图像,为了能够使用opencv的API来处理图像,需要增加NV12转化成bgr像素格式的函数。opencv显示图像.png。

bingdund的博客 541

Vert.x(vertx) 认证和授权详解(包含认证和授权在Web系统中的使用)

每个线上系统几乎都是离不开认证和授权的,Vert.x提供了灵活、简单、便捷的认证和授权的支持。Vert.x抽象出了两个核心的认证和授权的接口,一个是AuthProvider,另一个是User。通过这两个接口,我们可以非常灵活的实现我们自定义的认证和授权方法。当然,Vert.x也给我们提供了使用JDBC、Shiro、MongoDB、JWT等授权的实现,我们可以直接使用。 Vert.x提供的认证和...

happyfly的博客 1万+

SAP Joule Studio 能不能用来开发 skill:可以,但它开发的是 Joule 语境下的企业级 skill,而不是替代所有 SAP 开发技术的万能锤

SAP Joule Studio 专为开发Joule技能(Joule skills)而设计,这些技能是执行确定性单步业务动作的原子能力。它通过自然语言接口连接SAP和非SAP系统,适合处理边界清晰、可审计的任务(如创建工单、查询发票)。Joule技能作为会话式业务编排层,与ABAP、CAP等核心技术互补而非替代。开发支持低代码和专业代码两种方式,并具备企业级治理能力,但需要相应的BTP订阅和技术基础。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 201

mysql数据库知识

mysql数据库知识速查 参考的网址: 数据库设计设计规范 https://mp.weixin.qq.com/s?__biz=MzU4ODI1MjA3NQ==&mid=2247490967&idx=2&sn=fc92dc46ad2b6b2c2964c06f6277c4f7&chksm=fddecb53caa94245107a004935add8c0ccb059830f61d31ef404d172c3b07f87c0f576aa9a62&mpshare=1&sc

java技术博客 3798

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

StringGrid实现类似MSSQL2005修改表的功能

类似MSSQL创建表差不多的功能,就是允许输入一行,一旦输入某一行后,自动添加下一个空白行。同时支持选中行删除。这是困扰我两天的难题,最后还是到论坛发帖求助,求一解决方案unit gridtest;interfaceuses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,

夜色浮尘的专栏 890

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

利用POI导出Excel表格

一、Apache POI         Apache POI是免费开源的、具有能对Microsoft Office格式文件进行读和写功能的Java API。它支持对Microsoft Office各种格式文件的读写,但在实际应用中,我们最常使用的是利用POI来导入导出Excel表格。         所以这篇文章就主要讲,如何使用POI导出Excel表格,以及动态数据的导出。 二、导入必须...

周末未至 631

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

GaussDB(DWS)查询优化技术大揭秘

大数据时代,数据量呈爆发式增长,经常面临百亿、千亿数据查询场景,当数据仓库数据量较大、SQL语句执行效率低时,数据仓库性能会受到影响。本文将深入讲解在GaussDB(DWS)中如何进行表结构设计,如何进行SQL优化,如何查找慢SQL和高频SQL,提高数据仓库的性能和响应速度。

正在自我救赎的菜鸟。本人所有内容均不收费,可随意转发,复制 2518

杨辉三角计算机中的应用,杨辉三角

杨辉三角,是二项式系数在三角形中的一种几何排列,中国南宋数学家杨辉1261年所著的《详解九章算法》一书中出现。在欧洲,帕斯卡(1623----1662)在1654年发现这一规律,所以这个表又叫做帕斯卡三角形。帕斯卡的发现比杨辉要迟393年,比贾宪迟600年。[1]杨辉三角是中国数学史上的一个伟大成就。[1]中文名杨辉三角外文名Pascal's Triangle别称贾宪三角形、帕斯卡三角形表...

weixin_29982199的博客 3560

Vert.x 用JWT验证Web请求 译<十>

2019独角兽企业重金招聘Python工程师标准>>> ...

weixin_34029680的博客 532

数据资源 | 八大板块!数据公开下载渠道(下)

数据公开下载渠道

weixin_55633225的博客 1万+

SSTI模板注入

SSTI模板注入 一些比较好的文章: https://www.cnblogs.com/20175211lyz/p/11425368.html https://bbs.ichunqiu.com/thread-47685-1-1.html?from=aqzx8 什么是SSTI SSTI就是服务器端模板注入(Server-Side Template Injection),也给出了一个注入的概念。 常见的注入有:SQL 注入,XSS 注入,XPATH 注入,XML 注入,代码注入,命令注入等等。sql注入已经出

阿刁〇的博客 697

Vert.x 安全和访问控制 译<九>

2019独角兽企业重金招聘Python工程师标准>>> ...

weixin_34417814的博客 288

如何理解Vertx中的Handler为异步执行

刚接触Vertx时,对于函数、响应式编程很是头疼。由其对其中的Handler为异步调用更是不解,翻看Handler源码,其实就是一个接口,方便用于lambda表达式。Handler源码如下: @FunctionalInterface public interface Handler&lt;E&gt; { /** * Something has happened, so handle i...

lee_star1的博客 3836

vert.x web client发送http https请求

vert.x web client是一个异步的http客户端,可以很容易的发送异步请求,什么是异步呢?简单举例,同步的http请求,如果服务器没有响应就要一直等着.....异步就是还可以干别的。web client还是很好用的而且提供了很多高级功能。 首先引用gradle dependencies { compile 'io.vertx:vertx-web-client:3.9.2' } 创建一个web client客户端 WebClient client = WebClient.create

urnotsupercorn 1419

Vertx session 使用须知

要使用vertx的session,必须把sessionHandler设为router的第一个匹配的route,否则将报错。比如下面的代码将引起报错:public class LeanS1 extends AbstractVerticle {   @Override   public void start() throws Exception {     Router router = Rou...

weixin_33859504的博客 594

java cookie类_Java 的 Cookie 处理类详解

import java.io.IOException;import java.net.CookieHandler;import java.net.URI;import java.net.URL;import java.net.URLConnection;import java.text.DateFormat;import java.text.ParseException;import java.t...

weixin_28811757的博客 341
上一篇: Vert.x Java开发指南——第七章 公开Web API
下一篇: Vert.x Java开发指南——第九章 利用RxJava进行响应式编程
边城白衣
博客等级 码龄14年 106粉丝 10原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值