前面已经学习了Vert.x web的基础接口,本文主要讲解引入jwt为接口认证/鉴权。引用之前创建的项目《Vert.x 4 Web应用初识》,加入jwt token相关依赖 io.vertx:vertx-auth-jwt,开始了Vert.x中的Jwt认证之旅。
maven pom.xml 依赖节点引入jwt相关依赖
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-auth-jwt</artifactId>
<version>4.5.9</version>
</dependency>
生成rsa密钥对参考 《OpenSSL 工具生成RSA密钥对》这里只需要最简单不加密的RSA密钥对就行。生成好以后复制到项目中,位置参考下面项目结构图
在MainVerticle,start中创建jwt的认证提供者
代码聚焦
//采用RSA256非对称加密
Buffer privateKeyBuf = vertx.fileSystem().readFileBlocking("certs/rsa_private_2048.pem");//相对于resources目录
Buffer publicKeyBuf = vertx.fileSystem().readFileBlocking("certs/rsa_public_2048.pem");
JWTAuth jwtAuth = JWTAuth.create(vertx,
new JWTAuthOptions()
.addPubSecKey(
new PubSecKeyOptions()
.setAlgorithm("RS256")
.setBuffer(publicKeyBuf)
)
.addPubSecKey(
new PubSecKeyOptions()
.setAlgorithm("RS256")
.setBuffer(privateKeyBuf)
)
);
注意点:
模拟用户输入用户名和密码登录,成功返回toen字符串
代码聚焦
router.post("/login").handler(ctx->{
String username = ctx.request().getParam("username");
String password = ctx.request().getParam("password");
if ("admin".equals(username) && "admin".equals(password)) {
//初始化几个权限和角色数据(实际应用这里应该调用数据库查询对应用户的角色权限)
List<String> roles = new ArrayList<>();
roles.add("admin");
roles.add("user");
List<String> permissions = new ArrayList<>();
permissions.add("list_products");
ctx.response()
.end(jwtAuth.generateToken(
new JsonObject()
.put("permissions", permissions)
.put("roles",roles)
.put("username", username)
.put("someKey", "someValue")
,
new JWTOptions()
.setIgnoreExpiration(false)
.setExpiresInMinutes(30)
.setAlgorithm("RS256") //默认值是HS256,所有必须指定算法RS256
)
);
}else {
ctx.fail(401);
}
});
以上代码实现了登录业务,包含
使用postman工具测试
以上操图作可以看到,与我们预期结果一致。
代码聚焦
//创建token后则访问 需要认证(登录)的资源
AuthenticationHandler authenticationHandler = JWTAuthHandler.create(jwtAuth);
router.route("/protected/*").handler(authenticationHandler);
router.route("/protected/info").handler(ctx ->{
ctx.response()
.putHeader("content-type", "text/plain")
.end("protected info !");
});
//token 请求时候放入头部 Authorization Bearer <token>
此处jwtAuth 就是上面创建的jwtAuth RSA
上方代码表示访问/protected/
开头的接口都需要认证才行否则返回401
使用postman工具测试
首先是直接访问接口/protected/info
直接访问需要认证的接口,可以看到上方返回401状态码并返回未认证/未登录,与预期结果一致。
接下来设置token,使用认证的token来访问接口。
首先调用登录生成token,然后复制到下图的token位置
再次请求
从上图可以看到携带token再次请求接口成功获得了响应内容,与预期结果一致。
代码聚焦
//意思是从jwt token的obj中拿出permissions节点进行权限判断
AuthorizationProvider authorizationProvider = JWTAuthorization.create("permissions");
//[/protected/list_products]开头的路径需要list_products权限才能访问,从authorizationProvider表示jwt中拿出permission节点进行和list_products对比
router.route("/protected/list_products*").handler(
AuthorizationHandler.create(PermissionBasedAuthorization.create("list_products"))
.addAuthorizationProvider(authorizationProvider)
);
router.route("/protected/list_products").handler(ctx->{
ctx.response()
.putHeader("content-type", "text/plain")
.end("list_products Go !");
});
//指定地址指定权限
router.route("/protected/list_products/one").handler(
AuthorizationHandler.create(PermissionBasedAuthorization.create("one_products"))
.addAuthorizationProvider(authorizationProvider)
).handler(ctx->{
ctx.response()
.putHeader("content-type", "text/plain")
.end("one_products Go !");
});
上方代码指定接口权限对应
/protected/list_products*
开头需要list_products
权限/protected/list_products
需要list_products
权限/protected/list_products/one
需要list_products
和one_products
权限使用postman工具测试验证
注意:需要授权的接口是肯定需要认证的,所以以下接口测试都将默认携带上面生成的token
访问list_products接口成功,与预期结果一致 (token中包含list_products
权限,有疑问可以返回查看上面的token生成部分)
访问one接口,从上图可以看到403了,与预期结果一致(token中没有包含one_products权限,有疑问可以返回查看上面的token生成部分)
这里指定角色代码授权访问其实和权限相差不大,就是从token取值的节点变成了另外一个(同时节点名称也是可以自定义的)
代码聚焦
//意思是从jwt token的obj中拿出roles节点进行权限判断
AuthorizationProvider authorizationProviderRoles = JWTAuthorization.create("roles");
//指定路径需要admin role 才能访问
router.route("/protected/setting/get").handler(
AuthorizationHandler.create(PermissionBasedAuthorization.create("admin"))
.addAuthorizationProvider(authorizationProviderRoles)
).handler(ctx->{
ctx.response()
.putHeader("content-type", "text/plain")
.end("Get setting !");
});
以上代码表示需要admin的角色才能访问该接口,我们上面初始化的token是包含admin的所以可以直接访问,参考下方请求图
[user]
rsa_private_key.pem
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCcB6xtxRGn+gyN
t8qxjRIbVke/vQuO9Gr3AoAT3t8Ja7Ve489PJ/4r/KWtyDGrPcU+gWqjzaNM1Gph
w4ry2RZXQ9Dq92aG4F+hwxhi++ZgsHKNAukVEQdcWb+16idmBhm4/Ro8qoJZP+Je
vCCKpgovcKega+wcKBtBjSnQfxeDJ2A0VDU8js6KVXBiR+8WAyNtKjDHSnUe4z+Z
tJWOmu1yA89J0x3Y2owcejW9BjOoIBFszdRw1alP6EQn5tDB/nCiTxje7k11qBpf
wc5WOuRHOyakq0tsite+x2wvdjNnyN3LzbAhXIpMJDSFQX5KHzSk8qfV2tuHit27
XUTeug0nAgMBAAECggEABNBfy4s2/j3CwWSYRl4Z3uoMkKfegWydGD3lgbdwnjPK
hsD0lnafavAClJgGoEfpnAIWLjOcBDTRi26jDNcRz2NyQK2dZi/qA22nhPZMp1MK
VbtvQY0i8wB537z3tjgd9w0oEBMarnJAI7geMOjqi4goQ9TQlhXOsXkPfzVMnmk+
QazzmKKs4rQNlu+6Usjfg3LfvvXLL2FMrcU/N69ajEH8POLEGczEYNPtgotPffbO
z+t9ScFxsD0tFHIjA+zNDKixcec8/Q/n5gV5V1o+3axKobWAAzaixm95TiGXGxdI
quNjvtn8fTn6dHujZoWr/pj4NpvyxdWse5xiNu500QKBgQDOtw28pwi0EtrUU1Dd
A36FUlkrceeOZohv38WYu1liIeV5K22t83C4f4K8zq2KmKz6QTvp+oBiB6yJfZLi
EeJ8WTMbHOg7dHhfjWqJ/Jpkg9IpiyH4iCK2m/EWf7KafAxNImR+umZP+0n2twUl
SmuFbNwfKtq/u8Tv6QmngKwkHQKBgQDBOwiGkCwFBaqu7J1F6pyinzLdF+nYpQyP
hwv7cO+59CJTORwiFm1ZXJyGi30eY/w8DCIYY8F4svr+EXxDACX1nrp9jkgL47NH
nkIT4afQjJG3uAg4Nt9lHXz7l5hk7PQMtxPHhydvVl0xl8hj3eN6Aomk1AAv09HK
NXogHOSrEwKBgQCkFID+4cbyyJSSPJ/PDtr6kGbfKUaXraNWydRaazuDvUwcZfBl
RvqOOAhaPeNaQ93ptqYMDx6gsV6us9JHR9LyyQrb1pIvvz9c+S874Bnc9xV2jE2m
rMiBEj7HkQz/ur846rfCL8rOabRH9PZMp0m5WrNOugFwd2bW168mGeiJsQKBgAxH
uSn8HaAQFSHazb/0whGftnbQnz7ydlLkzUEkk0epGUlatsv/yuFD/nqagNAeoJgc
WUpdhJ0sGsFs0Q3dA4yRkt6J3VBMH0es6hwjWivp0xTu8C7KZfYiIqBGqRu452Wr
eOlUUJBF19RgBg86ucs2QBMmmgBwIMQOE/3YeDvbAoGAPibHuFlh7+7RKVEBa3uD
6hGWSEmnwxytS1x8bw24qYfzXPWipRWX7Hdtl+VILt7CqCyzXLLxmKkkFzigSC7n
P0FgUdw2GfgQlaNa47jq4TADBH9HeFbl+Qcb+qLvi8T6hPatOwGQXvBOrYHgQ20I
UBa6KgKMNA4DjwcIomSXyUk=
-----END PRIVATE KEY-----
rsa_public_key.pem
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnAesbcURp/oMjbfKsY0S
G1ZHv70LjvRq9wKAE97fCWu1XuPPTyf+K/ylrcgxqz3FPoFqo82jTNRqYcOK8tkW
V0PQ6vdmhuBfocMYYvvmYLByjQLpFREHXFm/teonZgYZuP0aPKqCWT/iXrwgiqYK
L3CnoGvsHCgbQY0p0H8XgydgNFQ1PI7OilVwYkfvFgMjbSowx0p1HuM/mbSVjprt
cgPPSdMd2NqMHHo1vQYzqCARbM3UcNWpT+hEJ+bQwf5wok8Y3u5NdagaX8HOVjrk
RzsmpKtLbIrXvsdsL3YzZ8jdy82wIVyKTCQ0hUF+Sh80pPKn1drbh4rdu11E3roN
JwIDAQAB
-----END PUBLIC KEY-----
MainVerticle.java
@Slf4j
public class MainVerticle extends AbstractVerticle {
@Override
public void start(Promise<Void> startPromise) throws Exception {
Router router = Router.router(vertx);
//采用RSA256非对称加密
Buffer privateKeyBuf = vertx.fileSystem().readFileBlocking("certs/rsa_private_2048.pem");//相对于resources目录
Buffer publicKeyBuf = vertx.fileSystem().readFileBlocking("certs/rsa_public_2048.pem");
JWTAuth jwtAuth = JWTAuth.create(vertx,
new JWTAuthOptions()
.addPubSecKey(
new PubSecKeyOptions()
.setAlgorithm("RS256")
.setBuffer(publicKeyBuf)
)
.addPubSecKey(
new PubSecKeyOptions()
.setAlgorithm("RS256")
.setBuffer(privateKeyBuf)
)
);
router.post("/login").handler(ctx->{
String username = ctx.request().getParam("username");
String password = ctx.request().getParam("password");
if ("admin".equals(username) && "admin".equals(password)) {
//初始化几个权限和角色数据(实际应用这里应该调用数据库查询对应用户的角色权限)
List<String> roles = new ArrayList<>();
roles.add("admin");
roles.add("user");
List<String> permissions = new ArrayList<>();
permissions.add("list_products");
ctx.response()
.end(jwtAuth.generateToken(
new JsonObject()
.put("permissions", permissions)
.put("roles",roles)
.put("username", username)
.put("someKey", "someValue")
,
new JWTOptions()
.setIgnoreExpiration(false)
.setExpiresInMinutes(30)
.setAlgorithm("RS256") //默认值是HS256,所有必须指定算法RS256
)
);
}else {
ctx.fail(401);
}
});
//创建token后则访问 需要认证(登录)的资源
AuthenticationHandler authenticationHandler = JWTAuthHandler.create(jwtAuth);
router.route("/protected/*").handler(authenticationHandler);
router.route("/protected/info").handler(ctx ->{
ctx.response()
.putHeader("content-type", "text/plain")
.end("protected info !");
});
//token 请求时候放入头部 Authorization Bearer <token>
//意思是从jwt token的obj中拿出permissions节点进行权限判断
AuthorizationProvider authorizationProvider = JWTAuthorization.create("permissions");
//[/protected/list_products]开头的路径需要list_products权限才能访问,从authorizationProvider表示jwt中拿出permission节点进行和list_products对比
router.route("/protected/list_products*").handler(
AuthorizationHandler.create(PermissionBasedAuthorization.create("list_products"))
.addAuthorizationProvider(authorizationProvider)
);
router.route("/protected/list_products").handler(ctx->{
ctx.response()
.putHeader("content-type", "text/plain")
.end("list_products Go !");
});
//指定地址指定权限
router.route("/protected/list_products/one").handler(
AuthorizationHandler.create(PermissionBasedAuthorization.create("one_products"))
.addAuthorizationProvider(authorizationProvider)
).handler(ctx->{
ctx.response()
.putHeader("content-type", "text/plain")
.end("one_products Go !");
});
//意思是从jwt token的obj中拿出roles节点进行权限判断
AuthorizationProvider authorizationProviderRoles = JWTAuthorization.create("roles");
//指定路径需要admin role 才能访问
router.route("/protected/setting/get").handler(
AuthorizationHandler.create(PermissionBasedAuthorization.create("admin"))
.addAuthorizationProvider(authorizationProviderRoles)
).handler(ctx->{
ctx.response()
.putHeader("content-type", "text/plain")
.end("Get setting !");
});
vertx.createHttpServer()
.requestHandler(router)
.listen(config().getInteger("http.port", 8080), result -> {
if (result.succeeded()) {
startPromise.complete();
}else {
startPromise.fail(result.cause());
}
});
}
}
[/user]
http://blog.xqlee.com/article/2408131134214153.html