关于在网关中白名单配置的使用
在项目里,经常会有需要添加白名单,黑名单的功能, 而一般的处理方式,都是在网关进行设置配置, Spring中提供的工具包类AntPathMatcher可以很方便处理.
1 说明
Spring为PathMatcher接口提供了默认实现类,即AntPathMatcher类. 且支持Ant风格路径匹配.
通配符 | 说明 |
---|---|
? | 匹配一个字符 |
* | 匹配0个或多个字符 |
** | 匹配0个或多个目录 |
此外,AntPathMatcher还支持{}参数匹配.
// true
antPathMatcher.match("/root/opt","/root/opt");
代码查看:
PathMatcher
public interface PathMatcher {
// 判断路径是否带模式
boolean isPattern(String path);
// 判断path是否完全匹配
boolean match(String pattern, String path);
// 判断path是否前缀匹配
boolean matchStart(String pattern, String path);
// 去掉路径开头的静态部分, 得到匹配的动态路径
String extractPathWithinPattern(String pattern, String path);
// 匹配路径中的变量
Map<String, String> extractUriTemplateVariables(String pattern, String path);
// 返回一个排序比较器,可对路径进行排序
Comparator<String> getPatternComparator(String path);
// 合并两个模式
String combine(String pattern1, String pattern2);
}
AntPathMatcher
public class AntPathMatcher implements PathMatcher {
public static final String DEFAULT_PATH_SEPARATOR = "/";
private static final int CACHE_TURNOFF_THRESHOLD = 65536;
private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{[^/]+?\\}");
private static final char[] WILDCARD_CHARS = new char[]{'*', '?', '{'};
private String pathSeparator;
private AntPathMatcher.PathSeparatorPatternCache pathSeparatorPatternCache;
private boolean caseSensitive = true;
private boolean trimTokens = false;
@Nullable
private volatile Boolean cachePatterns;
private final Map<String, String[]> tokenizedPatternCache = new ConcurrentHashMap(256);
final Map<String, AntPathMatcher.AntPathStringMatcher> stringMatcherCache = new ConcurrentHashMap(256);
public AntPathMatcher() {
this.pathSeparator = "/";
this.pathSeparatorPatternCache = new AntPathMatcher.PathSeparatorPatternCache("/");
}
// ... 省略
}
从上面构造可以得知, AntPathMatcher默认路径分隔符为“/”,而在匹配文件路径时,需要注意Windows下路径分隔符为“\”, Linux下为“/”.(所以在不同的操作系统来传递各自的文件分隔符)
isPattern方法
判断路径中是否有*或?,{, }, 存在则说明是模式,返回true,否则返回false.
public boolean isPattern(@Nullable String path) {
if (path == null) {
return false;
} else {
boolean uriVar = false;
for(int i = 0; i < path.length(); ++i) {
char c = path.charAt(i);
if (c == '*' || c == '?') {
return true;
}
if (c == '{') {
uriVar = true;
} else if (c == '}' && uriVar) {
return true;
}
}
return false;
}
}
match,matchStart,extractUriTemplateVariables方法
上面三个方法, 最后都调用到doMatch方法.
// match完全匹配
public boolean match(String pattern, String path) {
return this.doMatch(pattern, path, true, (Map)null);
}
// 前缀匹配
public boolean matchStart(String pattern, String path) {
return this.doMatch(pattern, path, false, (Map)null);
}
// 去掉路径开头的静态部分, 得到匹配的动态路径
public Map<String, String> extractUriTemplateVariables(String pattern, String path) {
Map<String, String> variables = new LinkedHashMap();
boolean result = this.doMatch(pattern, path, true, variables);
if (!result) {
throw new IllegalStateException("Pattern \"" + pattern + "\" is not a match for \"" + path + "\"");
} else {
return variables;
}
}
doMatch方法
/**
* @param pattern 模式
* @param path 路径
* @param fullMatch 是否需要完全匹配
* @param uriTemplateVariables 收集路径中的参数
*/
protected boolean doMatch(String pattern, @Nullable String path, boolean fullMatch, @Nullable Map<String, String> uriTemplateVariables) {
if (path != null && path.startsWith(this.pathSeparator) == pattern.startsWith(this.pathSeparator)) {
String[] pattDirs = this.tokenizePattern(pattern);
if (fullMatch && this.caseSensitive && !this.isPotentialMatch(path, pattDirs)) {
return false;
} else {
String[] pathDirs = this.tokenizePath(path);
int pattIdxStart = 0;
int pattIdxEnd = pattDirs.length - 1;
int pathIdxStart = 0;
// ...省略
}
protected String[] tokenizePattern(String pattern) {
String[] tokenized = null;
Boolean cachePatterns = this.cachePatterns;
if (cachePatterns == null || cachePatterns) {
tokenized = (String[])this.tokenizedPatternCache.get(pattern);
}
if (tokenized == null) {
tokenized = this.tokenizePath(pattern);
if (cachePatterns == null && this.tokenizedPatternCache.size() >= 65536) {
this.deactivatePatternCache();
return tokenized;
}
if (cachePatterns == null || cachePatterns) {
this.tokenizedPatternCache.put(pattern, tokenized);
}
}
return tokenized;
}
protected String[] tokenizePath(String path) {
return StringUtils.tokenizeToStringArray(path, this.pathSeparator, this.trimTokens, true);
}
tokenizePattern
方法最终也是调用 tokenizePath
方法,是在其基础上加了一层缓存。 tokenizePath
方法则是直接调用工具方法 tokenizeToStringArray
方法,与 String.split()
方法效果是差不多的。
2 案例
// 设置成可配置 一般从nacos配置中获取
List<String> whiteList = new ArrayList<>();
/**
*
* @param path
* @return 为空表示 白名单 放过
*/
@Nullable
public AntPathMatcher checkWhitelList(String path){
AntPathMatcher matcher = new AntPathMatcher();
for (String white : whiteList) {
if (matcher.match(white,path)){
return null;
}
}
return matcher;
}
3 说明
对于系统中添加白名单,黑名单等匹配功能, 可以使用Spring中提供的AntPathMatcher.使用比较灵活方便,其代码实现也直观.