文章目录
如何自定义注解?
自定义注解主要包括以下几个步骤:
- 定义注解:使用@interface关键字定义注解。
- 注解元素:在注解中定义元素,就像在接口中定义方法。
- 元注解:使用元注解(如@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的架构图
认证
- 将生成的 JWT 字符串在 Redis 上也保存一份,并设置过期时间,
- 判断用户是否登录时,需要先去 Redis 上查看 JWT 字符串是否存在,
- 存在的话再对 JWT 字符串做解析操作,
- 如果能成功解析,就没问题,如果不能成功解析,就说明令牌不合法。
用户登录请求
查询用户信息
返回用户信息
返回登录状态和token
AuthFilter网关鉴权
AuthFilter设置用户信息到请求头
AuthFilter接口耗时统计
将请求头中用户信息放入TTL中( HeaderInterceptor )
清除TTL中的用户信息
鉴权
- 鉴权(或者说是授权)是请求到达每个微服务后,
- 需要对请求进行权限判定,看是否有权限访问,
- 通常不会放在网关中来做。还是在微服务中自己来做
- 外部请求 :
请求到达网关后,通过自定义请求头拦截器(可以放在公共包下面,每个服务都可以引用),配合自定义注解和 AOP,拦截请求头,获取用户和权限信息,然后进行比对,有权限则放行,没权限则抛出异常。
- 内部请求 :
对于内部的请求来说,正常是不需要鉴权的,内部请求可以直接处理。问题是如果使用了 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());
}
}