[REST API 보안 적용]_07_리소스 서버 설정

2 minute read

스프링 시큐리티 OAuth 2 설정: 리소스 서버 설정

테스트 수정

  • GET을 제외하고 모두 엑세스 토큰을 가지고 요청 하도록 테스트 수정

ResourceServer 설정

  • @EnableResourceServerextends
  • ResourceServerConfigurerAdapter
  • configure(ResourceServerSecurityConfigurer resources)
    • 리소스 ID
  • configure(HttpSecurity http)
    • anonymous
    • GET /api/** : permit all
    • POST /api/: authenticated
    • PUT /api/**: authenticated
    • 에러 처리
      • accessDeniedHandler(OAuth2AccessDeniedHandler())

리소스 서버 설정

  • 설정한 OAuth2 서버와 연동이 되어서 사용이 됨

  • 어떤 외부 요청이 리소스에 접근할 때

    클라이언트가 인증이 필요하다면 OAuth2 서버에서 제공하는 토큰 서비스에 요청을 보내서 토큰을 발급받고, (클라이언트가 할 일)

    (리소스 서버는) 토큰 기반으로 인증정보가 있는지 없는지 확인하고 리소스 서버에 접근을 제한함

  • 리소스 서버는 이벤트 리소스를 제공하는 서버와 같이 있는게 맞고 인증서버는 따로 분리하는게 맞음

  • 작은 서비스에서는 같이 사용해도 상관은 없음

설정 내용

  • @EnableResourceServer
  • extends ResourceServerConfigurerAdapter
  • configure(ResourceServerSecurityConfigurer resources)
    • resourceId() : 리소스 ID설정
    • accessDeniedHandler : 엑세스 거부되었을 때, (접근 권한이 없는 경우) 어떻게 처리할 지
  • configure(HttpSecurity http)
    • anonymous : anonymous만 사용할 수 있고, 인증된 사용자는 사용할 수 없다.
    • GET /api/** : permit all : 모두 허용
    • POST /api/**: authenticated
    • PUT /api/**: authenticated
    • 에러 처리
      • accessDeniedHandler(OAuth2AccessDeniedHandler())
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.resourceId("event"); // 최소한 리소스ID 정도는 설정 필요
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .anonymous() // 익명 허용
                .and()
                .authorizeRequests()
                .mvcMatchers(HttpMethod.GET, "/api/**") // 이 요청은
                .permitAll() // 전부 허용
                .anyRequest() // 그 밖의 다른 요청
                .authenticated() // 인증 필요
                .and()
                .exceptionHandling() // 인증이 잘못됐다던가 권한이 없는 경우 발생하는 예외중에
                .accessDeniedHandler(new OAuth2AccessDeniedHandler()); //  접근권한이 없는 것은 OAuth2AccessDeniedHandler를 사용함. 이 핸들러는 403으로 status 응답을 내보내줌
    }
}

위 설정으로 테스트를 실행

GET 요청은 테스트에 성공한다. 익명의 요청을 허용해줬기 때문에,

GET 이외의 요청은 거부되어서 테스트 실패

image-20210122215229495

EventControllerTests 테스트 수정

인증이 필요한 요청들에 인증정보를 넣어서 테스트를 실행하도록 수정해야한다.

GET을 제외하고 모두 엑세스 토큰을 가지고 요청 하도록 테스트 수정

(인증이 필요한 요청 테스트에) 토큰 정보를 Request 헤더에 추가

AUTHORIZATION 헤더에 Bearer로 엑세스 토큰을 발급받아서 넣어준다.

mockMvc.perform(post("/api/events/") // 요청
        .header(HttpHeaders.AUTHORIZATION, "Bearer " + getBearerToken()) //인증 정보 헤더에 추가

인증 토큰 발급 메서드 추가

private String getBearerToken() throws Exception {
  // Given
  String username = "tester@email.com";
  String password = "1234";
  Account keesun = Account.builder()
    .email(username)
    .password(password)
    .roles(Set.of(AccountRole.ADMIN, AccountRole.USER))
    .build();
  this.accountService.saveAccount(keesun);

  String clientId = "myApp";
  String clientSecret = "pass";

  ResultActions perform = this.mockMvc.perform(post("/oauth/token")
                                               .with(httpBasic(clientId, clientSecret))
                                               .param("username", username)
                                               .param("password", password)
                                               .param("grant_type", "password"));

  var responseBody = perform.andReturn().getResponse().getContentAsString();
  Jackson2JsonParser parser = new Jackson2JsonParser();
  return parser.parseMap(responseBody).get("access_token").toString();
}

javax.persistence.NonUniqueResultException 에러발생

문제원인

  • uniq 해야되는데 테스트마다 계속 동일한 유저를 저장하니까 에러가남

해결방법

  • 아이디를 랜덤으로 생성하거나 매번 테스트 하기전에 데이터를 비워줌

  • InMemoryDB 이지만 테스트 진행 중에는 DB를 공유하므로 데이터가 공유된다.

    데이터가 독립적이지 않으므로 setUp이나 after에서 데이터를 다 지움

  • 테스트시 한 건씩은 괜찮지만 여러건을 한번에 테스트시에는 주의해야함

테스트 시 DB를 비우면서 테스트

@Autowired
AccountRepository accountRepository;

@Before
public void setUp() {
    this.eventRepository.deleteAll();
    this.accountRepository.deleteAll();
}