【问题标题】:How to inject current HttpServletRequest into any Spring service?如何将当前的 HttpServletRequest 注入任何 Spring 服务?
【发布时间】:2019-07-30 13:44:34
【问题描述】:

我有一项服务需要读取特殊的 http 查询参数。因此我必须以某种方式访问​​当前的HttpServletRequest。由于我无法将请求作为参数传递,我必须以某种方式注入或读取它。

有两种可能:要么从RequestContextHolder获取请求,要么直接注入HttpServletRequest。什么是正确的?或者也许还有第三种选择?

@Service
public class MyUserDetailsService implements UserDetailsService {
    //TODO is that correct? scope?
    @Autowired
    private HttpServletRequest req;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        HttpServletRequest req = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
        req.getParameter("myparam");
   }
}

那是线程安全的吗,因为MyUserDetailsService 显然是单例的,而HttpServletRequest 应该是@RequestScope

【问题讨论】:

  • 为什么不读取控制器中的查询参数并通过服务传递它?
  • 因为UserDetailsService 是一个spring 类,因此我无法更改方法signatnure,因为它同样被内部spring 服务调用。
  • public UserDetails loadUserByUsername(String username, HttpServletRequest request) throws UsernameNotFoundException
  • 不要...您现在已经将您的服务层绑定到您的 Web 层...而依赖关系应该是相反的。你需要这个参数是为了什么?看起来您使用了错误的组件(UserdetailsService 来实现某些目标)。话虽这么说,它会起作用吗,是的,它会起作用,因为HttpServletRequest 是当前执行线程请求的实际代理,因此被隔离了。你应该这样做,不,你不应该这样做。
  • @M.Deinum 我正在用两个AuthenticationProvider 配置AuthenticationManagerBuilder。一种用于数据库查找,另一种用于旧文件查找。每个提供者都是一个DaoAuthenticationProvider,它使用自定义UserDetailsService,这是我从数据库或遗留文件中获取用户的策略。但在遗留服务查找的情况下,我需要查询中的附加参数来支持遗留查找。这就是为什么我必须访问HttpServletRequest,并且不能将其作为参数传递。

标签: java spring spring-mvc


【解决方案1】:

如果不想在控制器中使用@Autowired,可以通过这种方式获取@Autowired

HttpServletRequest request = 
((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
.getRequest();

【讨论】:

    【解决方案2】:

    我不知道你到底要训练什么,但你需要知道: 使用服务类中的 HttpServletRequest,您将遇到错误,因为 serverlet 请求和诸如服务之类的 Spring 组件在不同的范围内,并且当 HttpServletRequest 不在同一个线程中时,您无法访问它。 我有几乎相同的问题,就我而言,我需要在 JWT 标头中获取用户 ID 并保存以进行一些查询。 所以我在 Spring Security 链中创建了一个过滤器并使用了 SecurityContextHolder 类。 这是我的代码:

    @Slf4j
    public class JWTAuthenticationFilter extends GenericFilterBean {
    
        private String enviroment;
        private JWTAuthenticationService jwtAuthenticationService;
    
        public JWTAuthenticationFilter(String enviroment,
                                       JWTAuthenticationService jwtAuthenticationService) {
            this.enviroment = enviroment;
            this.jwtAuthenticationService = jwtAuthenticationService;
        }
    
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
    
            Claims jwtClaims = this.jwtAuthenticationService.getJWTClaims((HttpServletRequest) request);
    
            try {
                this.jwtAuthenticationService.checkJWT(enviroment, jwtClaims);
            } catch (UnauthorizedException ex){
                log.error("Token JWT Inválido");
                ((HttpServletResponse) response).sendError(401, ex.getMessage());
                return;
            }
    
            Authentication authentication = this.jwtAuthenticationService.getAuthentication(jwtClaims);
    
            SecurityContextHolder.getContext().setAuthentication(authentication);
            filterChain.doFilter(request, response);
        }
    }
    
    
    @Service
    public class AuthRequestService {
    
      public String getAuthorizationKey() {
        Claims claims = (Claims)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        return claims.get(KEY_CLAIM).toString();
      }
    
    }
    

    通过这种方法,我避免了线程错误,因为我正在使用 SecurityContextHolder 管理器的当前上下文。

    【讨论】:

      【解决方案3】:

      直接注入它应该可以工作,但它将是请求范围,而不是会话范围。

      注入请求范围的 bean 将导致创建代理。从不是源自DispatcherServlet 的线程访问该代理上的方法将导致抛出IllegalStateException,因为根据代理没有可用请求(实际上可能有零个或多个并发请求,但是确定性地绑定到一个是不可能的)。 但是,当从实际上是 HTTP 请求的一部分的线程访问代理的方法时,在您的情况下,可能进入 Controller->Service,它的行为就像是当前的 HttpServletRequest

      我不确定服务层是否是绑定HttpServletRequest 的正确位置,因为服务可能是从其他异步上下文访问的,尽管我不确定您的用例。 无论如何,只要您的架构定义明确并且依赖于HttpServletRequest bean 的类仅在请求线程/上下文中使用它,它就可以正常工作。

      总而言之,您应该做什么取决于您的用例。听起来您只能从 DispatcherServlet 中访问它,因此是安全的,但也许您应该将数据获取抽象(分离关注)到另一个将数据提供给 Service 类的类。

      【讨论】:

      • MyUserDetailsService 可能是单例,这种方法线程安全吗? (不是我的反对票)这个答案说是,但是没有引用支持文件:stackoverflow.com/questions/48574780/…
      • 请求与线程相关联。因此,如果正在访问您的服务的线程作为请求的一部分这样做,那么这将填充当前请求。如果来自另一个线程上下文,它将为空。
      • MyUserDetailsS​​ervice 很可能是 SIngleton 范围的。但是这里还有另一个答案表明它是线程安全的。 stackoverflow.com/questions/49680692/…
      • 我已经更新了我的答案。线程安全到底是什么意思?
      猜你喜欢
      • 2019-04-17
      • 2011-03-20
      • 2019-12-05
      • 2013-10-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多