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方式实现用户注销(切换用户)。

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

相关推荐
程序媛小果15 分钟前
基于java+SpringBoot+Vue的旅游管理系统设计与实现
java·vue.js·spring boot
小屁孩大帅-杨一凡41 分钟前
java后端请求想接收多个对象入参的数据
java·开发语言
java1234_小锋1 小时前
使用 RabbitMQ 有什么好处?
java·开发语言
TangKenny1 小时前
计算网络信号
java·算法·华为
肘击鸣的百k路1 小时前
Java 代理模式详解
java·开发语言·代理模式
城南vision1 小时前
Docker学习—Docker核心概念总结
java·学习·docker
wyh要好好学习1 小时前
SpringMVC快速上手
java·spring
尢词1 小时前
SpringMVC
java·spring·java-ee·tomcat·maven
Mr. zhihao1 小时前
享元模式在 JDK 中的应用解析
java·享元模式
茶馆大橘1 小时前
微服务系列五:避免雪崩问题的限流、隔离、熔断措施
java·jmeter·spring cloud·微服务·云原生·架构·sentinel