Spring 安全性:针对特定服务绕过 @Secured

Spring Security : bypass @Secured for specific service

我目前正在我的应用程序中实现 Spring 安全性。我确实设法在我的服务上添加了 @Secured 注释,即从数据库中获取 getAllUsers() ,只要识别了用户,它就可以正常工作(取决于他的权限,他可以获取或不获取用户列表)。

但我有一个 @Scheduled 方法负责为所有用户编制索引,当它启动时它调用相同的受保护的 getAllUsers() 方法,并且由于未登录而明显崩溃:我得到以下异常:

org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext

我目前正在考虑一种可能的解决方案,即使用自定义注释标记内部方法,该注释将由自定义 AccessDecisionVoter 检索,允许调用者调用受保护的方法。

我正在寻找此类用例的最佳实践

因为方法是@Secured 并且spring 需要上下文中的安全身份验证对象。这是 AccessDecisionVoter Spring-security - AccessDecisionVoter-impl wont be invoked

的工作示例

或者如果你有过滤器或 smth 这将取决于用户上下文值这个应该没问题

@Scheduled
public void method() {
try {
    ScheduledAuthenticationUtil.configureAuthentication();
    // do work
}
catch(Exception e) {
    e.printStackTrace();
}
finally {
    ScheduledAuthenticationUtil.cleanAuthentication();
}
}


private static class ScheduledAuthenticationUtil {

    public static void configureAuthentication() {
        // inject auth obj into SecurityContextHolder
    }

    public static void cleanAuthentication() {
        // SecurityContextHolder clean authentication
    }
}

我假设您的服务 class 看起来像:

public class MyServiceImpl implements MyService {
    ...
    @Secured
    public Xxx getAllUsers() {
        ...
        // call DAO 
        ...
        return xxx;
    }
    ...
}

然后您从 @Scheduledclass 调用 myService.getAllUsers()

最简单的方法是拆分 getAllUsers 并使服务 class 继承自 2 个接口,一个包含安全方法,另一个包含可公开访问的版本:

public class MyServiceImpl implements MyService, MyScheduledService {
    ...
    @Secured
    public Xxx getAllUsers() {
        return restrictedGetAllUsers;
    }
    public Xxx restrictedGetAllUsers() {
        ...
        // call DAO 
        ...
        return xxx;
    }
    ...
}

public interface MyService {
    Xxx getAllUsers();
}

public interface MyScheduledService extends MyService {
    Xxx restrictedGetAllUsers();
}

然后在你的控制器中 class :

@Autowired MyService myService   => will call only getAllUsers()

在你的 @Scheduled class 中:

@Autowired MyScheduledService myService   => will call restrictedGetAllUsers()

所有这一切似乎过于复杂,但由于您的计划 class 和您的控制器没有理由以相同的方式调用服务方法,因此向它们提供具有不同安全要求的两个不同接口是有意义的。

我选择了 kxyz 答案,改进了 运行 一段代码的服务,方法是在 运行 设置代码之前设置所需的权限,并在代码为 运行 时放回以前的权限完成:

public void runAs(Runnable runnable, GrantedAuthority... authorities) {
    Authentication previousAuthentication = SecurityContextHolder.getContext().getAuthentication();

    configureAuthentication(authorities);
    try {
      runnable.run();
    } finally {
      configureAuthentication(previousAuthentication);
    }
  }

protected void configureAuthentication(GrantedAuthority... authorities) {
    Authentication authentication = new UsernamePasswordAuthenticationToken("system", null, Arrays.asList(authorities));
    configureAuthentication(authentication);
}

protected void configureAuthentication(Authentication authentication) {
    SecurityContextHolder.getContext().setAuthentication(authentication);
}

参考 PhilippeAuriach 的回答 - 有一个更好的方法 运行 具有授权的新线程 - 使用 spring 安全扩展 运行nable 方法,主线程的上下文被复制到委托中运行启用

public void authorizedExecute(Runnable runnable) {
    new Thread(new DelegatingSecurityContextRunnable(runnable)).start();
}