Vert.x,认证与授权 - HTTP基本认证

几乎所有的线上系统都离不开认证和授权,Vert.x auth相关组件提供了丰富(Session,JTW, OAuth,...),便捷的认证和授权支持。

当前,使用最多是Web应用,所以在后续讨论中,都是关于Vert.x auth在Web应用(Vertx web)中的使用。

基本概念

认证与授权

认证/鉴权(Authentication) 是指验证用户的身份。// Tells who the user is

  • 确认用户或系统的身份,以确保他们是所声称的那个人或系统。
  • 认证通常通过用户名和密码、数字证书、生物识别(如指纹或面部识别)等方式进行。
  • 例如,当你输入用户名和密码登录到某个应用程序时,系统会检查你的凭证以确认你的身份。

授权(Authorization) 授权是在认证之后进行的,它决定已认证用户可以访问哪些资源或执行哪些操作。// Tells what the user is allowed to do

  • 授权通常在认证之后,当在服务器端的身份确认后,会获取对应用户在服务器端的权限,在执行有权限要求的操作前,先用户的权限进行检查。
  • 授权的过程涉及设置权限和访问控制,比如数据库的读取、写入权限,或是特定文件夹的访问权限。
  • 例如,在同一个应用程序中,普通用户可能只能查看信息,而管理员则可以编辑和删除信息。

认证方式

参考来源: https://developer.baidu.com/article/details/3133306

有状态身份验证,也称为会话认证,指的是服务端需要记录每次会话的客户端信息以识别客户端身份。常见的有状态认证方式包括使用cookie和session。当用户登录后,服务端会将登录信息保存在session中,并给用户分配一个唯一的session ID。随后,每次用户请求时,都需要携带该session ID,以便服务端能够识别和验证用户身份。

无状态身份验证 (Token-Based Authentication)

无状态身份验证,也称为令牌认证,是一种不依赖服务端会话的认证方式。在无状态认证中,服务端不需要记录用户的会话信息。用户通过首次认证后,服务端会生成一个令牌(通常使用JSON Web Token,简称JWT),并将其传递给客户端。客户端在后续请求中携带该令牌进行身份验证。

有状态和无状态身份验证各有优缺点,选择合适的认证方式取决于具体需求和应用场景。 有状态认证适用于需要个性化配置和复杂权限控制的Web应用,而无状态认证适用于对性能和扩展性要求较高的场景,如微服务架构、API网关等。此外,无状态认证也适用于跨域应用和单点登录的场景。

Vert.x auth使用概述

通用认证和授权(vertx-auth-common)组件提供了用于身份验证和授权的接口,是Vert.x auth的基础,要在项目中使用,必须先引入依赖:

复制代码
<dependency>
	<groupId>io.vertx</groupId>
	<artifactId>vertx-auth-common</artifactId>
	<version>4.5.10</version>
</dependency>

要使用Vert.x auth我们需要实现的两个核心接口是AuthenticationProvider和User。AuthenticationProvider用于告诉系统如何进行身份认证,而User实例是用户对应在服务器中的权限的载体(authorisation of users),是AuthorizationProvider接口方法authenticate返回的(异步)结果。

归纳来说: AuthenticationProvider的authenticate对输入的信息进行身份认证,如果认证通过后,返回对应用户在服务器端的权限User(异步结果),如果不通过返回失败结果。先有个概念,后续将通过案例来具体说明。

HTTP基本认证(Http Basic Auth

HTTP基本认证(HTTP Basic Access Authentication )是一种用于网络协议的简单认证方式,这种方法通常用于控制对特定资源的访问。在HTTP基本认证中,服务器会向未经认证的请求返回一个401 Unauthorized状态码,并在响应头中包含一个'WWW-Authenticate'字段,提示客户端需要提供认证信息。客户端随后会在请求头中包含一个Authorization字段,其中包含了经过Base64编码的用户名和密码。

HTTP基本认证中的用户名和密码是以明文形式(经过Base64编码)传输的,因此不建议在不安全的网络中使用。为了保护认证信息的安全,应该在HTTPS/TLS的保护下使用HTTP基本认证。

HTTP基本认证不安全,目前很少使用。拿来讨论,是因为简单,便于理解认证与授权的编写。假设有3个页面:

复制代码
- page1.html: 不需要身份认证既可以访问;
- page2.html: 所有认证用户都可以访问;
- page3.html: 仅admin角色可以访问;

下面通过Vert.x Auth实现上述需求。

java 复制代码
public class BasicAccessAuth {
	private static final Logger LOGGER = Logger.getLogger(BasicAccessAuth.class.getName());

	public static void main(String[] args) {

		// 模拟服务器存储的用户密码,实际场景这些信息可以存储在数据库。
		HashMap<String, String> users = new HashMap<>();
		users.put("admin", "password");
		users.put("user", "password");

		Vertx vertx = Vertx.vertx();
		HttpServer server = vertx.createHttpServer();
		Router router = Router.router(vertx);

		// 步骤1: 创建一个authProvider实例,这里用了匿名类,实际编码时可以编写一个实现AuthenticationProvider接口的类,并实例化。
		AuthenticationProvider authProvider = new AuthenticationProvider() {
			// 认证处理器,异步方法,结果返回一个User结果。
			@Override
			public void authenticate(JsonObject credentials, Handler<AsyncResult<User>> resultHandler) {
				LOGGER.info("input credentials = " + credentials.encode()); // encodePrettily
				// 通过密码进行身份认证
				String username = credentials.getString("username");
				String password = credentials.getString("password");
				if (users.containsKey(username) && users.get(username).equals(password)) {
					// 通过了身份认证,根据用户在服务器中的权限创建一个User实例。
					// 我们模拟了一个简单的权限,角色。
					// 权限根据实际来设计,例如可以基于角色,也可以设计成基于权限,如: 某个路径(path)设计权限位(rwx) ...
					// 实际场景中,用户对应的权限通常存储在数据库中。
					// User实例是用户对应权限的载体。
					String role = username.equalsIgnoreCase("admin") ? "admin" : "user";
					JsonObject principal = new JsonObject();
					principal.put("username", username);

					// 属性信息
					JsonObject attributes = new JsonObject();
					attributes.put("role", role);
					// 步骤2: 根据用户对应权限实例化User做为认证结果。
					// 这里权限比较简单,使用了Vert.x自带的UserImpl,实际编码时可以编写一个实现User接口的类。
					User user = new UserImpl(principal, attributes);
					resultHandler.handle(Future.succeededFuture(user));
				} else {
					resultHandler.handle(Future.failedFuture("身份认证失败。"));
				}
			}
		};
		
		// '/hr/page1.html'直接返回静态页面,不使用基本认证。
		// 原理: 因为该路由比认证处理器的路由先加入router,所以优先级高,关于路由优先级与多路由,可以参考我之前写的文章。
		router.route("/hr/page1.html").handler(ctx -> {
			HttpServerResponse response = ctx.response();
			response.putHeader("Content-Type", "text/html");
			response.sendFile("webroot/page1.html");
		});

		// 步骤3: 创建认证处理器,与对应(需要认证的)路由关联。
		// 处理器可以自己写,也可使用Vertx自带的BasicAuthHandler
		// '/hr/*'路由与BasicAuthHandler绑定,实现访问'/hr/*'都需要进行Http Basic Auth认证。
		BasicAuthHandler basicAuthHandler = BasicAuthHandler.create(authProvider);
		router.route("/hr/*").handler(basicAuthHandler);
		
		router.route("/hr/page3.html").handler(ctx -> {
			// 经过身份认证路由后,认证成功会获取用户授权信息。
			User user = ctx.user();
			if (user != null) {
				// String role = user.attributes().getString("role");
				String role = user.get("role");
				if ("admin" != role) {
					HttpServerResponse response = ctx.response();
					response.setStatusCode(401).setStatusMessage("admin only");
					response.putHeader("Content-Type", "text/html; charset=utf-8");
					response.end("仅admin角色可以访问。");
				}
			}
			ctx.next();
		});
		
		router.route("/hr/*").handler(StaticHandler.create());

		server.requestHandler(router).listen(8080);
	}
}

访问http://127.0.0.1:8080/hr/page1.html,可以正常访问page1.html。

访问http://127.0.0.1:8080/hr/page2.html,会弹出浏览器原生的登录框提示密码登录。

当通过admin/password或者user/password登录后,可以正常访问page2.html内容。

访问http://127.0.0.1:8080/hr/page3.html,仅当通过admin/password登录时,才能看到page3.html内容,如果使用user用户登录,会提示"仅admin角色可以访问。"

测试时候注意,HTTP基本认证本地cookie会保存密码,无法通过(重启)服务器注销用户,最简单的方法是通过删除cookie方式实现用户注销(切换用户)。

完整的案例已上传在本文的资源中,感兴趣的可以下载。

相关推荐
腥臭腐朽的日子熠熠生辉18 分钟前
解决maven失效问题(现象:maven中只有jdk的工具包,没有springboot的包)
java·spring boot·maven
ejinxian20 分钟前
Spring AI Alibaba 快速开发生成式 Java AI 应用
java·人工智能·spring
杉之26 分钟前
SpringBlade 数据库字段的自动填充
java·笔记·学习·spring·tomcat
圈圈编码1 小时前
Spring Task 定时任务
java·前端·spring
俏布斯1 小时前
算法日常记录
java·算法·leetcode
27669582921 小时前
美团民宿 mtgsig 小程序 mtgsig1.2 分析
java·python·小程序·美团·mtgsig·mtgsig1.2·美团民宿
爱的叹息1 小时前
Java 连接 Redis 的驱动(Jedis、Lettuce、Redisson、Spring Data Redis)分类及对比
java·redis·spring
程序猿chen1 小时前
《JVM考古现场(十五):熵火燎原——从量子递归到热寂晶壁的代码涅槃》
java·jvm·git·后端·java-ee·区块链·量子计算
松韬2 小时前
Spring + Redisson:从 0 到 1 搭建高可用分布式缓存系统
java·redis·分布式·spring·缓存
绝顶少年2 小时前
Spring Boot 注解:深度解析与应用场景
java·spring boot·后端