后端springsecurity实现动态权限校验
在框架defaultsecurityfilterchain源码内打断点可以找到springsecurity的过滤器链可以看见一个叫authorizationfilter的过滤器

很明显这个叫authorizationmanager的应该是我们要找的玩意
直接去authorizationfilter内找这个类看他的源码可以发现check方法已经弃用,他推荐用的方法是authorize但这玩意也还是调用的check
@functionalinterface
public interface authorizationmanager<t> {
/**
* determines if access should be granted for a specific authentication and object.
* @param authentication the {@link supplier} of the {@link authentication} to check
* @param object the {@link t} object to check
* @throws accessdeniedexception if access is not granted
*/
default void verify(supplier<authentication> authentication, t object) {
authorizationdecision decision = check(authentication, object);
if (decision != null && !decision.isgranted()) {
throw new authorizationdeniedexception("access denied", decision);
}
}
/**
* determines if access is granted for a specific authentication and object.
* @param authentication the {@link supplier} of the {@link authentication} to check
* @param object the {@link t} object to check
* @return an {@link authorizationdecision} or null if no decision could be made
* @deprecated please use {@link #authorize(supplier, object)} instead
*/
@nullable
@deprecated
authorizationdecision check(supplier<authentication> authentication, t object);
/**
* determines if access is granted for a specific authentication and object.
* @param authentication the {@link supplier} of the {@link authentication} to
* authorize
* @param object the {@link t} object to authorize
* @return an {@link authorizationresult}
* @since 6.4
*/
@nullable
default authorizationresult authorize(supplier<authentication> authentication, t object) {
return check(authentication, object);
}
继续往下面看
可以看见他是进行了校验然后返回了一个布尔值
@override
public authorizationdecision check(supplier<authentication> authentication, t object) {
boolean granted = this.authorizationstrategy.isgranted(authentication.get());
return new authorizationdecision(granted);
}
代码实现
逻辑大概是通过传进来的接口路径然后匹配权限
@component
public class dynamicauthorizationmanager implements authorizationmanager<requestauthorizationcontext> {
@resource
private dynamicsecuritymetadatasource securitymetadatasource;
@override
public authorizationdecision check(supplier<authentication> authentication, requestauthorizationcontext context) {
httpservletrequest request = context.getrequest();
// 获取当前请求所需的权限
string url = request.getrequesturi();
string method = request.getmethod();
filterinvocation fi = new filterinvocation(string.valueof(request), url, method);
collection<configattribute> attributes = securitymetadatasource.getattributes(fi);
// 没有配置权限要求,允许访问
if (collectionutils.isempty(attributes)) {
return new authorizationdecision(true);
}
// 获取当前用户认证信息
authentication auth = authentication.get();
if (auth == null || !auth.isauthenticated()) {
return new authorizationdecision(false);
}
// 获取用户所拥有的权限
set<string> userpermissions = auth.getauthorities().stream()
.map(grantedauthority::getauthority)
.collect(collectors.toset());
// 判断是否有所需权限
boolean haspermission = attributes.stream()
.map(configattribute::getattribute)
.anymatch(userpermissions::contains);
return new authorizationdecision(haspermission);
}
}
getallconfigattributes和supports我大概看了一下直接复制粘贴的框架的源码以后万一有用呢
@component
public class dynamicsecuritymetadatasource implements filterinvocationsecuritymetadatasource {
@resource
private tpmenuservice menuservice;
private map<string, collection<configattribute>> configattributemap;
@postconstruct
public void loaddatasource() {
configattributemap = new hashmap<>();
list<tpmenu> menus = menuservice.list();
configattributemap = menus.stream()
.filter(menu -> stringutils.hastext(menu.getpath()) && stringutils.hastext(menu.getperms()))
.collect(collectors.tomap(
tpmenu::getpath,
menu -> {
list<configattribute> attributes = new arraylist<>();
attributes.add(new securityconfig(menu.getperms()));
return attributes;
}
));
}
@override
public collection<configattribute> getattributes(object object) throws illegalargumentexception {
string requesturl = ((filterinvocation) object).getrequesturl();
if (requesturl.contains("?")) {
requesturl = requesturl.substring(0, requesturl.indexof("?"));
}
int count = stringutils.countoccurrencesof(requesturl, "/");
if (count > 2) {
requesturl = requesturl.replaceall("/[^/]+$", "");
}
for (map.entry<string, collection<configattribute>> entry : configattributemap.entryset()) {
string pattern = entry.getkey();
if (new antpathmatcher().match(pattern, requesturl)) {
return entry.getvalue();
}
}
return null;
}
@override
public collection<configattribute> getallconfigattributes() {
set<configattribute> allattributes = new hashset<>();
configattributemap.values().foreach(allattributes::addall);
return allattributes;
}
@override
public boolean supports(class<?> clazz) {
return filterinvocation.class.isassignablefrom(clazz);
}
}
过滤器实现
然后记得security配置文件添加这个过滤器就ok了,配置文件可以看我另外一篇文章
@component
public class dynamicsecurityfilter extends onceperrequestfilter {
@resource
private dynamicsecuritymetadatasource securitymetadatasource;
@resource
private dynamicauthorizationmanager authorizationmanager;
@resource
private accessdeniedhandler accessdeniedhandler;
@override
protected void dofilterinternal(httpservletrequest request,
httpservletresponse response,
filterchain chain) throws servletexception, ioexception, ioexception {
if (shouldnotfilter(request)) {
chain.dofilter(request, response);
return;
}
try {
authentication authentication = securitycontextholder.getcontext().getauthentication();
requestauthorizationcontext context = new requestauthorizationcontext(request);
// 权限检查
authorizationdecision check = authorizationmanager.check(
() -> authentication,
context
);
if (check.isgranted()) {
chain.dofilter(request, response);
} else {
accessdeniedhandler.handle(request, response, new accessdeniedexception("权限不足"));
}
} catch (ioexception e) {
throw new runtimeexception(e);
} catch (servletexception e) {
throw new runtimeexception(e);
}
}
@override
protected boolean shouldnotfilter(httpservletrequest request) {
string path = request.getrequesturi();
return whitelist.stream()
.anymatch(pattern ->
pattern.endswith("/**")
? path.startswith(pattern.substring(0, pattern.length() - 3))
: pattern.equals(path)
);
}
}
debug重启可以看见我的过滤器已经添加进去了

如果有需要还可以直接去看官方demo
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论