从线程调用的 Feign Client + Spring 安全性

Feign Client called from a thread + Spring Security

我有打电话给假客户的情况。它工作正常,直到我尝试将假装客户端放入一个线程(简化版)中:

@Autowired
UsuarioFeign feignUserClient;

....

final Runnable t = new Runnable() {
    @Override
    public void run() {
        try {
            feignUserClient.findByEmail("someEmail@address.com");
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
};
new Thread(t).start();

当我尝试这样做时,我得到(由真实版本打印,而不是上面的简化版本):

java.lang.NullPointerException
at br.alfa.tutoria.AlfaTutoriaApplication.apply(AlfaTutoriaApplication.java:46)
at feign.SynchronousMethodHandler.targetRequest(SynchronousMethodHandler.java:158)
at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:88)
at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:76)
at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:103)
at com.sun.proxy.$Proxy246.findByEmail(Unknown Source)
at br.alfa.tutoria.service.impl.TutoriaServiceImpl.atribuiFuncaoTutor(TutoriaServiceImpl.java:285)
at br.alfa.tutoria.service.impl.TutoriaServiceImpl.run(TutoriaServiceImpl.java:270)
at java.lang.Thread.run(Unknown Source)

feignUserClient 是@Autowired(我试过将它转为 final,并使用构造函数注入它,但没有任何区别 - 无论如何,当代码执行时它的值不为 null)。

更多信息:

Feign 接口:

@FeignClient(name = "authUsers", url = br.alfa.tutoria.config.Url.AUTH_SERVER)
public interface UsuarioFeign {
    @RequestMapping(value = "/user-search-by-email", method = RequestMethod.POST)
    public User findByEmail(String email);
}

另外一个class,调用feign接口的……无所谓。我在几个不同的 classes 中尝试了它(所有这些都用 @RestController@Service 注释)。如果我从线程中调用 UsuarioFeign.findByEmail,它就会停止工作。

最后,问题与 Spring 安全上下文有关。按照设计,根据 Spring Security docs,“安全性存储在每个线程的基础上 ”。这意味着如果您需要新线程中的安全上下文之类的东西,您将无法获得它。

在文档中,您可以找到一种将其作为参数传输到新线程的方法:

Runnable originalRunnable = new Runnable() {
    public void run() {
        // invoke secured service
    }
};

SecurityContext context = SecurityContextHolder.getContext();
DelegatingSecurityContextRunnable wrappedRunnable =
    new DelegatingSecurityContextRunnable(originalRunnable, context);

new Thread(wrappedRunnable).start();
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

public class SecurityContextRunnable implements Runnable {
    private Runnable runnable;
    private RequestAttributes requestAttributes;
    private SecurityContext securityContext;
    private SecurityContext originalSecurityContext;

    private SecurityContextRunnable(Runnable runnable) {
        this(runnable, RequestContextHolder.getRequestAttributes());
    }

    private SecurityContextRunnable(Runnable runnable, RequestAttributes requestAttributes) {
        this(runnable, requestAttributes, null);
    }

    private SecurityContextRunnable(Runnable runnable, RequestAttributes requestAttributes, SecurityContext securityContext) {
        try {
            if (securityContext == null) {
                securityContext = SecurityContextHolder.getContext();
            }
        } catch (Exception e) {

        }

        this.runnable = runnable;
        this.requestAttributes = requestAttributes;
        this.securityContext = securityContext;
    }

    @Override
    public void run() {
        this.originalSecurityContext = SecurityContextHolder.getContext();
        try {
            SecurityContextHolder.setContext(securityContext);
            RequestContextHolder.setRequestAttributes(requestAttributes);
            runnable.run();
        } finally {
            SecurityContext emptyContext = SecurityContextHolder.createEmptyContext();
            if (emptyContext.equals(originalSecurityContext)) {
                SecurityContextHolder.clearContext();
            } else {
                SecurityContextHolder.setContext(originalSecurityContext);
            }
            this.originalSecurityContext = null;
        }
    }

    public static SecurityContextRunnable create(Runnable runnable) {
        return new SecurityContextRunnable(runnable);
    }

    public static SecurityContextRunnable create(Runnable runnable, RequestAttributes requestAttributes) {
        return new SecurityContextRunnable(runnable, requestAttributes);
    }

    public static SecurityContextRunnable create(Runnable runnable, RequestAttributes requestAttributes, SecurityContext securityContext) {
        return new SecurityContextRunnable(runnable, requestAttributes, securityContext);
    }

}


Runnable originalRunnable = new Runnable() {
    @Override
    public void run() {
        try {
            feignUserClient.findByEmail("someEmail@address.com");
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
};

SecurityContextRunnable wrappedRunnable = SecurityContextRunnable.create(originalRunnable);
new Thread(wrappedRunnable).start();