实际上,每个Web应用程序都应该有多个用户,每个用户都有一些数据-帖子,文档,消息等等。 最明显的事情是保护这些实体免遭非这些资源合法所有者的用户获取。
不幸的是,这不是最容易的事情。 我并不是说很难,它不像简单地返回资源那样直观。 当您是/record/{recordId}
端点时,您将立即执行对recordId的数据库查询。 只有这样,才需要检查此记录是否属于当前经过身份验证的用户。
框架在这里没有帮助,因为这种访问控制和所有权逻辑是特定于域的。 没有明显的通用方法来定义所有权。 它取决于实体模型和实体之间的关系。 在某些情况下,它可能非常复杂,需要在联接表中查找(对于多对多关系)。
但是您应该自动执行此操作,原因有两个。 首先,在每个端点/控制器方法上手动执行这些检查很繁琐,并且使代码难看。 其次,更容易忘记添加这些检查,尤其是在有新开发人员的情况下。
您可以在DAO的所有地方进行这些检查,但通常应尽早失败,因此这些检查应在控制器(端点处理程序)级别上进行。 对于Java和Spring,可以使用批注和HandlerInterceptor来自动执行此操作。 在使用任何其他语言或框架的情况下,也可以使用类似的方法-一种可插拔的方式来描述要检查的所有权关系。
以下是放置在每个控制器方法上的示例注释:
public @interface VerifyEntityOwnership {String entityIdParam() default "id";Class<?> entityType();
}
然后定义拦截器(当然,应将其配置为执行)
@Component
public class VerifyEntityOwnershipInterceptor extends HandlerInterceptorAdapter {private static final Logger logger = LoggerFactory.getLogger(VerifyEntityOwnershipInterceptor.class);@Autowiredprivate OrganizationService organizationService;@Autowiredprivate MessageService MessageService;@Autowiredprivate UserService userService;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {Authentication authentication = SecurityContextHolder.getContext().getAuthentication();// assuming spring-security with a custom authentication token typeif (authentication instanceof ApiAuthenticationToken) {AuthenticationData authenticationData = ((ApiAuthenticationToken) authentication).getAuthenticationData();UUID clientId = authenticationData.getClientId();HandlerMethod handlerMethod = (HandlerMethod) handler;VerifyEntityOwnership annotation = handlerMethod.getMethodAnnotation(VerifyEntityOwnership.class);if (annotation == null) {logger.warn("No VerifyEntityOwnership annotation found on method {}", handlerMethod.getMethod().getName());return true;}String entityId = getParam(request, annotation.entityIdParam());if (entityId != null) {if (annotation.entityType() == User.class) {User user = userService.get(entityId);if (!user.getClientId().equals(clientId)) {return false;}} else if (annotation.entityType() == Message.class) {Message record = messageService.get(entityId);if (!message.getClientId().equals(clientId) {return false;}} // .... more}}return true;}@SuppressWarnings("unchecked")private String getParam(HttpServletRequest request, String paramName) {String value = request.getParameter(paramName);if (value != null) {return value;}Map<String, String> pathVariables = (Map<String, String>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);return pathVariables.get(paramName);}
}
您会看到这假定每种类型都需要自定义逻辑。 如果您的模型很简单,则可以使之通用–使所有实体都使用它们都定义的getClientId()
方法实现某些Owned
接口。 然后只需要一个dao.get(id, entityClass);
并避免使用特定于实体的逻辑。
请注意,当方法上没有注释时,将显示警告-那里表示您可能已经忘记添加一个。 某些端点可能不需要所有权检查-对于它们,您可以具有特殊的@IgnoreEntityOwnership
批注。 关键是要做出有意识的决定,以不验证所有权,而不是忘记所有权并引入安全问题。
我的意思可能很明显。 但是我已经看到了许多这种遗漏的例子,包括生产政府项目。 正如我说的,框架不会强迫您考虑这一方面,因为它们不能以通用的方式做到这一点– Web框架通常与您的实体模型无关,而ORM与您的控制器无关。 有处理所有这些方面的综合框架,但是即使它们没有通用的机制 (至少我不知道)。
安全性包括对系统应用一套良好的实践和原则。 但是,它还包括一些程序和自动化程序,这些程序和自动化程序可以帮助开发人员和管理员不要忽略他们通常知道的东西,而会时不时地忘记。 而且,应用安全性原则越乏味,则一贯应用安全性原则的可能性就越大。
翻译自: https://www.javacodegeeks.com/2018/10/automate-access-control-user-entities.html