解决方案:
先直接说下解决方案
,权限点设计成如下:
/api/books/{id:\d*}
问题描述:
获取书本详情的标准restful路由,一般是这样的/api/books/12
, 12即该book的id,如果需要拥有访问该路由的权限,一般可以这样设计/api/books/*
。
但是如果类似有一个获取书本封面的请求,比如:/api/books/getCover
,那么如果给了/api/books/*
这样的权限的话,getCover这个也可以请求成功,就无法区分了。
源码分析:
请求地址和权限点匹配判断代码:
if(antPathMatcher.match(permission.getRequestUrl(),requestUri)){
...
}
匹配代码的源码如下:
那我们来分析下matchStrings
这个方法
private boolean matchStrings(String pattern, String str,
@Nullable Map<String, String> uriTemplateVariables) {
return getStringMatcher(pattern).matchStrings(str, uriTemplateVariables);
}
接下来看下getStringMatcher
protected AntPathStringMatcher getStringMatcher(String pattern) {
AntPathStringMatcher matcher = null;
Boolean cachePatterns = this.cachePatterns;
if (cachePatterns == null || cachePatterns.booleanValue()) {
matcher = this.stringMatcherCache.get(pattern);
}
if (matcher == null) {
matcher = new AntPathStringMatcher(pattern, this.caseSensitive);
if (cachePatterns == null && this.stringMatcherCache.size() >= CACHE_TURNOFF_THRESHOLD) {
// Try to adapt to the runtime situation that we're encountering:
// There are obviously too many different patterns coming in here...
// So let's turn off the cache since the patterns are unlikely to be reoccurring.
deactivatePatternCache();
return matcher;
}
if (cachePatterns == null || cachePatterns.booleanValue()) {
this.stringMatcherCache.put(pattern, matcher);
}
}
return matcher;
}
接下来看AntPathStringMatcher
的构造函数:
public AntPathStringMatcher(String pattern, boolean caseSensitive) {
this.rawPattern = pattern;
this.caseSensitive = caseSensitive;
StringBuilder patternBuilder = new StringBuilder();
Matcher matcher = GLOB_PATTERN.matcher(pattern);
int end = 0;
while (matcher.find()) {
patternBuilder.append(quote(pattern, end, matcher.start()));
String match = matcher.group();
if ("?".equals(match)) {
patternBuilder.append('.');
}
else if ("*".equals(match)) {
patternBuilder.append(".*");
}
else if (match.startsWith("{") && match.endsWith("}")) {
int colonIdx = match.indexOf(':');
if (colonIdx == -1) {
patternBuilder.append(DEFAULT_VARIABLE_PATTERN);
this.variableNames.add(matcher.group(1));
}
else {
String variablePattern = match.substring(colonIdx + 1, match.length() - 1);
patternBuilder.append('(');
patternBuilder.append(variablePattern);
patternBuilder.append(')');
String variableName = match.substring(1, colonIdx);
this.variableNames.add(variableName);
}
}
end = matcher.end();
}
// No glob pattern was found, this is an exact String match
if (end == 0) {
this.exactMatch = true;
this.pattern = null;
}
else {
this.exactMatch = false;
patternBuilder.append(quote(pattern, end, pattern.length()));
this.pattern = Pattern.compile(patternBuilder.toString(),
Pattern.DOTALL | (this.caseSensitive ? 0 : Pattern.CASE_INSENSITIVE));
}
}
看到这里基本上也就明白了,这里如果设定的*
号,这匹配的是.*
,那参考这里
else if (match.startsWith("{") && match.endsWith("}")) {
我们把获取详情的权限点设计成
/api/books/{id:\d*}
这样就可以匹配/api/books/12
或者/api/books/123
之类的详情,又不会包含/api/books/getCover
这样的接口。