-
OAuth2 사용해서 react와 함께 소셜로그인 기능 만들기Spring 2021. 10. 26. 21:40
로그인 창에서 많이 볼 수 있는 소셜로그인 기능을 만들어보자.
여기서 인증이 완료된 후 생성한 토큰을 어떻게 프론트엔드로 다시 보낼까에 대한 부분이 어려웠다 😵💫
(결론은 쿠키를 통해서 확인된 uri에 생성된 토큰 붙인 새로운 uri 생성하고 다시 보내기...!)
구글 프로젝트 계정 만들기
https://console.cloud.google.com/apis
먼저 홈페이지에 들어가서 로그인을 해준 후 , API 및 서비스 항목으로 들어간다.
적용할 프로젝트를 생성해준다.
OAuth 동의화면으로 가서 앱 정보를 입력해준다.
사용자 정보 중 프로젝트 사이트에서 활용할 때 필요한 정보의 범위를 설정해준다.
새로 인증정보를 추가해준다.
여기서 http://localhost:8080/oauth2/callback/google 사이트는 인증 완료 후에 돌아갈 리다이렉션 사이트이다.
(프론트엔드의 리다이렉션 사이트와는 다르다)
완료후에는 client ID와 secret 정보를 받게된다. 이를 프로젝트에 활용할 것이다.
네이버 프로젝트 계정 만들기
https://developers.naver.com/main/
네이버 개발자 센터에 가입을 해준다.
내 어플리케이션란에서 새프로젝트를 등록해준다.
필요한 정보들을 등록설정한다.
google과 마찬가지로 리다이렉션 URL을 설정해준다.
생성된 client id와 client secret을 복사해둔다.
카카오 프로젝트 계정 만들기
카카오 개발자 사이트에서 새 프로젝트를 등록해준다.
어플리케이션 선택 후 카카오 로그인 -> 동의항목을 설정해준다. (scope)
카카오 로그인에서 활성화를 시켜주고 리다이렉트 uri를 설정해준다.
카카오로그인 -> 보안 창에서 secret코드를 활성화 하고 코드를 발급받는다.
dependency, application.properties 추가
<dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-oauth2-client</artifactId> </dependency>
spring.security.oauth2.client.registration.google.client-id= spring.security.oauth2.client.registration.google.client-secret= spring.security.oauth2.client.registration.google.scope=profile,email spring.security.oauth2.client.registration.google.redirect-uri=http://localhost:8080/oauth2/callback/{registrationId} spring.security.oauth2.client.registration.naver.client-id= spring.security.oauth2.client.registration.naver.client-secret= spring.security.oauth2.client.registration.naver.client-authentication-method=post spring.security.oauth2.client.registration.naver.authorization-grant-type=authorization_code spring.security.oauth2.client.registration.naver.scope=name,email,profile_image spring.security.oauth2.client.registration.naver.client-name=Naver spring.security.oauth2.client.registration.naver.redirect-uri=http://localhost:8080/oauth2/callback/{registrationId} spring.security.oauth2.client.registration.kakao.client-id= spring.security.oauth2.client.registration.kakao.client-secret= spring.security.oauth2.client.registration.kakao.client-authentication-method=post spring.security.oauth2.client.registration.kakao.authorization-grant-type=authorization_code spring.security.oauth2.client.registration.kakao.scope=profile_nickname,account_email,profile_image spring.security.oauth2.client.registration.kakao.client-name=Kakao spring.security.oauth2.client.registration.kakao.redirect-uri=http://localhost:8080/oauth2/callback/{registrationId} # Provider spring.security.oauth2.client.provider.naver.authorization-uri=https://nid.naver.com/oauth2.0/authorize spring.security.oauth2.client.provider.naver.token-uri=https://nid.naver.com/oauth2.0/token spring.security.oauth2.client.provider.naver.user-info-uri=https://openapi.naver.com/v1/nid/me spring.security.oauth2.client.provider.naver.user-name-attribute=response spring.security.oauth2.client.provider.kakao.authorization-uri=https://kauth.kakao.com/oauth/authorize spring.security.oauth2.client.provider.kakao.token-uri=https://kauth.kakao.com/oauth/token spring.security.oauth2.client.provider.kakao.user-info-uri=https://kapi.kakao.com/v2/user/me spring.security.oauth2.client.provider.kakao.user-name-attribute=id app.oauth2.authorized-redirect-uris=http://localhost:3000/oauth2/redirect
발급받은 client id와 client secret을 등록해주고, scope도 등록해준다.
구글, 페이스북과는 다르게 네이버와 카카오는 provider를 등록해주어야 한다.
spring.security.oauth2.client.registration.---.redirect-uri는 서버에서 인증마치고 돌아가는 callback uri이고,
oauth2.authorized-redirect-uris는 서버에서 인증을 마치고 프론트엔드로 돌아가 redirect uri 정보들이다. (다르다)
참고로 프론트와 서버의 포트가 다르기 때문에 webConfig에서 cors 설정은 미리 해주어야 한다.
PoviderType 등록
@Getter public enum ProviderType { GOOGLE, FACEBOOK, NAVER, KAKAO }
@Column(name = "provider_type") @Enumerated(EnumType.STRING) private ProviderType providerType;
열거형 클래스 provider type를 만들어주고, 이를 user에 추가해준다.
JwtUserDetails에 OAuth2User를 추가 상속
public class JwtUserDetails implements UserDetails, OAuth2User { private User user; private Map<String, Object> attributes; public JwtUserDetails(User user) { super(); this.user = user; } public JwtUserDetails(User user, Map<String, Object> attributes) { this.user = user; this.attributes = attributes; } @Override public Map<String, Object> getAttributes() { return attributes; } }
Jwt인증을 위해서 생성한 JwtUserDetails에 OAuth2User를 추가로 상속시켜준다.
attributes를 추가해주고 생성자와 이를 반환하는 함수도 추가로 구현해준다.
OAuth2UserInfo
public abstract class OAuth2UserInfo { protected Map<String, Object> attributes; public OAuth2UserInfo(Map<String, Object> attributes) { this.attributes = attributes; } public Map<String, Object> getAttributes() { return attributes; } public abstract String getId(); public abstract String getName(); public abstract String getEmail(); public abstract String getImageUrl(); public abstract String getFirstName(); public abstract String getLastName(); }
사용자 정보는 provider타입에 따라서 각각 정보가 다른 이름이로 들어오게 된다.
이를 구현해서 정보를 담기위해서 필요한 정보의 항목만 추상클래스로 만들어 놓고 타입마다 다르게 각각 추상클래스를 상속받는 클래스를 구현해서 정보를 가져온다.
GoogleOAuth2UserInfo
public class GoogleOAuth2UserInfo extends OAuth2UserInfo { public GoogleOAuth2UserInfo(Map<String, Object> attributes) { super(attributes); } @Override public String getId() { return (String) attributes.get("sub"); } @Override public String getName() { return (String) attributes.get("name"); } @Override public String getEmail() { return (String) attributes.get("email"); } @Override public String getImageUrl() { return (String) attributes.get("picture"); } @Override public String getFirstName(){ return (String) attributes.get("given_name"); } @Override public String getLastName(){ return (String) attributes.get("family_name"); } }
FacebookOAuth2UserInfo
public class FacebookOAuth2UserInfo extends OAuth2UserInfo{ public FacebookOAuth2UserInfo(Map<String, Object> attributes) { super(attributes); } @Override public String getId() { return (String) attributes.get("id"); } @Override public String getName() { return (String) attributes.get("name"); } @Override public String getEmail() { return (String) attributes.get("email"); } @Override public String getImageUrl() { return (String) attributes.get("imageUrl"); } @Override public String getFirstName() { return null; } @Override public String getLastName() { return null; } }
NaverOAuth2UserInfo
public class NaverOAuth2UserInfo extends OAuth2UserInfo{ public NaverOAuth2UserInfo(Map<String, Object> attributes) { super(attributes); } @Override public String getId() { Map<String, Object> response = (Map<String, Object>) attributes.get("response"); if (response == null) { return null; } System.out.println(response); return (String) response.get("id"); } @Override public String getName() { Map<String, Object> response = (Map<String, Object>) attributes.get("response"); if (response == null) { return null; } return (String) response.get("name"); } @Override public String getEmail() { Map<String, Object> response = (Map<String, Object>) attributes.get("response"); if (response == null) { return null; } return (String) response.get("email"); } @Override public String getImageUrl() { Map<String, Object> response = (Map<String, Object>) attributes.get("response"); if (response == null) { return null; } return (String) response.get("profile_image"); } @Override public String getFirstName() { Map<String, Object> response = (Map<String, Object>) attributes.get("response"); if (response == null) { return null; } String name = (String)response.get("name"); return name.substring(1); } @Override public String getLastName() { Map<String, Object> response = (Map<String, Object>) attributes.get("response"); if (response == null) { return null; } String name = (String)response.get("name"); return name.substring(0,1); } }
KakaoOAuth2UserInfo
public class KakaoOAuth2UserInfo extends OAuth2UserInfo{ public KakaoOAuth2UserInfo(Map<String, Object> attributes) { super(attributes); } @Override public String getId() { return attributes.get("id").toString(); } @Override public String getName() { Map<String, Object> properties = (Map<String, Object>) attributes.get("properties"); if (properties == null) { return null; } return (String) properties.get("nickname"); } @Override public String getEmail() { Map<String, Object> properties = (Map<String, Object>) attributes.get("kakao_account"); if (properties == null) { return null; } return (String) properties.get("email"); } @Override public String getImageUrl() { Map<String, Object> properties = (Map<String, Object>) attributes.get("properties"); if (properties == null) { return null; } return (String) properties.get("thumbnail_image"); } @Override public String getFirstName() { Map<String, Object> properties = (Map<String, Object>) attributes.get("properties"); if (properties == null) { return null; } String name = (String) properties.get("nickname"); return name.substring(1); } @Override public String getLastName() { Map<String, Object> properties = (Map<String, Object>) attributes.get("properties"); if (properties == null) { return null; } String name = (String) properties.get("nickname"); return name.substring(0,1); } }
OAuth2UserInfoFactory
public class OAuth2UserInfoFactory { public static OAuth2UserInfo getOAuth2UserInfo(ProviderType providerType, Map<String, Object> attributes) { switch (providerType) { case GOOGLE: return new GoogleOAuth2UserInfo(attributes); case FACEBOOK: return new FacebookOAuth2UserInfo(attributes); case NAVER: return new NaverOAuth2UserInfo(attributes); case KAKAO: return new KakaoOAuth2UserInfo(attributes); default: throw new IllegalArgumentException("Invalid Provider Type."); } } }
switch를 통해서 provider타입에 따라서 각각 다른 userInfo 정보를 가져오도록 실행하도록 하는 팩토리 클래스를 구현해준다.
AppProperties
@ConfigurationProperties(prefix = "app") public class AppProperties { private final Auth auth = new Auth(); private final OAuth2 oauth2 = new OAuth2(); public static class Auth { private String tokenSecret; private long tokenExpirationMsec; public String getTokenSecret() { return tokenSecret; } public void setTokenSecret(String tokenSecret) { this.tokenSecret = tokenSecret; } public long getTokenExpirationMsec() { return tokenExpirationMsec; } public void setTokenExpirationMsec(long tokenExpirationMsec) { this.tokenExpirationMsec = tokenExpirationMsec; } } public static final class OAuth2 { private List<String> authorizedRedirectUris = new ArrayList<>(); public List<String> getAuthorizedRedirectUris() { return authorizedRedirectUris; } public OAuth2 authorizedRedirectUris(List<String> authorizedRedirectUris) { this.authorizedRedirectUris = authorizedRedirectUris; return this; } } public Auth getAuth() { return auth; } public OAuth2 getOauth2() { return oauth2; } }
oauth2 정보를 가지고 있는 AppProperties configuration을 생성해준다.
(인증 정보 redirectUri를 저장하고 가져올 수 있게 하는 역할)
앱 application
@EnableJpaAuditing @EnableConfigurationProperties(AppProperties.class) @SpringBootApplication public class WalkerholicApplication { public static void main(String[] args) { SpringApplication.run(WalkerholicApplication.class, args); } }
어플리케이션에 AppProperties Configuration을 추가해준다.
CustomOAuth2UserService
@Service @RequiredArgsConstructor public class CustomOauth2UserService extends DefaultOAuth2UserService { private final UserRepository userRepository; private final JwtTokenUtil jwtTokenUtil; @Override public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { System.out.println("attributes" + super.loadUser(userRequest).getAttributes()); OAuth2User user = super.loadUser(userRequest); //여기서 attriutes를 찍어보면 모두 각기 다른 이름으로 데이터가 들어오는 것을 확인할 수 있다. try{ return process(userRequest, user); } catch (AuthenticationException ex){ throw new OAuth2AuthenticationException(ex.getMessage()); } catch (Exception ex){ throw new InternalAuthenticationServiceException(ex.getMessage(), ex.getCause()); } } //인증을 요청하는 사용자에 따라서 없는 회원이면 회원가입, 이미 존재하는 회원이면 업데이트를 실행한다. private OAuth2User process(OAuth2UserRequest userRequest, OAuth2User user) { ProviderType providerType = ProviderType.valueOf(userRequest.getClientRegistration().getRegistrationId().toUpperCase()); //provider타입에 따라서 각각 다르게 userInfo가져온다. (가져온 필요한 정보는 OAuth2UserInfo로 동일하다) OAuth2UserInfo userInfo = OAuth2UserInfoFactory.getOAuth2UserInfo(providerType, user.getAttributes()); User savedUser = userRepository.findByEmail(userInfo.getEmail()); if (savedUser != null) { if (providerType != savedUser.getProviderType()) { throw new OAuthProviderMissMatchException( "Looks like you're signed up with " + providerType + " account. Please use your " + savedUser.getProviderType() + " account to login." ); } updateUser(savedUser, userInfo); } else { savedUser = createUser(userInfo, providerType); } System.out.println(jwtTokenUtil.generateToken(userInfo.getEmail())); return new JwtUserDetails(savedUser, user.getAttributes()); } //넘어온 사용자 정보를 통해서 회원가입을 실행한다. private User createUser(OAuth2UserInfo userInfo, ProviderType providerType) { User user = new User(); user.setFirstname(userInfo.getFirstName()); user.setLastname(userInfo.getLastName()); user.setRole(Role.USER); user.setLevel(Level.Starter); user.setPassword(""); user.setEmail(userInfo.getEmail()); user.setImageUrl(userInfo.getImageUrl()); user.setProviderType(providerType); return userRepository.save(user); } //사용자정보에 변경이 있다면 사용자 정보를 업데이트 해준다. private User updateUser(User user, OAuth2UserInfo userInfo) { if (userInfo.getFirstName() != null && !user.getFullname().equals(userInfo.getName())) { user.setFirstname(userInfo.getFirstName()); } if (userInfo.getLastName() != null && !user.getLastname().equals(userInfo.getLastName())){ user.setLastname(userInfo.getLastName()); } if (userInfo.getImageUrl() != null && !user.getImageUrl().equals(userInfo.getImageUrl())) { user.setImageUrl(userInfo.getImageUrl()); } return user; } }
넘어온 사용자 로그인 요청에 provider타입에 따라서 각각 다르게 userInfo를 실행해서 정보를 가져온다.
회원 등록 여부에 따라서 회원가입 혹은 업데이트를 진행한다.
HttpCookieOAuth2AuthorizationRequestRepository
public class HttpCookieOAuth2AuthorizationRequestRepository implements AuthorizationRequestRepository<OAuth2AuthorizationRequest> { public static final String OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME = "oauth2_auth_request"; public static final String REDIRECT_URI_PARAM_COOKIE_NAME = "redirect_uri"; private static final int cookieExpireSeconds = 180; //쿠키에 저장된 인증요청 정보를 가지고 온다 @Override public OAuth2AuthorizationRequest loadAuthorizationRequest(HttpServletRequest httpServletRequest) { return CookieUtils.getCookie(httpServletRequest, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME) .map(cookie -> CookieUtils.deserialize(cookie, OAuth2AuthorizationRequest.class)) .orElse(null); } //인증 요청 정보를 쿠키에 저장 @Override public void saveAuthorizationRequest(OAuth2AuthorizationRequest oAuth2AuthorizationRequest, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) { if (oAuth2AuthorizationRequest == null) { CookieUtils.deleteCookie(httpServletRequest, httpServletResponse, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME); CookieUtils.deleteCookie(httpServletRequest, httpServletResponse, REDIRECT_URI_PARAM_COOKIE_NAME); return; } CookieUtils.addCookie(httpServletResponse, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME, CookieUtils.serialize(oAuth2AuthorizationRequest), cookieExpireSeconds); String redirectUriAfterLogin = httpServletRequest.getParameter(REDIRECT_URI_PARAM_COOKIE_NAME); if (StringUtils.isNotBlank(redirectUriAfterLogin)) { CookieUtils.addCookie(httpServletResponse, REDIRECT_URI_PARAM_COOKIE_NAME, redirectUriAfterLogin, cookieExpireSeconds); } } //쿠키에 등록된 인증 요청 정보를 삭제 @Override public OAuth2AuthorizationRequest removeAuthorizationRequest(HttpServletRequest httpServletRequest) { return this.loadAuthorizationRequest(httpServletRequest); } public void removeAuthorizationRequestCookies(HttpServletRequest request, HttpServletResponse response) { CookieUtils.deleteCookie(request, response, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME); CookieUtils.deleteCookie(request, response, REDIRECT_URI_PARAM_COOKIE_NAME); } }
인증요청 정보 http request를 쿠키에 저장하고 저장한 것을 불러오고 삭제하는 authorizationRequestRepository를 상속받는 repository를 만들어준다.
CookieUtils
public class CookieUtils { public static Optional<Cookie> getCookie(HttpServletRequest request, String name) { Cookie[] cookies = request.getCookies(); if (cookies != null && cookies.length > 0) { for (Cookie cookie : cookies) { if (cookie.getName().equals(name)) { return Optional.of(cookie); } } } return Optional.empty(); } public static void addCookie(HttpServletResponse response, String name, String value, int maxAge) { Cookie cookie = new Cookie(name, value); cookie.setPath("/"); cookie.setHttpOnly(true); cookie.setMaxAge(maxAge); response.addCookie(cookie); } public static void deleteCookie(HttpServletRequest request, HttpServletResponse response, String name) { Cookie[] cookies = request.getCookies(); if (cookies != null && cookies.length > 0) { for (Cookie cookie: cookies) { if (cookie.getName().equals(name)) { cookie.setValue(""); cookie.setPath("/"); cookie.setMaxAge(0); response.addCookie(cookie); } } } } public static String serialize(Object object) { return Base64.getUrlEncoder() .encodeToString(SerializationUtils.serialize(object)); } public static <T> T deserialize(Cookie cookie, Class<T> cls) { return cls.cast(SerializationUtils.deserialize( Base64.getUrlDecoder().decode(cookie.getValue()))); } }
쿠키정보를 처리하는 cookieUtils 클래스를 생성해준다.
OAuth2AuthenticationSuccessHandler
@Component @RequiredArgsConstructor public class OAuth2SuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { private final JwtTokenUtil jwtTokenUtil; private final AppProperties appProperties; private final HttpCookieOAuth2AuthorizationRequestRepository httpCookieOAuth2AuthorizationRequestRepository; //oauth2인증이 성공적으로 이뤄졌을 때 실행된다 //token을 포함한 uri을 생성 후 인증요청 쿠키를 비워주고 redirect 한다. @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException { String targetUrl = determineTargetUrl(request, response, authentication); if (response.isCommitted()) { logger.debug("Response has already been committed. Unable to redirect to " + targetUrl); return; } clearAuthenticationAttributes(request, response); getRedirectStrategy().sendRedirect(request, response, targetUrl); } //token을 생성하고 이를 포함한 프론트엔드로의 uri를 생성한다. protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { Optional<String> redirectUri = CookieUtils.getCookie(request, HttpCookieOAuth2AuthorizationRequestRepository.REDIRECT_URI_PARAM_COOKIE_NAME) .map(Cookie::getValue); if(redirectUri.isPresent() && !isAuthorizedRedirectUri(redirectUri.get())) { throw new BadRequestException("Sorry! We've got an Unauthorized Redirect URI and can't proceed with the authentication"); } String targetUrl = redirectUri.orElse(getDefaultTargetUrl()); String token = jwtTokenUtil.generateToken(authentication.getName()); return UriComponentsBuilder.fromUriString(targetUrl) .queryParam("token", token) .build().toUriString(); } //인증정보 요청 내역을 쿠키에서 삭제한다. protected void clearAuthenticationAttributes(HttpServletRequest request, HttpServletResponse response) { super.clearAuthenticationAttributes(request); httpCookieOAuth2AuthorizationRequestRepository.removeAuthorizationRequestCookies(request, response); } //application.properties에 등록해놓은 Redirect uri가 맞는지 확인한다. (app.redirect-uris) private boolean isAuthorizedRedirectUri(String uri) { URI clientRedirectUri = URI.create(uri); return appProperties.getOauth2().getAuthorizedRedirectUris() .stream() .anyMatch(authorizedRedirectUri -> { // Only validate host and port. Let the clients use different paths if they want to URI authorizedURI = URI.create(authorizedRedirectUri); if(authorizedURI.getHost().equalsIgnoreCase(clientRedirectUri.getHost()) && authorizedURI.getPort() == clientRedirectUri.getPort()) { return true; } return false; }); } }
oauth2인증이 성공한 후에 실행되는 OAuth2AuthenticationSuccessHandler를 작성해준다.
프론트엔드를 통해서 들어온 redirect-uri가 우리가 저장해놓은 app.redirect-uris의 항목과 일치하는지 확인하고 일치한다면 token을 포함한 uri생성 후 인증요청 쿠키를 비워주고 redirect 하도록 한다.
OAuth2AuthenticationFailureHandler
@Component public class OAuth2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { @Autowired HttpCookieOAuth2AuthorizationRequestRepository httpCookieOAuth2AuthorizationRequestRepository; @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { super.onAuthenticationFailure(request, response, exception); String targetUrl = CookieUtils.getCookie(request, HttpCookieOAuth2AuthorizationRequestRepository.REDIRECT_URI_PARAM_COOKIE_NAME) .map(Cookie::getValue) .orElse(("/")); targetUrl = UriComponentsBuilder.fromUriString(targetUrl) .queryParam("error", exception.getLocalizedMessage()) .build().toUriString(); httpCookieOAuth2AuthorizationRequestRepository.removeAuthorizationRequestCookies(request, response); getRedirectStrategy().sendRedirect(request, response, targetUrl); } }
만약 인증이 실패한다면, 들어온 uri정보에 token정보 대신에 error 정보를 추가해서 리다이렉트 하도록 설정한다.
OAuthProviderMissMatchException
public class OAuthProviderMissMatchException extends RuntimeException{ public OAuthProviderMissMatchException(String message) { super(message); } public OAuthProviderMissMatchException(String message, Throwable cause) { super(message, cause); } public OAuthProviderMissMatchException(Throwable cause) { super(cause); } }
@ExceptionHandler(OAuthProviderMissMatchException.class) public ResponseEntity<ErrorResponse> handleOAuthProviderMissMatchException(OAuthProviderMissMatchException e){ ErrorResponse errorResponse = new ErrorResponse(); HttpStatus status = HttpStatus.BAD_REQUEST; errorResponse.setStatus(status.value()); errorResponse.setMessage(e.getMessage()); return new ResponseEntity<>(errorResponse, status); }
인증 provider가 불일치 할 경우 발생될 exception을 생성하고 exception핸들러에 추가해준다.
WebSecurityConfig
@Configuration @EnableWebSecurity //@EnableGlobalMethodSecurity(prePostEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private CustomOauth2UserService customOauth2UserService; @Autowired private OAuth2SuccessHandler oAuth2SuccessHandler; @Autowired private OAuth2AuthenticationFailureHandler oAuth2AuthenticationFailureHandler; @Autowired private HttpCookieOAuth2AuthorizationRequestRepository httpCookieOAuth2AuthorizationRequestRepository; @Bean public HttpCookieOAuth2AuthorizationRequestRepository cookieAuthorizationRequestRepository() { return new HttpCookieOAuth2AuthorizationRequestRepository(); } @Override protected void configure(HttpSecurity http) throws Exception { http.cors() .and() .csrf() .disable() .formLogin() .disable() .authorizeRequests() .permitAll() .anyRequest() .permitAll() .and() .exceptionHandling() .authenticationEntryPoint(jwtAuthenticationEntryPoint) .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .oauth2Login() .authorizationEndpoint() .authorizationRequestRepository(cookieAuthorizationRequestRepository()) .baseUri("/oauth2/authorization") .and() .redirectionEndpoint() .baseUri("/oauth2/callback/*") .and() .userInfoEndpoint() .userService(customOauth2UserService) .and() .successHandler(oAuth2SuccessHandler) .failureHandler((AuthenticationFailureHandler) oAuth2AuthenticationFailureHandler); http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class); }
webSecurityConfig에 HttpCookieRepository, CustomOAuth2UserService, OAuth2AuthenticationSuccessHandler, OAuth2AuthenticationFailureHandler를 추가해서 작성해준다.
여기서 baseUri는 프론트엔드에서 서버로 인증을 요청할 때 이동할 uri이고, redirectionEndpoint의 base uri는 어플리케이션 설정에서 정해주었던 callback uri 이다.
프론트엔드 설정
//서버로 인증을 요청할 uri (서버의 webSecurityConfig의 base uri와 일치해야 한다) export const API_BASE_URL = 'http://localhost:8080'; //서버에서 인증을 완료한 후에 프론트엔드로 돌아올 redirect uri (app.oauth2.authorized-redirect-uri와 일치해야 한다) export const OAUTH2_REDIRECT_URI = 'http://localhost:3000/oauth2/redirect'; export const GOOGLE_AUTH_URL = API_BASE_URL + '/oauth2/authorization/google?redirect_uri=' + OAUTH2_REDIRECT_URI; export const FACEBOOK_AUTH_URL = API_BASE_URL + '/oauth2/authorization/facebook?redirect_uri=' + OAUTH2_REDIRECT_URI; export const NAVER_AUTH_URL = API_BASE_URL + '/oauth2/authorization/naver?redirect_uri=' + OAUTH2_REDIRECT_URI; export const KAKAO_AUTH_URL = API_BASE_URL + '/oauth2/authorization/kakao?redirect_uri=' + OAUTH2_REDIRECT_URI;
//클릭후 해당 uri로 이동하는 버튼을 생성 <div className="auth_oauth_button"> <a href={GOOGLE_AUTH_URL}><img src={google} alt="" /></a> <a href={KAKAO_AUTH_URL}><img src={kakao} alt="" /></a> <a href={NAVER_AUTH_URL}><img src={naver} alt="" /></a> </div>
//프론트엔드로 돌아올 uri에 스크린을 등록해준다. <Route exact path="/oauth2/redirect" component={HomeScreen}/>
프론트엔드에서 uri설정을 완료하고 버튼을 누르면 해당 uri로 이동하도록 설정한다.
( 도움이 정말정말 많이 되었던 참고 사이트 🤎 )
https://deeplify.dev/back-end/spring/oauth2-social-login#kakaooauth2userinfo
https://engkimbs.tistory.com/849
(이곳은 코드 작성 내용을 참고했던 깃허브이다)
'Spring' 카테고리의 다른 글
Springboot(Maven), React, MySQL 프로젝트 Heroku 배포하기 (0) 2021.11.05 SMTP 서버를 통한 이메일 보내기 (0) 2021.10.26 TDD (Test Driven Development) : 단위 테스트 작성하기 (0) 2021.10.26 JPA delete 쿼리가 실행되지 않을 때 (0) 2021.10.26 Controller와 RestController ( + ResponseEntity) (0) 2021.10.26