自定义注解+AOP实现接口鉴权和认证

如何自定义注解?

自定义注解主要包括以下几个步骤:

  1. 定义注解:使用@interface关键字定义注解。
  2. 注解元素:在注解中定义元素,就像在接口中定义方法。
  3. 元注解:使用元注解(如@Retention、@Target等)来描述注解的行为。

定义注解
  • 可以使用@interface关键字来定义一个注解。下面是一个简单的例子:
  • MyAnnotation注解有两个元素:value和number。其中,number有一个默认值0。
public @interface MyAnnotation {
    String value();
    int number() default 0;
}

元注解

元注解是注解的注解,用来描述注解本身的行为。常见的元注解有:

  • @Retention:指明注解的保留策略。
  • @Target:指明注解的使用目标。

@Retention

  • @Retention指定了注解的生命周期,它有三个取值:
  • RetentionPolicy.SOURCE:注解只在源代码中存在,编译后就不存在了
  • RetentionPolicy.CLASS:注解在编译后会存在于.class文件中,但在运行时不会存在。
  • RetentionPolicy.RUNTIME:注解在运行时依然存在,可以通过反射读取。

@Target

  • @Target指定了注解可以使用的地方,如类、方法、字段等。常见的取值有:
  • ElementType.TYPE:用于类、接口、枚举、注解类型。
  • ElementType.FIELD:用于字段或属性。
  • ElementType.METHOD:用于方法。
  • ElementType.PARAMETER:用于参数。
  • ElementType.CONSTRUCTOR:用于构造函数。
  • ElementType.LOCAL_VARIABLE:用于局部变量。

完整的自定义注解示例
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;

// 定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {
    String value();
    int number() default 0;
}

使用自定义注解
public class Test {
    @MyAnnotation(value = "Test method", number = 42)
    public void testMethod() {
        // 方法的具体实现
    }
}

通过反射读取注解
import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args) throws Exception {
        Method method = Test.class.getMethod("testMethod");

        if (method.isAnnotationPresent(MyAnnotation.class)) {
            MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
            System.out.println("Value: " + annotation.value());
            System.out.println("Number: " + annotation.number());
        }
    }
}

Pmhub的架构图

7_GVWIHPIJ@5%YGP8)_QN.png

认证

  • 将生成的 JWT 字符串在 Redis 上也保存一份,并设置过期时间,
  • 判断用户是否登录时,需要先去 Redis 上查看 JWT 字符串是否存在,
  • 存在的话再对 JWT 字符串做解析操作,
  • 如果能成功解析,就没问题,如果不能成功解析,就说明令牌不合法。

在这里插入图片描述

用户登录请求

image.png

查询用户信息

image.png
image.png

返回用户信息

image.png

返回登录状态和token

image.png

AuthFilter网关鉴权

image.png

AuthFilter设置用户信息到请求头

image.png

AuthFilter接口耗时统计

image.png

将请求头中用户信息放入TTL中( HeaderInterceptor )

image.png

清除TTL中的用户信息

image.png

鉴权

  • 鉴权(或者说是授权)是请求到达每个微服务后,
  • 需要对请求进行权限判定,看是否有权限访问,
  • 通常不会放在网关中来做。还是在微服务中自己来做

  1. 外部请求 :

请求到达网关后,通过自定义请求头拦截器(可以放在公共包下面,每个服务都可以引用),配合自定义注解和 AOP,拦截请求头,获取用户和权限信息,然后进行比对,有权限则放行,没权限则抛出异常。

image.png

  1. 内部请求 :

对于内部的请求来说,正常是不需要鉴权的,内部请求可以直接处理。问题是如果使用了 OpenFeign,数据都是通过接口暴露出去的,不鉴权的话,又会担心从外部来的请求调用这个接口,对于这个问题,我们也可以自定义注解+AOP,然后在内部请求调用的时候,额外加一个头字段加以区分。

自定义内部认证注解
/**
 * 内部认证注解
 * 
 * @author canghe
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface InnerAuth
{
    /**
     * 是否校验用户信息
     */
    boolean isUser() default false;
}

自定义内部服务调用验证处理切面

检查请求头中的特定字段, 确保只有内部请求和已设置用户信息的请求才能继续执行目标方法

/**
 * 内部服务调用验证处理
 *
 * @author canghe
 */
@Aspect
@Component
public class InnerAuthAspect implements Ordered {
    @Around("@annotation(innerAuth)")
    public Object innerAround(ProceedingJoinPoint point, InnerAuth innerAuth) throws Throwable {
        String source = ServletUtils.getRequest().getHeader(SecurityConstants.FROM_SOURCE);
        // 内部请求验证
        if (!StringUtils.equals(SecurityConstants.INNER, source)) {
            throw new InnerAuthException("没有内部访问权限,不允许访问");
        }

        String userid = ServletUtils.getRequest().getHeader(SecurityConstants.DETAILS_USER_ID);
        String username = ServletUtils.getRequest().getHeader(SecurityConstants.DETAILS_USERNAME);
        // 用户信息验证
        if (innerAuth.isUser() && (StringUtils.isEmpty(userid) || StringUtils.isEmpty(username))) {
            throw new InnerAuthException("没有设置用户信息,不允许访问 ");
        }
        return point.proceed();
    }

    /**
     * 确保在权限认证aop执行前执行
     */
    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE + 1;
    }
}
  • 因为使用的是 OpenFeign,请求通过 OpenFeign 调用也需要鉴权,
  • 所以实现了 feign.RequestInterceptor 接口来定义一个 OpenFeign 的请求拦截器,
  • 在拦截器中,统一为 OpenFeign 请求设置请求头信息

Feign 配置注册
/**
 * Feign 配置注册
 *
 * @author canghe
 **/
@Configuration
public class FeignAutoConfiguration
{
    @Bean
    public RequestInterceptor requestInterceptor()
    {
        return new FeignRequestInterceptor();
    }
}

Feign 请求拦截器
/**
 * feign 请求拦截器
 *
 * @author canghe
 */
@Component
public class FeignRequestInterceptor implements RequestInterceptor {
    @Override
    public void apply(RequestTemplate requestTemplate) {
        HttpServletRequest httpServletRequest = ServletUtils.getRequest();
        Map<String, String> headers = ServletUtils.getHeaders(httpServletRequest);
        // 传递用户信息请求头,防止丢失
        String userId = headers.get(SecurityConstants.DETAILS_USER_ID);
        if (StringUtils.isNotEmpty(userId)) {
            requestTemplate.header(SecurityConstants.DETAILS_USER_ID, userId);
        }
        String userKey = headers.get(SecurityConstants.USER_KEY);
        if (StringUtils.isNotEmpty(userKey)) {
            requestTemplate.header(SecurityConstants.USER_KEY, userKey);
        }
        String userName = headers.get(SecurityConstants.DETAILS_USERNAME);
        if (StringUtils.isNotEmpty(userName)) {
            requestTemplate.header(SecurityConstants.DETAILS_USERNAME, userName);
        }
        String authentication = headers.get(SecurityConstants.AUTHORIZATION_HEADER);
        if (StringUtils.isNotEmpty(authentication)) {
            requestTemplate.header(SecurityConstants.AUTHORIZATION_HEADER, authentication);
        }

        // 配置客户端IP
        requestTemplate.header("X-Forwarded-For", IpUtils.getIpAddr());
    }
}
自定义注解可以用于实现AOP鉴权,以下是一个简单的示例代码,展示了如何使用自定义注解实现AOP鉴权。 首先,定义一个自定义注解 `@Authorization`: ```java import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Authorization { String[] roles() default {}; } ``` 然后,在需要进行鉴权的方法上添加 `@Authorization` 注解,并指定允许访问的角色列表: ```java public class MyService { @Authorization(roles = {"admin", "superuser"}) public void performAuthorizedAction() { // 执行需要鉴权的操作 } public void performUnauthenticatedAction() { // 执行无需鉴权的操作 } } ``` 接下来,创建一个切面类 `AuthorizationAspect`,在该类中使用 `@Around` 注解来拦截被 `@Authorization` 注解修饰的方法,并进行鉴权验证: ```java import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; @Component @Aspect public class AuthorizationAspect { @Around("@annotation(authorization)") public Object authorize(ProceedingJoinPoint joinPoint, Authorization authorization) throws Throwable { // 模拟鉴权逻辑 if (isUserAuthorized(authorization.roles())) { return joinPoint.proceed(); // 继续执行被拦截方法 } else { throw new UnauthorizedAccessException("Access denied"); // 抛出异常或执行其他处理 } } private boolean isUserAuthorized(String[] roles) { // 实际的鉴权逻辑,比如根据用户角色判断是否有权限访问 // 返回 true 表示有权限,返回 false 表示无权限 return true; } } ``` 最后,使用 Spring 或其他 AOP 框架来启用该切面,确保切面类被正确加载和生效。 通过以上步骤,你可以实现自定义注解用于AOP鉴权,对指定的方法进行权限验证。当调用被 `@Authorization` 注解修饰的方法时,会触发切面逻辑,在切面中进行鉴权验证,根据验证结果决定是否允许继续执行方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值