在开发 Web 应用时,我们经常需要对不同的接口进行权限控制,以保证只有合法的用户才能访问特定的资源。一种常见的做法是使用拦截器(Interceptor)来拦截请求,检查请求头中是否携带了有效的认证信息,如果没有,就返回错误响应,如果有,就放行请求。这样,我们就可以在拦截器中实现统一的权限验证逻辑,而不需要在每个接口中重复编写代码。
但是,这种做法也有一些缺点,比如:
1.我们需要在拦截器中配置哪些接口需要拦截,哪些接口不需要拦截,这样会增加配置的复杂度和维护成本。
2.我们需要在拦截器中硬编码认证信息的格式和解析方式,这样会降低代码的可扩展性和可维护性。
3.我们无法在接口层面直观地看出哪些接口需要权限验证,哪些接口不需要权限验证,这样会影响代码的可读性和可理解性。
为了解决这些问题,我们可以使用注解(Annotation)来标记接口是否需要权限验证,然后在拦截器中根据注解的存在与否来决定是否拦截请求。这样,我们就可以实现以下优点:
1.我们可以在接口上直接使用 @RequireAuth 注解来表示该接口需要权限验证,这样可以提高代码的可读性和可理解性,也可以避免配置的复杂度和维护成本。
2.我们可以在 @RequireAuth 注解中定义认证信息的格式和解析方式,这样可以提高代码的可扩展性和可维护性,也可以方便地支持不同的认证方式。
3.我们可以在拦截器中只需要判断请求目标是否是一个 HandlerMethod,并且是否有 @RequireAuth 注解,这样可以简化拦截器的逻辑,也可以提高拦截器的性能。
下面,我们来看一下如何使用 @RequireAuth 注解实现接口权限控制的具体步骤:
1. 定义 @RequireAuth 注解。我们可以使用 @Target 和 @Retention 注解来指定 @RequireAuth 注解的作用范围和生命周期,这里我们选择作用于方法上,且运行时有效。我们还可以使用 @Documented 注解来表示 @RequireAuth 注解会被包含在 JavaDoc 中。我们还可以在 @RequireAuth 注解中定义一些属性,比如 authType 来表示认证方式,authHeader 来表示认证信息在请求头中的名称,authDecoder 来表示认证信息的解码器,等等。这些属性可以有默认值,也可以由使用者自定义。例如,我们可以定义 @RequireAuth 注解如下:
@Target(ElementType.METHOD) // 作用于方法上
@Retention(RetentionPolicy.RUNTIME) // 运行时有效
@Documented // 包含在 JavaDoc 中
// 认证方式,默认为 BASIC
// 认证信息在请求头中的名称,默认为 Authorization
// 认证信息的解码器,默认为 Base64.Decoder
// 其他属性,根据需要定义
2. 使用 @RequireAuth 注解。我们可以在需要权限验证的接口上使用 @RequireAuth 注解来标记,这样就可以表示该接口需要权限验证。我们可以使用 @RequireAuth 注解的默认属性,也可以自定义属性。例如,我们可以在以下接口上使用 @RequireAuth 注解:
// 获取用户信息的接口,需要权限验证,使用默认属性
// 省略业务逻辑
// 更新用户信息的接口,需要权限验证,自定义属性
// 省略业务逻辑
// 删除用户信息的接口,不需要权限验证,不使用注解
// 省略业务逻辑
3. 实现拦截器。我们可以继承 HandlerInterceptorAdapter 类来实现一个拦截器,重写 preHandle 方法来拦截请求。在 preHandle 方法中,我们可以获取请求目标,判断是否是一个 HandlerMethod,如果是,就获取其上的 @RequireAuth 注解,如果有,就进行权限验证,如果没有,就放行请求。在权限验证中,我们可以根据 @RequireAuth 注解的属性,获取请求头中的认证信息,使用指定的解码器解码认证信息,然后根据认证方式进行验证,如果验证通过,就放行请求,如果验证失败,就返回错误响应。例如,我们可以实现拦截器如下:
// 请求目标为 method of controller,需要进行验证
/* 方法没有 @RequireAuth 注解, 放行 */
/* 方法有 @RequireAuth 注解,需要拦截校验 */
// 获取 @RequireAuth 注解的属性
// 获取请求头中的认证信息
// 没有认证信息,拦截
res.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 设置响应状态码为 401
res.getWriter().write("Unauthorized"); // 设置响应内容为 Unauthorized
// 有认证信息,解码
Decoder decoder = authDecoder.getDeclaredConstructor().newInstance(); // 根据解码器类创建解码器实例
String decodedAuthInfo = new String(decoder.decode(authInfo)); // 解码认证信息
// 根据认证方式进行验证
case BASIC: // 基本认证
// 认证信息格式为 username:password
// 验证用户名和密码,这里简单地使用固定的值,实际应用中应该从数据库或其他地方获取
return true; // 验证通过,放行
res.setStatus(HttpServletResponse.SC_FORBIDDEN); // 设置响应状态码为 403
res.getWriter().write("Forbidden"); // 设置响应内容为 Forbidden
return false; // 验证失败,拦截
case BEARER: // 令牌认证
// 认证信息格式为 token