java - Spring Boot 应用程序中对 REST 端点的自定义访问控制

标签 java spring spring-boot spring-security

首先,我了解@PreAuthorize注释和Expression based access control .

为了学习(以及出于多种原因),我想要的是:

  • 用户经过身份验证,其角色由 LDAP 目录提供,并在进行身份验证时填充到主体对象中。这是有效的,就像“它当前已在项目中就位”一样。
  • 注释(选择为@AccessControl)实现了访问控制完全与角色绑定(bind)的范例。可以在类/类型(REST Controller )上设置注释,在这种情况下,它适用于没有其他此类注释的任何方法,或方法(REST 端点)上。无论是限制还是放松授权约束,最深的注释总是获胜。
  • 访问控制逻辑比我从基于表达式的访问控制中获得的逻辑要复杂一些,将由另一段代码强制执行。它也更易于维护,但我想这只是在我看来。

例如,除了方法上的 @AccessControl 注释之外, Controller 还具有只能由角色列表中具有 ADMIN 的用户访问的端点:

@RestController
@RequestMapping("/admin")
@AccessControl({ Roles.ADMIN })
public class AdminController {
...
}

在过去几天阅读了大量内容之后,我目前的犹豫更多是关于是否编写自定义请求过滤器还是 AOP 建议。

使用自定义请求过滤器,我发现自己(暂时)无法确定请求将映射到哪个 Controller 的哪个方法。这些注释超出了我的能力范围。

有了 AOP 建议,我(尚)不知道如何用 403 Forbidden 状态回复客户端。

我的问题直接源于这两点:

  • 如何获取为客户端请求调用的 Controller 方法?
  • 当客户端未经授权时,如何从 AOP 建议返回 HTTP 状态代码并有效结束请求的处理?

最佳答案

结果比我最初想象的要简单得多,我使用 AOP 选项在不到一天的时间内完成了它。

这是AccessControl注释的代码,注释已删除:

@Documented
@Inherited
@Retention(RUNTIME)
@Target({ TYPE, METHOD })
public @interface AccessControl {

    public String[] value() default {};

}

它可以放置在 Controller 上(请参阅我原来的帖子/问题)或 Controller 方法上:

@RestController
@RequestMapping("/admin")
@AccessControl({ Roles.ADMIN })
public class AdminController {


    // This endpoint has open access: no authorization check will happen.
    @AccessControl
    @RequestMapping(value = "{id}", method = RequestMethod.GET)
    public DummyDto getNoCheck(@PathVariable Integer id) {

        return service.get(id);
    }

    // This endpoint specifically allows access to the "USER" role, which is lower 
    // than ADMIN in my hierarchy of roles.
    @AccessControl(Roles.USER)
    @RequestMapping(value = "{id}", method = RequestMethod.GET)
    public DummyDto getCheckUser(@PathVariable Integer id) {

        return service.get(id);
    }

    // The authorization check defaults to checking the "ADMIN" role, because there's
    // no @AccessControl annotation here.
    @RequestMapping(value = "{id}", method = RequestMethod.GET)
    public DummyDto getCheckRoleAdmin(@PathVariable Integer id) {

        return service.get(id);
    }

}

为了执行实际验证,必须回答两个问题:

  • 首先,要处理哪些方法?
  • 第二,检查什么?

问题1:要处理哪些方法?

对我来说,答案类似于“我的代码中的所有 REST 端点”。由于我的代码位于特定的根包中,并且由于我在 Spring 中使用 RequestMapping 注释,因此具体答案以切入点规范的形式出现:

@Pointcut("execution(@org.springframework.web.bind.annotation.RequestMapping * *(..)) && within(my.package..*)")

问题2:运行时到底检查什么?

我不会将整个代码放在这里,但基本上,答案在于将用户的角色与方法(如果方法本身没有访问控制规范,则为 Controller )所需的角色进行比较。

@Around("accessControlled()")
public Object process(ProceedingJoinPoint pjp) throws Throwable {
    ...
    // Get the roles specified in the access control rule that applies (from the method annotation, or from the controller annotation).
    // Get the user roles from the UserDetails previously saved when the user went through the authentication process.
    // Check authorizations: does the user have one role that is required? If no, throw an exception. If yes, don't do anything.
    // No exception has been thrown: let the method proceed and return its results.
}

在我最初的想法中困扰我的是异常(exception)。由于我已经有一个带有 @ControllerAdvice 注释的异常映射器类,因此我只是重用该类来将我的特定 AccessControlException 映射到 403 Forbidden 状态代码。

为了检索用户的角色,我使用 SecurityContextHolder.getContext().getAuthentication() 来恢复身份验证 token ,然后使用 authentication.getPrincipal() 来检索自定义用户详细信息对象,其中有一个我通常在身份验证过程中设置的 roles 字段。

上面的代码不能按原样使用(例如,会发生路径映射冲突),但这只是为了传达总体想法。

关于java - Spring Boot 应用程序中对 REST 端点的自定义访问控制,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50786762/

相关文章:

spring - Grails/Spring RememberMe对同一用户+ session 并发的多个浏览器的处理

java - Spring Boot多war应用集成测试

java - 如何使用带有内容的 spring boot Controller 下载 csv 文件?

java - JOOQ store() 不返回 ResultSet

java - 从 Elasticsearch 获取必填字段

java - 注入(inject)不适用于新的运算符(operator)

java - Spring Boot 和 ActiveMQ : Ignores broker-url and uses default localhost:61616

java - 尝试在 Android 中获取用户的 Facebook 个人资料图片时出现白色问号

java - Spring Boot数据与MongoDB——过滤子文档数组查询

java - Spring中实例DAI与Singleton Bean的交互