-
Spring Security Oauth2.0 로그인 단위테스트프로젝트/WebRTC 화상통화 프로젝트 2022. 7. 20. 01:17반응형
(2) Spring Security와 OAuth 2.0으로 로그인 구현하기(SpringBoot + React)
(3) Spring Security OAuth 2.0 단위테스트
(4) Spring Security가 OAuth 로그인을 처리하는 방법
개요
이전에 Spring Security를 활용하여 OAuth2.0으로 구글, 네이버, 카카오 로그인을 구현하였습니다.
https://junuuu.tistory.com/415?category=1014988
Spring Security와 Oauth 2.0으로 로그인 구현하기
이해하는데 도움이 되는 개념 - Spring Boot - Oauth 2.0 - 인증 인가 - JPA 지식 다음과 같은 내용을 다루고 있습니다. - Spring Security와 OAuth2.0으로 네이버,카카오,구글 로그인 구현 - 클라이언트에게 로그
junuuu.tistory.com
오늘은 구현한 내용을 가지고 단위테스트를 작성해보려고 합니다.
단위 테스트하기 전 생각해보기
OAuth2.0에 로그인하기 위해서는 클라이언트에서는 다음과 같은 url으로 요청을 보냅니다.
/oauth2/authorization/google /oauth2/authorization/naver /oauth2/authorization/kakao
하지만 사용자는 Provider(구글, 카카오, 네이버)에 아이디, 비밀번호를 입력하고 스프링 시큐리티에서 자동으로 값을 받아서 유저의 자원을 전달해줍니다.
즉, 어떤 컨트롤러도 만들지 않았기 때문에 해당 요청에 대해서 테스트를 어떻게 해야 할지 모르겠습니다.
또한 RestDocs 문서화를 고려하였을 때 클라이언트에게 반환하는 값이 필요한데 클라이언트에게 반환하는 값은 직접 구현한 OAuthController에 존재합니다.
OAuthController
@RestController @RequestMapping("/oauth") public class OAuthController { @GetMapping("/loginInfo") public ResponseEntity<?> oauthLoginInfo(Authentication authentication) { //oAuth2User.toString() 예시 : Name: [2346930276], Granted Authorities: [[USER]], User Attributes: [{id=2346930276, provider=kakao, name=김준우, email=bababoll@naver.com}] OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal(); //attributes.toString() 예시 : {id=2346930276, provider=kakao, name=김준우, email=bababoll@naver.com} Map<String, Object> attributes = oAuth2User.getAttributes(); return ResponseEntity.status(HttpStatus.OK) .body(attributes); } }
하지만 OAuthController의 /oauth/loginInfo는 Spring Security에서 OAuth2 성공 시 redirect 되는 url입니다.
즉, 클라이언트가 요청하는 url은 아닙니다.
하지만 클라이언트에게 반환하는 값을 가지고 있습니다.
또한 저희가 직접 만든 컨트롤러이기 때문에 단위테스트가 가능하기 때문에 http-request 정보만 수정해준다면 문서화도 가능할 것 같습니다.
사전 작업
build.gradle에 테스트의존성을 추가해줍니다.
testImplementation 'org.springframework.security:spring-security-test'
test/resources/application.properties에 oauth에 대한 설정을 입력해줍니다.
#Google spring.security.oauth2.client.registration.google.client-id=test spring.security.oauth2.client.registration.google.client-secret=test spring.security.oauth2.client.registration.google.scope=profile, email
단위 테스트
package zipzong.zipzong.config.auth; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.core.user.DefaultOAuth2User; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; import zipzong.zipzong.controller.OAuthController; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*; @WebMvcTest(OAuthController.class) class OAuthLoginTest { @Autowired private MockMvc mockMvc; @MockBean private OAuthService oAuthService; @Test void Oauth_로그인() throws Exception { //given DefaultOAuth2User user = makeDefaultOAuth2User(); Authentication authentication = new UsernamePasswordAuthenticationToken(user, "password", user.getAuthorities()); //when ResultActions resultActions = mockMvc.perform(get("/oauth/loginInfo").with(authentication(authentication))); //then resultActions.andExpect(status().isOk()); } private DefaultOAuth2User makeDefaultOAuth2User() { //{sub=107986715009708108628, provider=google, name=김준우, email=bababrll@naver.com} Map<String, Object> customAttribute = new LinkedHashMap<>(); customAttribute.put("sub", "107986715009708108628"); customAttribute.put("provider", "google"); customAttribute.put("name", "김준우"); customAttribute.put("email", "bababrll@naver.com"); return new DefaultOAuth2User( Collections.singleton(new SimpleGrantedAuthority("USER")), customAttribute, "sub"); } }
@WebMvcTest 설명
MVC를 위한 테스트로 컨트롤러가 예상대로 작동하는지 테스트하기 위해 사용됩니다.
Security, Filter, Interceptor, request / rsponse Handling , Controller의 항목들만 스캔하도록 제한합니다.
WebApplication과 관련된 Bean들만 등록하기 때문에 @SpringBootTest보다 빠릅니다.
통합 테스트를 진행하기 어려운 테스트를 개별적으로 진행할 수 있습니다.
@MockBean 설명
OAuthController는 내부적으로 oAuthService를 의존하고 있진 않지만 Security가 OAuthService를 의존하고 있기 때문에 가짜 객체를 넣어줍니다.
하지만 컨트롤러 내부에서 하는 행위가 없기 때문에 given() 메서드는 수행하지 않아도 상관없습니다.
테스트 코드 내부 설명
OAuthController의 outhLoginInfo 메서드는 Authentication authentication을 인자로 받습니다.
public ResponseEntity<?> oauthLoginInfo(Authentication authentication)
이 authentication을 Spring Security에서 넘어오는 값입니다.
따라서 이 값을 임의로 세팅해주고 with(authentication(authentication)) 메서드를 통해 해당 값을 주입해줍니다.
값 세팅하는 부분
//given DefaultOAuth2User user = makeDefaultOAuth2User(); Authentication authentication = new UsernamePasswordAuthenticationToken(user, "password", user.getAuthorities()); private DefaultOAuth2User makeDefaultOAuth2User() { //{sub=107986715009708108628, provider=google, name=김준우, email=bababrll@naver.com} Map<String, Object> customAttribute = new LinkedHashMap<>(); customAttribute.put("sub", "107986715009708108628"); customAttribute.put("provider", "google"); customAttribute.put("name", "김준우"); customAttribute.put("email", "bababrll@naver.com"); return new DefaultOAuth2User( Collections.singleton(new SimpleGrantedAuthority("USER")), customAttribute, "sub"); }
여기서 DefaultOAuth2User로 user를 세팅하는 이유는 OAuthService에서 반환하는 값이 DefaultOAuth2User이기 때문입니다.
Authentication은 인터페이스이며 이를 구현하는 구현체인 UsernamePaswordAuthenticationToken에서 인자로
(Authentication의 구현체, credentials, authroties)를 넣어줍니다.
실제 테스트 부분
ResultActions resultActions = mockMvc.perform(get("/oauth/loginInfo").with(authentication(authentication)));
/otuah/loginInfo로 GET 요청이 왔을 때 미리 세팅해둔 authentication 값과 함께 요청합니다.
이후에 기대되는 결과로는 200 OK가 기대되고 실제로 200 OK를 응답하여 테스트가 성공하게 됩니다.
resultActions.andExpect(status().isOk());
출처
https://tecoble.techcourse.co.kr/post/2020-09-30-spring-security-test/
Spring Security가 적용된 곳을 효율적으로 테스트하자.
Spring Security와 관련된 기능을 테스트하다보면 인증 정보를 미리 주입해야 하는 경우가 종종 발생한다. 기본적으로 생각할 수 있는 가장 간단한 방법은 테스트 전에 SecurityContext에 직접 Authenticatio
tecoble.techcourse.co.kr
https://docs.spring.io/spring-security/site/docs/4.2.x/reference/html/test-mockmvc.html
12. Spring MVC Test Integration
There are a number of options available to associate a user to the current HttpServletRequest. For example, the following will run as a user (which does not need to exist) with the username "user", the password "password", and the role "ROLE_USER": You can
docs.spring.io
https://stackoverflow.com/questions/62799664/spring-webmvctest-how-to-mock-authentication
Spring WebMvcTest how to mock Authentication?
I have a controller with the following method. I get the Authentication from the method parameters and use it to get the principal. @PatchMapping("/{employeeId}/present") public
stackoverflow.com
'프로젝트 > WebRTC 화상통화 프로젝트' 카테고리의 다른 글
스프링 부트 + Mysql 도커로 띄우기 (0) 2022.07.21 Spring Rest Docs 적용하기 (0) 2022.07.21 Spring Security와 Oauth 2.0으로 로그인 구현하기(SpringBoot + React) (13) 2022.07.19 ERD 설계하기 (0) 2022.07.17 프로젝트 생성하기(Spring Boot + MySQL + Spring Data JPA) (0) 2022.07.16