简体   繁体   English

Spring Boot:将请求范围的,延迟初始化的自定义User对象注入控制器

[英]Spring boot: inject a request-scoped, lazy-initialized custom User object into a controller

I'm building a Spring Boot application to provide a stateless REST API. 我正在构建一个Spring Boot应用程序来提供无状态REST API。 For security, we're using OAuth 2. My app receives a bearer-only token. 为了安全起见,我们使用OAuth2。我的应用程序收到仅承载令牌。

The user's information is stored in our database. 用户信息存储在我们的数据库中。 I can look it up using the injected Principal in the controller: 我可以在控制器中使用注入的Principal查找它:

@RequestMapping(...)
public void endpoint(Principal p) {
  MyUser user = this.myUserRepository.findById(p.getName());
  ...
}

To avoid this extra line of boilerplate, I would like to be able to inject the MyUser object directly into my controller method. 为了避免增加额外的内容,我希望能够将MyUser对象直接注入到我的控制器方法中。 How can I achieve this? 我该如何实现? (The best I've come up with so far is to create a Lazy, Request-scoped @Bean...but I haven't been able to get it working...) (到目前为止,我想出的最好的办法是创建一个懒惰的,请求范围的@Bean ...但是我无法使其正常工作...)

The Idiomatic Way 惯用方式

The idiomatic way in Spring Security is to use a UserDetailsService or implement your own : Spring Security中的惯用方式是使用UserDetailsService实现自己的

public class MyUserDetailsService implements UserDetailsService {
    @Autowired
    MyUserRepository myUserRepository;

    public UserDetails loadUserByUsername(String username) {
        return this.myUserRepository.findById(username);
    }
}

And then there are several spots in the Spring Security DSL where this can be deposited, depending on your needs. 然后,根据您的需求,在Spring Security DSL中可以存放多个位置。

Once integrated with the authentication method you are using (in this case OAuth 2.0), then you'd be able to do: 与您正在使用的身份验证方法集成(在本例中为OAuth 2.0)之后,您将能够执行以下操作:

public void endpoint(@AuthenticationPrincipal MyUser myuser) {

}

The Quick, but Less-Flexible Way 快速但不太灵活的方式

It's generally better to do this at authentication time (when the Principal is being ascertained) instead of at method-resolution time (using an argument resolver) as it makes it possible to use it in more authentication scenarios. 通常最好在身份验证时(确定Principal时)执行此操作,而不是在方法解析时(使用参数解析器)执行此操作,因为这样可以在更多身份验证方案中使用它。

That said, you could also use the @AuthenticationPrincipal argument resolver with any bean that you have registered, eg 也就是说,您也可以将@AuthenticationPrincipal参数解析器与已注册的任何bean一起使用,例如

public void endpoint(
    @AuthenticationPrincipal(expression="@myBean.convert(#this)") MyUser user) 
{

}

...

@Bean
public Converter<Principal, MyUser> myBean() {
    return principal -> this.myUserRepository.findById(p.getName())
}

The tradeoff is that this conversion will be performed each time this method is invoked. 折衷是每次调用此方法时都将执行此转换。 Since your app is stateless, this might not be an issue (since the lookup needs to be performed on each request anyway), but it would mean that this controller could likely not be reused in other application profiles. 由于您的应用程序是无状态的,因此这可能不是问题(因为无论如何都需要对每个请求执行查找),但这意味着该控制器可能无法在其他应用程序配置文件中重用。

You can achieve this by implementing HandlerMethodArgumentResolver. 您可以通过实现HandlerMethodArgumentResolver来实现。 For example: 例如:

Custom annotation: 自定义注释:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Version {
}

Implementation: 实现方式:

public class HeaderVersionArgumentResolver implements HandlerMethodArgumentResolver {

@Override
public boolean supportsParameter(MethodParameter methodParameter) {
    return methodParameter.getParameterAnnotation(Version.class) != null;
}

@Override
public Object resolveArgument(
  MethodParameter methodParameter, 
  ModelAndViewContainer modelAndViewContainer, 
  NativeWebRequest nativeWebRequest, 
  WebDataBinderFactory webDataBinderFactory) throws Exception {

    HttpServletRequest request 
      = (HttpServletRequest) nativeWebRequest.getNativeRequest();

    return request.getHeader("Version");
}
}

When you implement this you should add this as argument resolver: 实现此功能时,应将其添加为参数解析器:

@Configuration
public class WebConfig implements WebMvcConfigurer {

@Override
public void addArgumentResolvers(
  List<HandlerMethodArgumentResolver> argumentResolvers) {
    argumentResolvers.add(new HeaderVersionArgumentResolver());
}
}

Now we can use it as argument 现在我们可以将其用作参数

public ResponseEntity findByVersion(@PathVariable Long id, @Version String version) 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM