请求资源统一定义组件

组件开发背景:

在本组件开发前,通过对controller请求进行了统一封装,使得对外部系统只暴露 api/data接口,已到达模糊请求接口,api服务请求参数统一的目的,但是之前的实现中需要 添加各种command映射和访问权限白名单,导致开发过程中的配置项太多。而且分散在各 个不同地方,并且以真实的controller方法定义相隔离,导致通过command来反向查找具 体的业务代码实现太过麻烦,所以为减少开发成本和查找成本,提升开发效率,所以开发了 本组件,对command资源映射和权限访问白名单进行统一定义。

实现原理:

通过在controller方法上添加自定义注解PermissionSource,在spring的root容器启动完 成事件中,去对spring加载进来的controller方法签名进行解析,再根据方法签名上的自定 义注解PermissionSource来初始化command处理策略以全局变量保存的上下文中,全局 变量保存着通过url直连访问的url直连地址,并且保存直连地址和权限资源请求对象的映射 map,同时保存了所以command和权限资源请求对象的map对象。 通过自定义请求servlet将/api下的请求进行分发,在自定义请求分发起接受到外部的访问请 求后,通过请求地址和请求参数获取到对应的权限资源请求对象,并通过权限资源请求对象 对请求进行鉴权后,将请求包装成目标controller方法所需要的请求参数并转发给目标 controller方法进行处理。

权限资源定义注解:

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
import com.lixiang.permissionlevel.enums.PermissionLevel;
import com.lixiang.permissionlevel.enums.RequestType;
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})
@Retention(RetentionPolicy.RUNTIME)
public @interface PermissionSource {
/**
* 请求访问类型:默认通过command访问
* @return
*/
RequestType type() default RequestType.COMMAND;

/**
* 请求访问command,必填,当为url访问是,该字段值忽略掉
* @return
*/
String command() ;

/**
* 访问权限级别,默认为严格访问控制权限
* @return
*/
PermissionLevel level() default PermissionLevel.COMMAND_PERMISSION;
}

资源请求访问类型枚举类:

1
2
3
4
5
6
7
8
9
10
public enum RequestType {
/**
* 通过原生URL暴露的请求
*/
URL,
/**
* 通过命令暴露的请求
*/
COMMAND
}

资源请求访问级别枚举类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public enum PermissionLevel {
/**
* 通过command暴露的无权限控制的请求
*/
ALL,
/**
* 通过command暴露的对外提供接口服务的请求
*/
OUT_INTERFACE,
/**
* 通过command暴露的所有内部注册用户
*/
INNER_USER,
/**
* 正常会员才可以访问的请求
*/
COMMAND_NORMAL_MEMBER,
/**
* 赋权了才可以访问的请求
*/
COMMAND_PERMISSION
}

资源定义信息加载引擎-spring容器启动事件监听器:

(因为springmvc有两个spring容器,所以在监听器会触发两次,但是只有在加载了controller的主容器初始化时才需要初始化资源定义信息上下文,而在普通的spring容器加载完成时因为没有加载controller对象,所以不需要执行初始化资源定义信息上下文)

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
import com.lixiang.permissionlevel.config.CommandConfigLoader;
import com.lixiang.permissionlevel.config.CommandLevelLoader;
import com.lixiang.permissionlevel.config.PermissionContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import java.util.Map;

@Component
public class PermissionSpringListener implements ApplicationListener<ContextRefreshedEvent> {
private static Logger logger = LoggerFactory.getLogger(PermissionSpringListener.class);
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
ApplicationContext context = event.getApplicationContext();
// 只有在root容器初始化是才需要执行
if (context.getParent() != null){
return;
}
Map<String, Object> controllerBeans = context.getBeansWithAnnotation(Controller.class) ;
if (controllerBeans != null){
for (Object bean : controllerBeans.values()) {
processBean(bean);
}
}
Map<String, CommandConfigLoader> commandConfigLoaders = context.getBeansOfType(CommandConfigLoader.class);
if (commandConfigLoaders != null) {
for (CommandConfigLoader bean : commandConfigLoaders.values()) {
bean.loadCommandConfig();
}
}
Map<String, CommandLevelLoader> commandLevelLoader = context.getBeansOfType(CommandLevelLoader.class);
if (commandLevelLoader != null) {
for (CommandLevelLoader bean : commandLevelLoader.values()) {
bean.loadCommandLevel();
}
}
}

private void processBean(Object bean){
Class beanClazz = AopProxyUtils.ultimateTargetClass(bean);
PermissionContext.processBeanClass(beanClazz);
}
}

初始化资源定义信息工具类:

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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
import com.alibaba.dubbo.common.utils.CollectionUtils;
import com.alibaba.dubbo.common.utils.ConcurrentHashSet;
import com.lixiang.permissionlevel.annotation.PermissionSource;
import com.lixiang.permissionlevel.constant.AuthorizationConstants;
import com.lixiang.permissionlevel.enums.PermissionLevel;
import com.lixiang.permissionlevel.enums.RequestType;
import com.lixiang.permissionlevel.exception.PermissionInitException;
import com.lixiang.permissionlevel.exception.PermissionRequestException;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

public class PermissionContext {

private static Logger logger = LoggerFactory.getLogger(PermissionContext.class);
/**
* url直连请求集
*/
private static Set<String> URL_REQS = new ConcurrentHashSet<>();
/**
* 所有请求和请求对象map
*/
private static Map<String, PermissionRequest> URL_METHOD_PREQUESTS = new ConcurrentHashMap<>();
/**
* command请求和请求对象map
*/
private static Map<String, PermissionRequest> COMMAND_PREQUESTS = new ConcurrentHashMap<>();
private static String API_PATH = "/api";
private static final String PATH_SLIPT = "/";

/**
* 处理controller bean,将controller类上定义的PermissionSource加载到上下文中
* @param beanClazz
*/
public static void processBeanClass(Class beanClazz) {
// 所有的controller bean 都有进行扩展
String basePath = getBasePath(beanClazz);
Method[] methods = beanClazz.getMethods();
for (Method method : methods) {
handleControllerMethod(method, basePath);
}
}

/**
* 获取controller的基础地址
*/
private static String getBasePath(Class beanClazz) {
if (beanClazz.isAnnotationPresent(RequestMapping.class)) {
RequestMapping requestMapping = (RequestMapping) beanClazz.getAnnotation(RequestMapping.class);
String[] vals = requestMapping.value();
if (vals != null && vals.length != 0) {
return vals[0];
}
}
return "";
}

/**
* 处理controller 的mapping方法,将controller的mapping方法上定义的PermissionSource加载到上下文中
* @param method
* @param basePath
*/
private static void handleControllerMethod(Method method, String basePath) {
// 该方法为mapper方法
if (method.isAnnotationPresent(RequestMapping.class)) {
RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
PermissionSource permissionSource = getMethodPermissionSource(method);
handleControllerMethod(requestMapping, permissionSource, basePath, method);
}
}

/**
* 获取方法上定义的PermissionSource注解
* @param method
* @return
*/
private static PermissionSource getMethodPermissionSource(Method method) {
if (method.isAnnotationPresent(PermissionSource.class)) {
return method.getAnnotation(PermissionSource.class);
}
return null;
}


/**
* 将controller的mapping方法上定义的PermissionSource解析成PermissionRequest对象,加载到上下文中
* @param requestMapping
* @param permissionSource
* @param basePath
* @param method
*/

private static void handleControllerMethod(RequestMapping requestMapping, PermissionSource permissionSource, String basePath, Method method) {
String relativePath = getRelativePath(requestMapping);
if (permissionSource == null) {
// 为配置资源权限注解,则默认直接配置为通过url访问
initUrlRequest(basePath, relativePath, method, PermissionLevel.COMMAND_PERMISSION, requestMapping);
} else {
if (Objects.equals(RequestType.URL, permissionSource.type())) {
initUrlRequest(basePath, relativePath, method, permissionSource.level(), requestMapping);
} else {
initCommandRequest(basePath, relativePath, method, permissionSource.level(), requestMapping, permissionSource.command());
}
}
}


/**
* 初始化url方式暴露的请求资源,并注入到上下文中
* @param basePath
* @param relativePath
* @param method
* @param level
* @param requestMapping
*/
private static void initUrlRequest(String basePath, String relativePath, Method method, PermissionLevel level, RequestMapping requestMapping) {
RequestMethod requestMethod = getRequestMethod(requestMapping, null);
PermissionRequest permissionRequest = buildUrlRequest(basePath, relativePath, method, level, requestMethod);
URL_METHOD_PREQUESTS.put(permissionRequest.getMethodRequestPath(), permissionRequest);
URL_REQS.add(permissionRequest.getMethodRequestPath());
}

/**
* 初始化command方式暴露的请求资源,并注入到上下文中
* @param basePath
* @param relativePath
* @param method
* @param level
* @param requestMapping
* @param command
*/
private static void initCommandRequest(String basePath, String relativePath, Method method, PermissionLevel level, RequestMapping requestMapping, String command) {
RequestMethod requestMethod = getRequestMethod(requestMapping, RequestMethod.POST);
PermissionRequest permissionRequest = buildCommandRequest(basePath, relativePath, method, level, requestMethod, command);
if (COMMAND_PREQUESTS.containsKey(command)) {
PermissionRequest oldPermissionRequest = COMMAND_PREQUESTS.get(command);
throw new PermissionInitException(String.format("command[%s] 已经被请求%s定义,不可再请求%s上重复使用。", command, oldPermissionRequest.getRequestPath(), permissionRequest.getRequestPath()));
}
URL_METHOD_PREQUESTS.put(permissionRequest.getMethodRequestPath(), permissionRequest);
COMMAND_PREQUESTS.put(command, permissionRequest);
}

/**
* 构建请求资源
* @param basePath
* @param relativePath
* @param method
* @param level
* @param requestMethod
* @return
*/
private static PermissionRequest buildUrlRequest(String basePath, String relativePath, Method method, PermissionLevel level, RequestMethod requestMethod) {
return buildRequest(basePath, relativePath, method, level, requestMethod, "", RequestType.URL);
}

/**
* 构建请求资源
* @param basePath
* @param relativePath
* @param method
* @param level
* @param requestMethod
* @param command
* @return
*/
private static PermissionRequest buildCommandRequest(String basePath, String relativePath, Method method, PermissionLevel level, RequestMethod requestMethod, String command) {
return buildRequest(basePath, relativePath, method, level, requestMethod, command, RequestType.COMMAND);
}


/**
* 构建请求资源
* @param basePath
* @param relativePath
* @param method
* @param level
* @param requestMethod
* @param command
* @param requestType
* @return
*/
private static PermissionRequest buildRequest(String basePath, String relativePath, Method method, PermissionLevel level, RequestMethod requestMethod, String command, RequestType requestType) {
if (StringUtils.isNotBlank(basePath) && StringUtils.endsWith(basePath, PATH_SLIPT)) {
basePath = StringUtils.substring(basePath, 0, basePath.length() - 1);
}
if (StringUtils.isNotBlank(relativePath) && !StringUtils.startsWith(relativePath, PATH_SLIPT)) {
relativePath = PATH_SLIPT + relativePath;
}
String reqPath = basePath + relativePath;
String methodReqPath = createMethodReqPath(requestMethod, reqPath);
PermissionRequest request = new PermissionRequest();
request.setRequestMethod(requestMethod);
request.setBasePath(basePath);
request.setRelativePath(relativePath);
request.setRequestPath(reqPath);
request.setMethodRequestPath(methodReqPath);
request.setRequestType(requestType);
request.setCommand(command);
request.setLevel(level);
request.setClazz(method.getDeclaringClass());
request.setMethodName(method.getName());
return request;
}

/**
* 获取请求相对地址,本扩张器暂时对多映射地址请求不处理
* @param requestMapping
* @return
*/
private static String getRelativePath(RequestMapping requestMapping) {
String[] relativePaths = requestMapping.value();
if (relativePaths == null || relativePaths.length == 0) {
return "";
}
if (relativePaths.length > 1) {
logger.warn("暂时不支持对多映射");
}
return relativePaths[0];
}

private static RequestMethod getRequestMethod(RequestMapping requestMapping, RequestMethod defMethod) {
RequestMethod[] methods = requestMapping.method();
if (methods == null || methods.length == 0) {
return defMethod;
}
if (methods.length > 1) {
logger.warn("暂时不支持对多请求Method");
}
return methods[0];
}

/**
* 拼装MethodReqPath
* @param method
* @param reqPath
* @return
*/
public static String createMethodReqPath(RequestMethod method, String reqPath) {
if (method != null) {
return method.toString() + "," + reqPath;
} else {
return reqPath;
}
}

/**
* 拼装MethodReqPath
* @param method
* @param reqPath
* @return
*/
public static String createMethodReqPath(String method, String reqPath) {
return method.toUpperCase() + "," + reqPath;
}

/**
* 通过api请求地址和请求方式,判断是否为通过http暴露的请求资源
* @param apiReq
* @param method
* @return
*/
public static boolean checkIsUrlReq(String apiReq, String method) {
if (StringUtils.isBlank(apiReq)) {
return false;
}
if (URL_REQS.contains(apiReq)) {
return true;
}
String methodReqPath = createMethodReqPath(method, apiReq);
PermissionRequest permissionRequest = URL_METHOD_PREQUESTS.get(methodReqPath);
if (permissionRequest == null) {
throw new PermissionRequestException(String.format("请求资源[%s]配置未找到", methodReqPath));
}
return Objects.equals(RequestType.URL, permissionRequest.getRequestType());
}

/**
* 检查请求是否合法
* @param requestUrl
* @return
*/
public static boolean checkIsLegalReq(String requestUrl) {
boolean fls = requestUrl.equals("/finance/api");
boolean ils = requestUrl.equals("/inventory/api");
boolean pls = requestUrl.equals("/purchase/api");
boolean sls = requestUrl.equals("/sale/api");
boolean tls = requestUrl.equals("/template/api");
return tls || pls || ils || fls || sls || requestUrl.equals("/api") || requestUrl.equals("/data");
}

/**
* 通过command配置集合重置上下文中的资源权限级别
* @param commands
* @param level
*/
public static void resetCommand(Collection<String> commands, PermissionLevel level) {
if (CollectionUtils.isNotEmpty(commands)) {
for (String command : commands) {
PermissionRequest request = PermissionContext.COMMAND_PREQUESTS.get(command);
if (request != null) {
request.setLevel(level);
} else {
logger.warn("未找到command:{}指定的请求资源定义", command);
}
}
}
}

/**
* 根据HttpServletRequest获取系统中定义的请求资源信息
* @param request
* @return
*/
public static PermissionRequest getPermissionRequest(HttpServletRequest request) throws PermissionRequestException {
String sourceRequestUrl = request.getRequestURI();
String requestUrl = getApiPath(sourceRequestUrl);
String method = request.getMethod();
String command = (String) request.getAttribute(AuthorizationConstants.REQUEST_COMMAND);
logger.info("command=" + command);
if (StringUtils.isNotBlank(command) && PermissionContext.COMMAND_PREQUESTS.containsKey(command)) {
return PermissionContext.COMMAND_PREQUESTS.get(command);
}
String methodRequestPath = PermissionContext.createMethodReqPath(method, requestUrl);
if (PermissionContext.URL_METHOD_PREQUESTS.containsKey(methodRequestPath)) {
return PermissionContext.URL_METHOD_PREQUESTS.get(methodRequestPath);
} else if (PermissionContext.URL_METHOD_PREQUESTS.containsKey(requestUrl)) {
return PermissionContext.URL_METHOD_PREQUESTS.get(requestUrl);
} else if (PermissionContext.URL_REQS.contains(methodRequestPath)) {
PermissionRequest permissionRequest = new PermissionRequest();
permissionRequest.setRequestPath(methodRequestPath);
permissionRequest.setRequestType(RequestType.URL);
return permissionRequest;
} else if (PermissionContext.URL_REQS.contains(requestUrl)) {
PermissionRequest permissionRequest = new PermissionRequest();
permissionRequest.setRequestPath(requestUrl);
permissionRequest.setRequestType(RequestType.URL);
return permissionRequest;
} else if (PermissionContext.URL_REQS.contains(sourceRequestUrl)) {
PermissionRequest permissionRequest = new PermissionRequest();
permissionRequest.setRequestPath(sourceRequestUrl);
permissionRequest.setRequestType(RequestType.URL);
return permissionRequest;
}
return null;
}

/**
* 合并命令配置信息
* @param commandConfig
*/
public static void mergeConfigCommand(Map<Object, Object> commandConfig) {
if (!org.springframework.util.CollectionUtils.isEmpty(commandConfig)) {
for (Map.Entry<Object, Object> config : commandConfig.entrySet()) {
String command = config.getKey().toString();
String methodRequest = trimMethodRequest(config.getValue().toString());
// 更新访问权限类型
if (StringUtils.isNotBlank(command) && StringUtils.isNotBlank(methodRequest)) {
PermissionRequest permissionRequest = getPermissionRequest(methodRequest);
PermissionRequest commandRequest = COMMAND_PREQUESTS.get(command);
if (permissionRequest == null) {
logger.error(String.format("未找到命令%s所对应的controller 处理方法", command));
} else if (StringUtils.isNotBlank(permissionRequest.getCommand()) &&
commandRequest != null && commandRequest != permissionRequest
) {
throw new PermissionInitException(String.format("command[%s]已经被请求%s定义,不可再请求%s上重复使用。", command
, commandRequest.getRequestPath(), methodRequest));
} else {
PermissionRequest currentPermission;
if (!Objects.equals(permissionRequest.getMethodRequestPath(), methodRequest)) {
currentPermission = new PermissionRequest();
BeanUtils.copyProperties(permissionRequest, currentPermission);
currentPermission.setMethodRequestPath(methodRequest);
currentPermission.setRequestMethod(getRequestMethod(methodRequest));
} else {
currentPermission = permissionRequest;
String sourceCommand = permissionRequest.getCommand();
if (StringUtils.isNotBlank(sourceCommand)) {
COMMAND_PREQUESTS.remove(sourceCommand);
}
}
currentPermission.setCommand(command);
currentPermission.setRequestType(RequestType.COMMAND);
currentPermission.setLevel(PermissionLevel.COMMAND_PERMISSION);
COMMAND_PREQUESTS.put(command, currentPermission);
}
}
}
}
}

/**
* 将资源权限定义解析为标准格式:将资源权限定义中存在的空格去掉和http请求转为大写
* @param methodRequest
* @return
*/
private static String trimMethodRequest(String methodRequest) {
String[] methodRequestConfigs = methodRequest.split(",");
if (methodRequestConfigs.length < 2) {
return "";
}
return StringUtils.trim(methodRequestConfigs[0]) + "," + StringUtils.trim(methodRequestConfigs[1]);
}

public static PermissionRequest getPermissionRequest(String methodRequest) {
if (StringUtils.isBlank(methodRequest)) {
return null;
}
PermissionRequest permissionRequest = URL_METHOD_PREQUESTS.get(methodRequest);
if (permissionRequest == null && StringUtils.contains(methodRequest, ",")) {
String url = StringUtils.split(methodRequest, ",")[1];
permissionRequest = URL_METHOD_PREQUESTS.get(url);
}
return permissionRequest;
}

/**
* 因为通过servlet注册进来的url直连是对所有的请求方式都为url直连,
* <p>
* 而基于servlet注册进来的url的优先级更高,所以需要重置通过注解设置的访问配置
* @param urlRequest
*/
public static void resetUrlRequest(String urlRequest) {
if (!URL_REQS.contains(urlRequest)) {
// 1.添加到url直连集合中
URL_REQS.add(urlRequest);
}
resetOrAddToUrlRequest(urlRequest);
for (RequestMethod method : RequestMethod.values()) {
String methodRequestPath = createMethodReqPath(method, urlRequest);
resetToUrlRequest(methodRequestPath);
}
}

private static void resetToUrlRequest(String methodRequestPath) {
if (URL_METHOD_PREQUESTS.containsValue(methodRequestPath)) {
PermissionRequest request = URL_METHOD_PREQUESTS.get(methodRequestPath);
request.setRequestType(RequestType.URL);
if (StringUtils.isNotBlank(request.getCommand())) {
COMMAND_PREQUESTS.remove(request.getCommand());
}
}
}

private static void resetOrAddToUrlRequest(String methodRequestPath) {
if (URL_METHOD_PREQUESTS.containsValue(methodRequestPath)) {
PermissionRequest request = URL_METHOD_PREQUESTS.get(methodRequestPath);
request.setRequestType(RequestType.URL);
if (StringUtils.isNotBlank(request.getCommand())) {
COMMAND_PREQUESTS.remove(request.getCommand());
}
} else {
PermissionRequest request = new PermissionRequest();
request.setMethodRequestPath(methodRequestPath);
request.setRequestType(RequestType.URL);
URL_METHOD_PREQUESTS.put(methodRequestPath, request);
}
}

public static boolean containsCommand(String command) {
return COMMAND_PREQUESTS.containsKey(command);
}

public static PermissionRequest getPermissionByCommand(String command) {
return COMMAND_PREQUESTS.get(command);
}

public static Collection<PermissionRequest> getAllPrequests() {
return URL_METHOD_PREQUESTS.values();
}

private static RequestMethod getRequestMethod(String methodRequest) {
if (StringUtils.contains(methodRequest, ",")) {
String methodStr = StringUtils.split(methodRequest, ",")[0];
for (RequestMethod method : RequestMethod.values()) {
if (method.toString().equals(methodStr.trim().toUpperCase())) {
return method;
}
}
}
return RequestMethod.POST;
}

public static String getApiPath() {
return API_PATH;
}

public static void setApiPath(String apiPath) {
API_PATH = apiPath;
}

public static String getApiPath(String urlRequest) {
urlRequest = StringUtils.trim(urlRequest);
if (StringUtils.startsWith(urlRequest, API_PATH)) {
return StringUtils.substring(urlRequest, API_PATH.length());
}
return urlRequest;
}

}

自定义统一请求处理分发器:

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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
import com.alibaba.fastjson.JSONObject;
import com.lixiang.permissionlevel.config.*;
import com.lixiang.permissionlevel.exception.CommondNotFoundException;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.DispatcherServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import static com.lixiang.permissionlevel.config.PermissionContext.checkIsLegalReq;
import static com.lixiang.permissionlevel.config.PermissionContext.checkIsUrlReq;

@Component
public class CustomDispatcherServlet extends DispatcherServlet {
public static final String QUOT = "&quot;";
private static final long serialVersionUID = -3983568685367864966L;
public static final String REQUEST_COMMAND = "request_command";
private static Logger LOGGER = LoggerFactory.getLogger(CustomDispatcherServlet.class);
private static final List<String> PARAMS = new ArrayList<String>();

static {
PARAMS.add("announcementContent");
PARAMS.add("xmlFilePath");
}

@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
String requestUrl = request.getRequestURI();
String method = request.getMethod();
try {
request.setCharacterEncoding("UTF-8");
if (!checkIsUrlReq(PermissionContext.getApiPath(requestUrl), method) && checkIsLegalReq(requestUrl)) {
// 非过滤URL
RequestEntiyFace requestEntiyface = null;
RequestEntiy requestEntiy = getRequestEntiy(request);
if (requestEntiy != null) {
// 请求参数可以正常解析
request.setAttribute(REQUEST_COMMAND, requestEntiy.getCommand());
String command = requestEntiy.getCommand();
LOGGER.info("请求命令:" + command);
if (StringUtils.isBlank(command)) {
throw new CommondNotFoundException(String.format("请求[%s,%s]不为url直接访问的请求,请求参数中不存在command",
method, requestUrl));
}
if (!PermissionContext.containsCommand(command)) {
throw new CommondNotFoundException("命令:" + command);
}
requestEntiyface = doRequestPath(requestEntiy);
if (requestEntiyface != null) {
request = new HttpResquestWrapper(request, requestEntiyface);
}
}
}
super.doService(request, response);
} catch (Exception e) {
LOGGER.error(String.format("请求[%s,%s]分发失败", method, requestUrl), e);
ResponseEntity result = returnException("4001", "请求分发失败" + "," + e.getMessage(), "");
response.setContentType("application/json; charset=UTF-8");
try {
response.getWriter().write(JSONObject.toJSONString(result));
} catch (Exception e2) {
throw new IOException(e2.getMessage());
}
}
}

protected RequestEntiyFace doRequestPath(RequestEntiy requestEntiy) {
RequestEntiyFace requestEntiyface = new RequestEntiyFace();
String command = requestEntiy.getCommand();
PermissionRequest request = PermissionContext.getPermissionByCommand(command);
if (request.getRequestMethod() != null) {
requestEntiyface.setMethod(request.getRequestMethod().toString());// 设置请求方法
}
requestEntiyface.setURI(request.getRequestPath());// 设置请求路径
Map<String, String> heads = new HashMap<String, String>();
heads.put("x-openerp-token", requestEntiy.getAccessToken());
requestEntiyface.setHeads(heads);
if (StringUtils.startsWith(requestEntiy.getData(), "{")) { // 说明data数据是对象
JSONObject json = JSONObject.parseObject(requestEntiy.getData());
json = (JSONObject) objectFilter(json);
requestEntiy.setData(json.toJSONString());
Map<String, String[]> params = new HashMap<String, String[]>();
for (String key : json.keySet()) {
String[] param = new String[1];
param[0] = json.get(key) + "";
params.put(key, param);
}
requestEntiyface.setGetParam(params);
}
requestEntiyface.setBodyData(requestEntiy.getData());
return requestEntiyface;
}


private RequestEntiy getRequestEntiy(HttpServletRequest request) throws Exception {
String encode = "UTF-8";
InputStream inputStream = request.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] tmp = new byte[2048];
int i = inputStream.read(tmp);
while (i > 0) {
baos.write(tmp, 0, i);
i = inputStream.read(tmp);
}
String value = new String(baos.toByteArray(), encode);
if (value != null && !"".equals(value)) {
RequestEntiy requestEntiy = JSONObject.parseObject(value, RequestEntiy.class);
requestEntiy.setData(requestEntiy.getData());
return requestEntiy;
}
return null;
}

public static String formatHtml(String needToFormat) {
if (needToFormat != null && needToFormat.length() > 0) {
needToFormat = needToFormat.replace("<script>", "");
needToFormat = needToFormat.replace("&", "&amp;");
needToFormat = needToFormat.replace("\"", QUOT);
needToFormat = needToFormat.replace("“", QUOT);
needToFormat = needToFormat.replace("”", QUOT);
needToFormat = needToFormat.replace("<", "&lt;");
needToFormat = needToFormat.replace(">", "&gt;");
needToFormat = needToFormat.replace("'", "&#39;");
needToFormat = needToFormat.replace("\r\n", "");
return needToFormat;
}
return "";
}

private Object objectFilter(Object json) {
Object result = null;
if (json == null) {
result = "";
} else if (json instanceof JSONObject) {
JSONObject jsono = (JSONObject) json;
Set<String> keys = jsono.keySet();
if (!keys.isEmpty()) {
for (String k : keys) {
if (PARAMS.indexOf(k) != -1) {
continue;
}
jsono.put(k, objectFilter(jsono.get(k)));
}
}
result = jsono;
} else if (json instanceof String) {
result = formatHtml(json.toString());
} else {
result = json;
}
return result;
}

private ResponseEntity returnException(String code, String mess, Object data) {
ResponseEntity responseEntity = new ResponseEntity();
responseEntity.setCode(Integer.parseInt(code));
Calendar calendar = Calendar.getInstance(Locale.CHINA);
responseEntity.setTimeStamp(calendar.getTimeInMillis());
responseEntity.setResult("exception");
responseEntity.setMess(mess);
responseEntity.setData(data);
return responseEntity;
}

}

全局配置类:

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
import com.lixiang.permissionlevel.config.CommandConfigLoader;
import com.lixiang.permissionlevel.handler.CustomDispatcherServlet;
import com.lixiang.permissionlevel.handler.UserAuthInterceptor;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.filter.CharacterEncodingFilter;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.HashSet;
import java.util.Set;

/**
* 应用自动配置
*/
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true)
// 当前类不能在basePackages描到的包下, 否则会出现死循环
@ComponentScan(basePackages = {"com.lixiang.permissionlevel"})
@EnableAsync
public class AppAutoConfiguraction implements WebMvcConfigurer {

private static String apiPath = "/api";
// static {
// PermissionContext.setApiPath(apiPath);
// BeanFactory.init();
// }

@Bean
public ServletRegistrationBean restServlet() { // 注解扫描上下文
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
// 通过构造函数指定dispatcherServlet的上下文
CustomDispatcherServlet customDispatcherServlet = new CustomDispatcherServlet();
customDispatcherServlet.setApplicationContext(applicationContext);
// 用ServletRegistrationBean包装servlet
ServletRegistrationBean registrationBean = new ServletRegistrationBean();
registrationBean.setLoadOnStartup(1);
registrationBean.setServlet(customDispatcherServlet);
// 指定urlmapping
registrationBean.addUrlMappings("/*");
// 指定name,如果不指定默认为dispatcherServlet
registrationBean.setName("customDispatcherServlet");
return registrationBean;
}

@Bean
public CommandConfigLoader commandConfigLoader(){
Set<String> exculdeUrls = new HashSet<>();
exculdeUrls.add("/api/websocket");
return new CommandConfigLoader("dispatcher.properties", exculdeUrls);
}

@Override
public void addInterceptors(InterceptorRegistry registry) {
InterceptorRegistration addInterceptor = registry.addInterceptor(new UserAuthInterceptor());
addInterceptor.excludePathPatterns("/webap/**", "/css/**", "/js/**","/images/**","/user");
// super.addInterceptors(registry);
}

/**
* 处理字符编码的Filter一定要在其他Filter之前,不然其他Filter已经获取的请求参数,此时再去处理字符编码没有任何意义
* @return
*/
@Bean
public CharacterEncodingFilter setCharacterEncodingFilter(){
return new CharacterEncodingFilter("UTF-8", true, true);
}

}

用户访问权限请求拦截器:

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
import com.alibaba.dubbo.common.utils.StringUtils;
import com.lixiang.permissionlevel.config.PermissionContext;
import com.lixiang.permissionlevel.config.PermissionRequest;
import com.lixiang.permissionlevel.enums.PermissionLevel;
import com.lixiang.permissionlevel.enums.RequestType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;

@Component
public class UserAuthInterceptor implements HandlerInterceptor {

private Logger logger = LoggerFactory.getLogger(this.getClass());
private static final String LOGINER = ",登录人=";
private static final String INTERCEPTOR_REQUEST = "Interceptor-请求命令:";
private static final String RESULT_MSG = ",执行结果:阻止。理由:会员账户";
private static final String ACCOUNT_MSG = "您的会员账户";
private static final String USER_MSG = "尊贵的用户,";

//初始化当前访问请求资源权限定义信息和当前登录用户信息方法
private void initUserToSaasParameter(HttpServletRequest request, HttpServletResponse response, PermissionRequest permissionRequest) {

}

// 预处理
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// swagger接口不进行拦截
if (request.getRequestURI().contains("swagger") || request.getRequestURI().contains("csrf") || request.getRequestURI().contains("error") || request.getRequestURI().contains("api-docs") || request.getRequestURI().contains("webjars") || request.getRequestURI().contains("doc.html") || request.getRequestURI().contains("webap") || request.getRequestURI().contains("swagger-resources")) {
return true;
}
PermissionRequest permissionRequest = PermissionContext.getPermissionRequest(request);
long checkLogin = System.currentTimeMillis();
long checkLoginEnd = System.currentTimeMillis();
logger.info("====登录信息校验耗时:{}ms", checkLoginEnd - checkLogin);
if (permissionRequest == null) {
return false;
}
String command = permissionRequest.getCommand();
logger.info("-----preHandle--->>>command-----" + command);
request.setCharacterEncoding("UTF-8");
// 判断是否不过滤的请求
if (Objects.equals(PermissionLevel.ALL, permissionRequest.getLevel()) || Objects.equals(PermissionLevel.OUT_INTERFACE, permissionRequest.getLevel())) {
return true;
} else if (Objects.equals(permissionRequest.getRequestType(), RequestType.URL)) {
return true;
}
if (Objects.equals(PermissionLevel.INNER_USER, permissionRequest.getLevel())) {
return true;
}
return true;
}

// 后处理(未进行页面渲染)
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
logger.info("-----postHandle-----");
}

// 返回处理(已经渲染了页面)
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
logger.info("-----afterCompletion-----");
}

public String getIp(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
// 多次反向代理后会有多个ip值,第一个ip才是真实ip
int index = ip.indexOf(",");
if (index != -1) {
return ip.substring(0, index);
} else {
return ip;
}
}
ip = request.getHeader("X-Real-IP");
if (StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {
return ip;
}
return request.getRemoteAddr();
}

}