使用 REST API 的属性/字段级安全性
我正在为支持多租户授权模型的 REST API 构建概念证明.该模型不仅控制用户可以访问哪些对象,还控制对象中的字段.此模型的目标是确保租户管理员只能修改其租户,并且只能查看允许的对象属性.
I'm building a proof of concept for a REST API that supports a multi-tenant authorizations model. This model not only from controls which objects a user can access but also the fields in the object. The goal of this model is to ensure that a tenant admin can only modify their tenant as well as only see object properties that are permitted.
我有一个正在处理的现有代码库,可在 https 上公开获取://github.com/cypherkey/multi-tenant-rest-api.它基于示例 Spring OAUTH2 资源服务器项目.我编写了自己的字段级安全实现,它使用分析 DTO 和模型中字段的注释,如果用户有足够的权限,它会使用反射将数据从一个类复制到另一个类.虽然这似乎有效,但我想确保我走在正确的道路上.是否有更标准的 Spring 方法或可能实现此目的的其他框架?
I have an existing code base that I am working on and is available publicly at https://github.com/cypherkey/multi-tenant-rest-api. It is based on the sample Spring OAUTH2 resource server project. I wrote my own implementation of field level security that uses analyzes the annotations on the fields in the DTO and model and if the user has sufficient rights, it uses reflections to copy data from one class to another. While this seems to work, I want to ensure I'm going down the right path. Is there a more standard Spring approach or maybe another framework that might accomplish this?
我一直在研究 JSONViews.它们看起来可以用于序列化.我将为 SUPERADMIN、TENANTADMIN 和 USER 级别角色创建不同的视图.控制器将负责确定客户端是否可以访问该对象,而 JSONView 将负责过滤字段/属性.问题是我发现了一些支持序列化的示例,但不支持控制器级别的 POST/PUT 上的反序列化.例如:
I've been researching JSONViews. They look like they would work for serialization. I would create a different view for SUPERADMIN, TENANTADMIN, and USER level roles. The controller would be responsible for determining whether the client can access the object and the JSONView would be responsible for filtering the fields/properties. The problem is I have found a few examples of supporting this for serialization but not for de-serialization on a POST/PUT at the controller level. For example:
我发现这个问题是因为我偶然发现了同样的问题.我知道这是一个老问题,但我正在发布我的解决方案,所以也许处于相同情况的任何人都会发现它有帮助.我最终使用基于反射(用于更新请求)和 Genson 的 API 方法(用于读取请求)的混合解决方案解决了这个问题.
I found this question because I stumbled with the same problem. I know this is an old question, but I'm posting my solution so maybe anyone in the same situation could find it helpful. I finally resolved it using a mixed solution based on reflection (for update requests) and Genson's API methods (for read requests).
首先,我定义了一个类,该类将包含每个实体和每种角色的禁止字段的List
.当应用程序启动时,它读取包含此信息的配置文件并将其存储在此类的对象中.此对象包含在 Context
中,因此可从服务请求中访问此信息.
Firstable, I defined a class which would contain a List
of forbidden fields for each entity and each kind of role. When the application starts, it reads a config file which contains this information and stores it in an object of this class. This object is included to the Context
, so this information is accessible from the requests of the service.
当 GET 请求被调用时,实体被获取,但之后,一个 Genson
对象被创建如下:
When the GET requests is called, the entity is obtained, but after that, a Genson
object is created like this:
protected Genson buildRestrictedGenson(String rolename, ElementExcludedFields ef) {
List<String> excludedFields = null;
if(rolename.equals(Utils.ROL_ADMIN))
excludedFields = ef.getAdminUser();
else if(rolename.equals(Utils.ROL_BASIC))
excludedFields = ef.getBasicUser();
else
return new Genson();
GensonBuilder gb = new GensonBuilder();
for(String field : excludedFields)
gb.exclude(field);
return gb.create();
}
使用exclude
方法意味着当你序列化一个对象时,得到的String
不会包含禁止字段.因此,对于每个实体的每个 GET,我只需要调用此方法,将使用此 Genson
对象获取的对象序列化并将其包含在响应中.
Using exclude
method means that when you serialize an object, the String
obtained won't contain the forbidden fields. So for every GET of every entity, I just need to call this method, serialize the object obtained with this Genson
object and include it in the response.
更新有点复杂,因为我用Hibernate做数据持久化,它需要完整的对象来执行更新.因此,第一步是从数据库中获取原始对象.当我拥有它时,我可以使用反射来忽略禁止字段,将原始值分配给更新"对象的这个字段.像这样:
For updating is a bit more complex, because I use Hibernate for data persistance, and it needs the complete object to perform updates. So, the first step would be get the original object from database. When I have it, I can use reflection to ignore the forbidden fields, assigning the original values to this fields of the "updating" object. Something like this:
protected void excludeUpdateFields(T entity, int id, String rolename, ElementExcludedFields ef) {
T t = getService().get(id);
Iterable<Field> fields = ReflectionUtils.getAllFields(entity.getClass());
List<String> fieldList;
if(rolename.equals(Utils.ROL_ADMIN))
fieldList = ef.getAdminUser();
else if(rolename.equals(Utils.ROL_BASIC))
fieldList = ef.getBasicUser();
else
return;
for(Field field : fields) {
if(fieldList.contains(field.getName())) {
try {
field.setAccessible(true);
Object value = field.get(t);
field.set(entity, value);
} catch (IllegalArgumentException ex) {
Logger.getLogger(Controller.class.getName()).log(Level.SEVERE, null, ex);
} catch (IllegalAccessException ex) {
Logger.getLogger(Controller.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
}
调用此函数后,禁止字段的更新不会有任何影响,因为更新"对象将在这些字段中具有原始值.
After calling this function, the updating of the forbidden fields won't have any effect, because the "updating" object will have the original values in those fields.