rpc远程调用统一鉴权组件

组件背景:

因为很多的业务平台,大多数进行数据查询的时候,需要通过memberId去做数据隔离。同时 配合统一sql重新组件的工作,所以需要通过在框架层面上对rpc上下文参数进行设置、校 验、传递。

组件原理:

1.在服务消费端通过dubbo过滤器,从线程级变量中获取上下文参数(saas中只要判断 memberId是否存在),并判断当前请求接口是否需要进行鉴权,如果需要进行鉴权而则校 验上下文参数是否合法,不合法则直接抛出异常;
2.将获取到的上下文参数放到dubbo的rpc请求上下文中;
3.服务生产端获取dubbo的rpc上下文参数,如果存在则存放到线程级变量中,判断当前请 求接口是否需要进行鉴权,如果需要进行鉴权而则校验上下文参数是否合法,不合法则直接 抛出异常。

rpc统一鉴权时序图:

rpc统一鉴权策略描述注解类: RpcAuthority

通过该注解可以给指定的rpc方法设置rpc鉴权策略,现在指指定了要鉴权和不要鉴权两个策 略,后续如果需要更强的扩展性,可以将鉴权类型指定为整数值,在具体的鉴权实现中再 根据这个整数值来制定具体的鉴权策略。

1
2
3
4
5
6
7
8
9
10
11
import com.lixiang.dubbo.authority.RpcType;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.TYPE, ElementType.PACKAGE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RpcAuthority {
RpcType rpcType() default RpcType.RPC;
}

rpc统一鉴权功能描述接口类:AuthorityHandler

1
2
3
4
5
6
7
8
9
10
11
12
import com.lixiang.dubbo.TokenInfo;
import com.lixiang.dubbo.authority.exception.RpcAuthorityException;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;

public interface AuthorityHandler<T extends TokenInfo>{
void customerCheck (Invoker<?> invoker, Invocation invocation) throws RpcAuthorityException;

void providerCheck (Invoker<?> invoker, Invocation invocation) throws RpcAuthorityException;

T convertToToken (String tokenInfo);
}

rpc请求信息描述类:RpcInfo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import java.util.Arrays;
import java.util.Objects;

public class RpcInfo {
private Class interfaceClazz;
private String rpcMethod;
private Class<?>[] parameterTypes;

public Class getInterfaceClazz() {
return interfaceClazz;
}

public void setInterfaceClazz(Class interfaceClazz) {
this.interfaceClazz = interfaceClazz;
}

public String getRpcMethod() {
return rpcMethod;
}

public void setRpcMethod(String rpcMethod) {
this.rpcMethod = rpcMethod;
}

public Class<?>[] getParameterTypes() {
return parameterTypes;
}

public void setParameterTypes(Class<?>[] parameterTypes) {
this.parameterTypes = parameterTypes;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RpcInfo rpcInfo = (RpcInfo) o;
return Objects.equals(interfaceClazz, rpcInfo.interfaceClazz) &&
Objects.equals(rpcMethod, rpcInfo.rpcMethod) &&
Arrays.equals(parameterTypes, rpcInfo.parameterTypes);
}

@Override
public int hashCode() {
int result = Objects.hash(interfaceClazz, rpcMethod);
result = 31 * result + Arrays.hashCode(parameterTypes);
return result;
}
}

鉴权策略实现: SimpleAuthorityHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import com.alibaba.fastjson.JSONObject;
import com.lixiang.dubbo.authority.AuthorityHandler;
import com.lixiang.dubbo.authority.RpcType;
import com.lixiang.dubbo.authority.annotation.RpcAuthority;
import com.lixiang.dubbo.authority.exception.RpcAuthorityException;
import com.lixiang.dubbo.rpc.DubboRpcUtils;
import com.lixiang.dubbo.rpc.RpcInfo;
import com.lixiang.dubbo.utils.AnnotationUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

public class SimpleAuthorityHandler implements AuthorityHandler<MemberIdToken> {
private static Logger logger = LoggerFactory.getLogger(SimpleAuthorityHandler.class);
private ConcurrentHashMap<RpcInfo, RpcType> rpcAuthorityConfig = new ConcurrentHashMap<>();
private RpcType defaultRpcType = RpcType.COMMON;
public static final String RPC_TOKEN_NAME = "rpcTokenName";
private static final ThreadLocal<MemberIdToken> CURRENT_USER_INFO = new ThreadLocal<>();

@Override
public void customerCheck(Invoker<?> invoker, Invocation invocation) throws RpcAuthorityException {
MemberIdToken token = CURRENT_USER_INFO.get();
checkToken(invoker, invocation, token);
if (token != null) {
invocation.getAttachments().put(RPC_TOKEN_NAME, JSONObject.toJSONString(token));
}
}
@Override
public void providerCheck(Invoker<?> invoker, Invocation invocation) throws RpcAuthorityException {
String tokenInfo = invocation.getAttachment(RPC_TOKEN_NAME);
MemberIdToken token = convertToToken(tokenInfo);
checkToken(invoker, invocation, token);
if (token != null) {
setToken(token);
} else {
setToken(-1L);
}
}

private void checkToken(Invoker<?> invoker, Invocation invocation, MemberIdToken token) throws RpcAuthorityException {
if (token == null || token.getMemberId() == null) {
RpcInfo rpcInfo = DubboRpcUtils.getInvokerRpcInfo(invoker, invocation);
RpcType rpcType;
if (rpcAuthorityConfig.containsKey(rpcInfo)) {
rpcType = rpcAuthorityConfig.get(rpcInfo);
} else {
rpcType = getRpcType(rpcInfo);
rpcAuthorityConfig.putIfAbsent(rpcInfo, rpcType);
}
if (Objects.equals(RpcType.RPC, rpcType)) {
throw new RpcAuthorityException(String.format("rpc请求[%s]的rpc 权限不合法", JSONObject.toJSONString(rpcInfo)));
}
}
}

@Override
public MemberIdToken convertToToken(String tokenInfo) {
MemberIdToken token = null;
if (StringUtils.isNotEmpty(tokenInfo)) {
try {
token = JSONObject.parseObject(tokenInfo, MemberIdToken.class);
} catch (Exception e) {
logger.warn("rpc MemberIdToken 格式不对", e);
}
}
return token;
}

private RpcType getRpcType(RpcInfo rpcInfo) {
Class interfaceClazz = rpcInfo.getInterfaceClazz();
Package interfacePackage = interfaceClazz.getPackage();
RpcAuthority packageRpcAuthority = AnnotationUtils.recursionGet(interfacePackage, RpcAuthority.class);
RpcAuthority clazzRpcAuthority = (RpcAuthority) interfaceClazz.getAnnotation(RpcAuthority.class);
try {
Method rpcMethod = interfaceClazz.getMethod(rpcInfo.getRpcMethod(), rpcInfo.getParameterTypes());
RpcAuthority methodRpcAuthority = rpcMethod.getAnnotation(RpcAuthority.class);

if (methodRpcAuthority != null) {
return methodRpcAuthority.rpcType();
} else if (clazzRpcAuthority != null) {
return clazzRpcAuthority.rpcType();
} else if (packageRpcAuthority != null) {
return packageRpcAuthority.rpcType();
} else {
return defaultRpcType;
}

} catch (NoSuchMethodException e) {
throw new RpcAuthorityException(e);
}
}

public static void setToken(MemberIdToken token) {
CURRENT_USER_INFO.set(token);
}

public static void setToken(Long memberId) {
MemberIdToken token = new MemberIdToken();
token.setMemberId(memberId);
setToken(token);
}

public static MemberIdToken getToken() {
return CURRENT_USER_INFO.get();
}

public static void setUserId(Long userId) {
MemberIdToken token = CURRENT_USER_INFO.get();
token.setUserId(userId);
setToken(token);
}
}

1.获取rpc服务鉴权级别方法: 通过按照就近原则获取服务鉴权级别:按照方法签名注解->接口签名注解->包签名注解路 径去寻找RpcAuthority注解,如果有RpcAuthority注解,立马返回,如果都没有找到,则 指定为默认鉴权级别:不需要进行鉴权

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private RpcType getRpcType(RpcInfo rpcInfo) {
Class interfaceClazz = rpcInfo.getInterfaceClazz();
Package interfacePackage = interfaceClazz.getPackage();
RpcAuthority packageRpcAuthority = AnnotationUtils.recursionGet(interfacePackage, RpcAuthority.class);
RpcAuthority clazzRpcAuthority = (RpcAuthority) interfaceClazz.getAnnotation(RpcAuthority.class);
try {
Method rpcMethod = interfaceClazz.getMethod(rpcInfo.getRpcMethod(), rpcInfo.getParameterTypes());
RpcAuthority methodRpcAuthority = rpcMethod.getAnnotation(RpcAuthority.class);

if (methodRpcAuthority != null) {
return methodRpcAuthority.rpcType();
} else if (clazzRpcAuthority != null) {
return clazzRpcAuthority.rpcType();
} else if (packageRpcAuthority != null) {
return packageRpcAuthority.rpcType();
} else {
return defaultRpcType;
}

} catch (NoSuchMethodException e) {
throw new RpcAuthorityException(e);
}
}

2.服务消费端鉴权校验 从线程级变量中获取上下文参数(saas中只要判断memberId是否存在),并将获取到的上下 文参数放到dubbo的rpc请求上下文中(不管是否需求鉴权,都会把线程上下文推到rpc上 下文中),判断当前请求接口是否需要进行鉴权,如果需要进行鉴权而则校验上下文参数是 否合法,不合法则直接抛出异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Override
public void customerCheck(Invoker<?> invoker, Invocation invocation) throws RpcAuthorityException {
MemberIdToken token = CURRENT_USER_INFO.get();
checkToken(invoker, invocation, token);
if (token != null) {
invocation.getAttachments().put(RPC_TOKEN_NAME, JSONObject.toJSONString(token));
}
}

private void checkToken(Invoker<?> invoker, Invocation invocation, MemberIdToken token) throws RpcAuthorityException {
if (token == null || token.getMemberId() == null) {
RpcInfo rpcInfo = DubboRpcUtils.getInvokerRpcInfo(invoker, invocation);
RpcType rpcType;
if (rpcAuthorityConfig.containsKey(rpcInfo)) {
rpcType = rpcAuthorityConfig.get(rpcInfo);
} else {
rpcType = getRpcType(rpcInfo);
rpcAuthorityConfig.putIfAbsent(rpcInfo, rpcType);
}
if (Objects.equals(RpcType.RPC, rpcType)) {
throw new RpcAuthorityException(String.format("rpc请求[%s]的rpc 权限不合法", JSONObject.toJSONString(rpcInfo)));
}
}
}

3.服务生产端鉴权校验 服务生产端获取dubbo的rpc上下文参数,如果存在则存放到线程级变量中,判断当前请求 接口是否需要进行鉴权,如果需要进行鉴权而则校验上下文参数是否合法,不合法则直接抛 出异常。

1
2
3
4
5
6
7
8
9
10
11
@Override
public void providerCheck(Invoker<?> invoker, Invocation invocation) throws RpcAuthorityException {
String tokenInfo = invocation.getAttachment(RPC_TOKEN_NAME);
MemberIdToken token = convertToToken(tokenInfo);
checkToken(invoker, invocation, token);
if (token != null) {
setToken(token);
} else {
setToken(-1L);
}
}

服务消费端鉴权拦截器: CustomerFilter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import com.alibaba.fastjson.JSONObject;
import com.lixiang.dubbo.authority.AuthorityHandler;
import com.lixiang.dubbo.simpletoken.SimpleAuthorityHandler;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Activate(group = CommonConstants.CONSUMER)
public class CustomerFilter implements Filter {
private AuthorityHandler authorityHandler = new SimpleAuthorityHandler();
private static Logger logger = LoggerFactory.getLogger(CustomerFilter.class);

@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
try {
authorityHandler.customerCheck(invoker, invocation);
} catch (RpcException e) {
writeRpcErrorLog(e, invoker, invocation);
throw e;
}

try {
Result result = invoker.invoke(invocation);
if (result.hasException()) {
writeRpcErrorLog(result.getException(), invoker, invocation);
}
return result;
} catch (Exception e) {
writeRpcErrorLog(e, invoker, invocation);
throw e;
}
}

private void writeRpcErrorLog(Throwable e, Invoker<?> invoker, Invocation invocation) {
RpcInfo rpcInfo = DubboRpcUtils.getInvokerRpcInfo(invoker, invocation);
Object[] args = invocation.getArguments();

logger.warn(String.format("rpc接口信息:%s,rpc请 求参数:%s,异常信息:%s",
JSONObject.toJSONString(rpcInfo),
JSONObject.toJSONString(args),
e.getMessage()
), e);
}
}

服务生产端端鉴权拦截器:ProviderFilter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import com.lixiang.dubbo.authority.AuthorityHandler;
import com.lixiang.dubbo.simpletoken.SimpleAuthorityHandler;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.Filter;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.rpc.*;

@Activate(group = CommonConstants.PROVIDER)
public class ProviderFilter implements Filter {

private AuthorityHandler authorityHandler = new SimpleAuthorityHandler();

@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
authorityHandler.providerCheck(invoker, invocation);
return invoker.invoke(invocation);
}
}

设置默认装载自定义过滤器:在工程resource下的META-INF目录下创建dubbo 目录,并创建文件com.alibaba.dubbo.rpc.Filter,指定自定义的异常处理Filter类为默认扩展点

1
2
xiangxiangCustomerFilter=com.lixiang.dubbo.rpc.CustomerFilter
xiangxiangProviderFilter=com.lixiang.dubbo.rpc.ProviderFilter