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

时间:2018-06-10 18:20:23

标签: java spring spring-boot spring-security

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

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

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

作为示例,除了方法上的@AccessControl注释之外,控制器将具有只能在其角色列表中具有ADMIN的用户访问的端点:

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

我目前的犹豫不决,在过去几天阅读了很多内容后,更多的是关于是否要编写自定义请求过滤器或者更确切地说是AOP建议。

使用自定义请求过滤器,我发现自己无法(暂时)确定请求将映射到哪个控制器的哪个方法。注释不在我的手中。

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

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

  • 如何获取将为客户端请求调用的控制器方法?
  • 如何在AOP建议中返回HTTP状态代码,并且在客户端未获得授权时有效地结束处理请求?

2 个答案:

答案 0 :(得分:0)

如果您想要遵循AOP建议根目录,我想提供一种方法:

关于这一点,如果使用AOP:

  

如何从AOP建议中返回HTTP状态代码   当客户端没有时,有效地结束请求的处理   授权? 解决方案:

在您的方面课程中,使用at Advice请执行以下操作:

@Around("execution(* net.my.package.AdminController.*(..)) && args(.., principal)")
public ResponseEntity<?> processRequest(final ProceedingJoinPoint joinPoint, final Principal principal) {
    final String controllerMethodName = joinPoint.getSignature().getName();
    LOGGER.info("Controller Method name : {}", controllerMethodName);
    final boolean isAuthSuccessful = authenticationService.authenticate(principal);//Pass auth details here
    if(!isAuthSuccessful) {
        return ResponseEntity.status(HttpStatus.FORBIDDEN).body("Request declined"); //End request if auth failed
    } else {
        try {
            return (ResponseEntity<?>)joinPoint.proceed(); //Continue with request
        } catch (Throwable e) {
            LOGGER.error("Error In Aspect :", e);
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("failed request");
        }
    }
}

好吧,上面的代码提供了解决您所面临挑战的评论。但是要使此代码工作,请确保执行以下操作:

  1. 确保要拦截的所有控制器方法都返回ResponseEntity
  2. 您可以更改@Around方面,以便为@AccessControl注释使用值为@annotation的点剪切,并且可以自由链接@Around方面的条件
  3. 确保找到将数据传递到此方面的方法,以便您有办法验证用户凭据

答案 1 :(得分:0)

事实证明这比我最初想的要简单得多,并且我在不到一天的时间内使用AOP选项完成了它。

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

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

    public String[] value() default {};

}

它可以放在控制器上(参见我原来的帖子/问题)或控制器方法:

@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:要处理哪些方法?

对我而言,答案类似于&#34;我的代码中的所有REST端点&#34;。由于我的代码位于特定的根包中,并且由于我在Spring中使用RequestMapping批注,因此具体的答案以Pointcut规范的形式出现:

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

问题2:在运行时确切检查了什么?

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

@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.
}

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

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

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