Java Spring Boot의 요청 헤더에서 Bearer 토큰을 얻는 방법은 무엇입니까?

Nov 26 2020

안녕하세요 달성하려는 것은 자바 스프링 부트 RESTApi 컨트롤러의 프런트 엔드에서 제출 한 베어러 토큰을 가져오고 다른 마이크로 서비스에 가짜 클라이언트를 사용하여 다른 요청을 수행하는 것입니까? 여기 내가하는 일이야

위의 이미지는 우편 배달부에서 내 요청을 수행하는 방법이며 여기에 내 컨트롤러 코드가 있습니다.

@Operation(summary = "Save new")
@PostMapping("/store")
public ResponseEntity<ResponseRequest<TransDeliveryPlanning>> saveNewTransDeliveryPlanning(
        @Valid @RequestBody InputRequest<TransDeliveryPlanningDto> request) {

    TransDeliveryPlanning newTransDeliveryPlanning = transDeliveryPlanningService.save(request);

    ResponseRequest<TransDeliveryPlanning> response = new ResponseRequest<TransDeliveryPlanning>();

    if (newTransDeliveryPlanning != null) {
        response.setMessage(PESAN_SIMPAN_BERHASIL);
        response.setData(newTransDeliveryPlanning);
    } else {
        response.setMessage(PESAN_SIMPAN_GAGAL);
    }

    return ResponseEntity.ok(response);
}

내 서비스는 다음과 같습니다.

public TransDeliveryPlanning save(InputRequest<TransDeliveryPlanningDto> request) {
       Future<List<PartnerDto>> initPartners = execs.submit(getDataFromAccount(transDeliveryPlanningDtSoDtoPartnerIdsSets));

}

public Callable<List<PartnerDto>> getDataFromAccount(Set<Long> ids) {

    String tokenString = "i should get the token from postman, how do i get it to here?";
    List<PartnerDto> partnerDtoResponse = accountFeignClient.getData("Bearer " + tokenString, ids);
    
    return () -> partnerDtoResponse;
}

보시다시피, "tokenString"에 제가 질문 한 문자열을 넣었습니다. 우체부에서 어떻게 가져 오나요?

답변

10 stacker Dec 03 2020 at 14:58

제안 된 답변이 작동하지만 FeignClient호출 할 때마다 토큰을 전달하는 것이 가장 좋은 방법은 아닙니다. 가짜 요청에 대한 인터셉터를 만들 것을 제안하고 거기에서 토큰을 추출하여 RequestContextHolder요청 헤더에 직접 추가 할 수 있습니다. 이렇게 :

    @Component
    public class FeignClientInterceptor implements RequestInterceptor {
    
      private static final String AUTHORIZATION_HEADER = "Authorization";

      public static String getBearerTokenHeader() {
        return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getHeader("Authorization");
      }
    
      @Override
      public void apply(RequestTemplate requestTemplate) {

          requestTemplate.header(AUTHORIZATION_HEADER, getBearerTokenHeader());
       
      }
    }

이렇게하면 문제에 대한 깨끗한 솔루션을 얻을 수 있습니다

4 jccampanero Nov 29 2020 at 18:31

여기에 몇 가지 옵션이 있습니다.

예를 들어 요청 범위 빈 과 제안한대로 하나의 MVC 인터셉터를 사용할 수 있습니다.

기본적으로 토큰 값에 대한 래퍼를 정의해야합니다.

public class BearerTokenWrapper {
   private String token;

   // setters and getters
}

그런 다음 MVC 구현을 제공합니다 HandlerInterceptor.

public class BearerTokenInterceptor extends HandlerInterceptorAdapter {

  private BearerTokenWrapper tokenWrapper;

  public BearerTokenInterceptor(BearerTokenWrapper tokenWrapper) {
    this.tokenWrapper = tokenWrapper;
  }

  @Override
  public boolean preHandle(HttpServletRequest request,
          HttpServletResponse response, Object handler) throws Exception {
    final String authorizationHeaderValue = request.getHeader("Authorization");
    if (authorizationHeaderValue != null && authorizationHeaderValue.startsWith("Bearer")) {
      String token = authorizationHeaderValue.substring(7, authorizationHeaderValue.length());
      tokenWrapper.setToken(token);
    }
    
    return true;
  }
}

이 인터셉터는 MVC 구성에 등록되어야합니다. 예를 들면 :

@EnableWebMvc
@Configuration
public class WebConfiguration extends WebConfigurer { /* or WebMvcConfigurerAdapter for Spring 4 */

  @Override
  public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(bearerTokenInterceptor());
  }

  @Bean
  public BearerTokenInterceptor bearerTokenInterceptor() {
      return new BearerTokenInterceptor(bearerTokenWrapper());
  }

  @Bean
  @Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
  public BearerTokenWrapper bearerTokenWrapper() {
    return new BearerTokenWrapper();
  }

}

이 설정으로 Service해당 빈을 자동 연결하는 데 빈을 사용할 수 있습니다 .

@Autowired
private BearerTokenWrapper tokenWrapper;

//...


public TransDeliveryPlanning save(InputRequest<TransDeliveryPlanningDto> request) {
       Future<List<PartnerDto>> initPartners = execs.submit(getDataFromAccount(transDeliveryPlanningDtSoDtoPartnerIdsSets));

}

public Callable<List<PartnerDto>> getDataFromAccount(Set<Long> ids) {

    String tokenString = tokenWrapper.getToken();
    List<PartnerDto> partnerDtoResponse = accountFeignClient.getData("Bearer " + tokenString, ids);
    
    return () -> partnerDtoResponse;
}

유사한 솔루션이 여기에 스택 오버플로에서 제공되었습니다. 예를 들어이 관련 질문 을 참조하십시오 .

이 Spring 기반 접근 방식 외에도이 다른 stackoverflow 질문에 노출 된 솔루션과 유사한 것을 시도 할 수 있습니다 .

솔직히 나는 그것을 테스트 한 적이 없지만 Feign 클라이언트 정의에서 바로 요청 헤더 값을 제공 할 수있는 것 같습니다.

@FeignClient(name="AccountFeignClient")
public interface AccountFeignClient {    
    @RequestMapping(method = RequestMethod.GET, value = "/data")
    List<PartnerDto> getData(@RequestHeader("Authorization") String token, Set<Long> ids);
}

물론 Controller다른 Controllers가 확장 할 수 있는 공통점도 있습니다. 이것은 헤더와 제공된 HTTP 요청 Controller에서 베어러 토큰을 얻는 데 필요한 로직을 Authorization제공하지만, 제 생각에는 앞서 언급 한 솔루션 중 어느 것이 든 더 좋습니다.

3 f.trajkovski Nov 29 2020 at 19:06

비슷한 경우가있었습니다. 한 마이크로 서비스의 요청을 가로 채서 토큰을 가져 와서 새 ApiClient를 설정하고이 ApiClient를 사용하여 다른 마이크로 서비스에서 엔드 포인트를 호출했습니다. 하지만 가짜 클라이언트를 미리 구성 할 가능성이 있는지 정말 모르겠습니다. 할 수있는 한 가지는 DefaultApiFilter를 생성하고, 요청을 가로 채고, 데이터베이스에 토큰을 저장 (또는 일부 정적 변수, 일부 싱글 톤 클래스 또는 이와 유사한 것으로 설정) 한 다음이를 사용하려고 할 때 서비스 메서드를 호출하는 것입니다. FeignClient :

package com.north.config;

import org.springframework.stereotype.Component;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

@Component
public class DefaultApiFilter implements Filter {


@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, 
FilterChain filterChain) throws IOException, ServletException {
    HttpServletRequest req = (HttpServletRequest) servletRequest;

    String auth = req.getHeader("Authorization");

    //TODO if you want you can persist your token here and use it on other place

    //TODO This may be used for verification if it comes from the right endpoint and if you should save the token
    final String requestURI = ((RequestFacade) servletRequest).getRequestURI();

    filterChain.doFilter(servletRequest, servletResponse);
    }
}

doFilter메서드는 항상 엔드 포인트가 호출되기 전에 실행되고 나중에 엔드 포인트가 호출됩니다.

나중에를 호출 할 때이를 사용 accountFeignClient.getData("Bearer " + tokenString, ids);하여 데이터베이스 (또는 보관 한 다른 위치)에서 가져와 여기에서 설정할 수 있습니다.

1 KeVin Nov 29 2020 at 13:53

나는 대답을 얻었지만 더 나은 옵션을 기다릴 것이라고 생각합니다. 여기에 내 대답은 모든 컨트롤러에 @RequestHeader를 추가하여 내 토큰의 가치를 얻고 토큰을 가져와야 String token = headers.getFirst(HttpHeaders.AUTHORIZATION);하며 여기에 완전한 컨트롤러가 있습니다.

@Operation(summary = "Save new")
@PostMapping("/store")
public ResponseEntity<ResponseRequest<TransDeliveryPlanning>> saveNewTransDeliveryPlanning(@RequestHeader HttpHeaders headers, 
        @Valid @RequestBody InputRequest<TransDeliveryPlanningDto> request) {

    String token = headers.getFirst(HttpHeaders.AUTHORIZATION);

    TransDeliveryPlanning newTransDeliveryPlanning = transDeliveryPlanningService.save(token, request);

    ResponseRequest<TransDeliveryPlanning> response = new ResponseRequest<TransDeliveryPlanning>();

    if (newTransDeliveryPlanning != null) {
        response.setMessage(PESAN_SIMPAN_BERHASIL);
        response.setData(newTransDeliveryPlanning);
    } else {
        response.setMessage(PESAN_SIMPAN_GAGAL);
    }

    return ResponseEntity.ok(response);
}

그리고 나는 어딘가에 무언가를 읽었 Interceptor으므로 내가 생각하는 모든 컨트롤러에 @RequestHeader를 입력 할 필요가 없지만 그 해결책이나 올바르게 사용하는 방법을 모르겠습니다. 누군가가 더 나은 것을 할 수 있다면 나는 당신의 대답을 받아 들일 것입니다

Abhijay Dec 02 2020 at 07:51

유틸리티 클래스에서이 간단한 정적 메서드를 만들고이 메서드를 직접 재사용 할 수 있습니다.

import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

public class BearerTokenUtil {

  public static String getBearerTokenHeader() {
    return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getHeader("Authorization");
  }
}

귀하의 서비스는 다음과 같습니다.

public TransDeliveryPlanning save(InputRequest<TransDeliveryPlanningDto> request) {
       Future<List<PartnerDto>> initPartners = execs.submit(getDataFromAccount(transDeliveryPlanningDtSoDtoPartnerIdsSets));
}

public Callable<List<PartnerDto>> getDataFromAccount(Set<Long> ids) {
    List<PartnerDto> partnerDtoResponse = accountFeignClient.getData(BearerTokenUtil.getBearerTokenHeader(), ids);
    return () -> partnerDtoResponse;
}
RandyHector Dec 04 2020 at 18:19

@stacker의 아래 답변이 정확하다고 생각하지만 어떻게 든 불완전하고 "페이 인에서 사용하는 방법"이 누락되었다고 생각합니다.

당신이 가로 챌 수있는 예제를 위해서, 나는 실제 사용 사례를 제공합니다 User-Agent서비스에 발신자의를하고이 전달 Feign호출

Feign주석을 기반으로 클라이언트 를 사용한다고 가정하면 이것이 추가 코드없이 모든 Feign 클라이언트 호출에서 인터셉터를 사용할 수있는 방법입니다.

@Configuration
@EnableFeignClients(
    defaultConfiguration = DefaultFeignConfiguration.class
)
public class FeignConfig
{
}
@Configuration
@Import(FeignClientsConfiguration.class)
public class DefaultFeignConfiguration
{
    @Bean
    public RequestInterceptor userAgentHeaderInterceptor() {
        return UserAgentHeaderInterceptor();
    } 
}

이것은 User-Agent 인터셉터 클래스입니다.

public class UserAgentHeaderInterceptor extends BaseHeaderInterceptor
{

    private static final String USER_AGENT = "User-Agent";


    public UserAgentHeaderInterceptor()
    {
        super(USER_AGENT);
    }
}
public class BaseHeaderInterceptor implements RequestInterceptor
{

    private final String[] headerNames;


    public BaseHeaderInterceptor(String... headerNames)
    {
        this.headerNames = headerNames;
    }


    @Override
    public void apply(RequestTemplate template)
    {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

        if (attributes != null)
        {
            HttpServletRequest httpServletRequest = attributes.getRequest();

            for (String headerName : headerNames)
            {
                String headerValue = httpServletRequest.getHeader(headerName);
                if (headerValue != null && !headerValue.isEmpty())
                {
                    template.header(headerName, headerValue);
                }
            }
        }
    }
}

귀하의 경우에는이 기본 클래스를 가져 와서 다음과 같은 방식으로 자신 만의 인터셉터를 만들어야합니다. UserAgentHeaderInterceptor