Loading the catalog…
Loading the catalog…
구글에 서비스 등록하기 Google Cloud 접속 Google Auth Platform 접속 프로젝트 만들기 구글 인증 플랫폼 구성 클라이언트 만들기 개발과 실서비스에서 사용할 redirect uri를 둘 다 등록 Spring Security가 Google 인증 페이지로 리다이렉트할 때 redirect_uri를 함께 전달하므로, Google은 인증 완료 후 어떤 콜백 URL로 사용자를 돌려보낼지 알 수 있다. 클라이언트 ID와 비밀번호 저장 창을 닫으면 사라지므로 보관한다. 클라이언트 ID와 비밀번호 인텔리제이 환경변수에 등록 프로젝트의 WhiteList에 추가 // Google 로그인 시작 new PermitRule(HttpMethod.GET, "/oauth2/authorization/google"), // Google 로그인 콜백 new PermitRule(HttpMethod.GET, "/login/oauth2/code/google") OAuth2 로그인 성공 여부를 알아보기 위해AuthenticationSuccessHandler 생성 후 등록 @Slf4j @Component public class OAuth2LoginSuccessHandler implements AuthenticationSuccessHandler { @Override public void onAuthenticationSuccess( HttpServletRequest request, HttpServletResponse response, Authentication authentication ) { OAuth2User oauth2User = (OAuth2User) authentication.getPrincipal(); Map<String, Object> attributes = oauth2User.getAttributes(); log.info("OAuth2 로그인 성공"); log.info("attributes = {}", attributes); log.info("providerId = {}", attributes.get("sub")); log.info("email = {}", attributes.get("email")); log.info("name = {}", attributes.get("name")); log.info("picture = {}", attributes.get("picture")); response.setStatus(HttpServletResponse.SC_OK); } } @Bean @Order(1) public SecurityFilterChain publicFilterChain(HttpSecurity http) throws Exception { AntPathMatcher antPathMatcher = new AntPathMatcher(); http .securityMatcher(request -> { String path = request.getRequestURI(); String method = request.getMethod(); return SecurityWhitelist.WHITELIST.stream() .anyMatch(rule -> { return rule.getHttpMethod().matches(method) && antPathMatcher.match(rule.getPattern(), path); }); }) .cors(cors -> cors.configurationSource(corsConfigurationSource())) .csrf(csrf -> csrf.disable()) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .formLogin(form -> form.disable()) .httpBasic(basic -> basic.disable()) .logout(logout -> logout.disable()) .authorizeHttpRequests(auth -> auth.anyRequest().permitAll()) .oauth2Login(oauth2 -> oauth2.successHandler(oauth2LoginSuccessHandler)); // 핸들러 등록 return http.build(); } 브라우저에서 호출 후 로그 확인 localhost:8080/oauth2/authorization/google 브라우저에서 호출 구글 로그인 후 uri가 등록한 주소로 리다이렉트 된 것 확인 로그에서 성공 확인 User 엔티티에 AuthProvider 라는 Enum을 추가해서 일반으로 가입한 유저인지 소셜로그인으로 가입한 유저인지 구분 public enum AuthProvider { LOCAL, GOOGLE, KAKAO } 이름 중복을 허용하도록 기존 로직 수정 소셜 로그인으로 유저명을 받아오게 되면 이미 서버에 저장된 이름이 있을 수 있으므로 임의의 이름을 정해주기 보다는 이름의 중복을 허용 유저의 중복처리 이름의 중복을 허용하였으므로 유저들을 구분하기 위한 8자리 tag를 가입시 저장하도록 수정 LOCAL 회원가입시에는 LOCAL + 이메일 조합으로 중복을 체크하고 소셜로그인으로 회원 가입시에는 서비스에서 내려주는 식별 아이디를 이용해서 AuthProvider + 식별ID 조합으로 중복을 체크 일반 로그인에서도 LOCAL + 이메일 조합으로 유저를 찾고, 소셜 로그인에서는 AuthProvider + 식별ID 조합으로 중복을 체크 OAuth2LoginSuccessHandler 수정 oauth를 통해 받은 식별ID 와 AuthProvider 조합으로 유저를 조회 조회된 유저가 있을때 삭제된 유저이면 시작페이지로 리다이렉트 하면서 쿼리 파라미터로 오류 전달 조회된 유저가 없으면 새 유저로 저장 JwtUtils 를 활용하여 RefreshToken과 MediaToken을 생성하여 쿠키로 설정한 뒤 chat.html 페이지로 리다이렉트 package com.tgg.chat.common.security.oauth; import com.tgg.chat.common.security.jwt.JwtUtils; import com.tgg.chat.common.security.token.RedisTokenStore; import com.tgg.chat.domain.user.entity.User; import com.tgg.chat.domain.user.enums.AuthProvider; import com.tgg.chat.domain.user.repository.UserRepository; import com.tgg.chat.domain.user.service.UserTagGenerator; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseCookie; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.stereotype.Component; import java.io.IOException; import java.time.Duration; import java.util.Map; import java.util.Optional; @Slf4j @Component @RequiredArgsConstructor public class OAuth2LoginSuccessHandler implements AuthenticationSuccessHandler { private final UserRepository userRepository; private final JwtUtils jwtUtils; private final RedisTokenStore redisTokenStore; private final UserTagGenerator userTagGenerator; @Value("${frontend_url}") private String frontendUrl; @Value("${app.cookie.secure}") private boolean cookieSecure; @Override public void onAuthenticationSuccess( HttpServletRequest request, HttpServletResponse response, Authentication authentication ) throws IOException { OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication; OAuth2User oauth2User = oauthToken.getPrincipal(); String registrationId = oauthToken.getAuthorizedClientRegistrationId(); String providerId; String username; AuthProvider authProvider; if ("google".equals(registrationId)) { providerId = oauth2User.getAttribute("sub"); username = oauth2User.getAttribute("name"); authProvider = AuthProvider.GOOGLE; } else if ("kakao".equals(registrationId)) { Map<String, Object> attributes = oauth2User.getAttributes(); Map<String, Object> kakaoAccount = (Map<String, Object>) attributes.get("kakao_account"); Map<String, Object> profile = (Map<String, Object>) kakaoAccount.get("profile"); providerId = String.valueOf(attributes.get("id")); username = (String) profile.get("nickname"); authProvider = AuthProvider.KAKAO; } else { throw new IllegalArgumentException( "지원하지 않는 OAuth Provider: " + registrationId ); } Optional<User> optionalUser = userRepository.findByAuthProviderAndProviderId(authProvider, providerId); User user; if (optionalUser.isPresent()) { User findUser = optionalUser.get(); // 탈퇴한 소셜 계정이면 로그인 차단 if (findUser.getDeleted()) { response.sendRedirect( frontendUrl + "/index.html?oauthError=deleted_account" ); return; } user = findUser; } else { String userTag; do { userTag = userTagGenerator.generate(); } while(userRepository.existsByUserTag(userTag)); user = User.of( null, null, username, userTag, providerId, authProvider ); user = userRepository.save(user); } String sid = jwtUtils.generateSid(); String refreshToken = jwtUtils.createRefreshToken(user, sid); String mediaToken = jwtUtils.createMediaToken(user, sid); redisTokenStore.saveRefreshToken( user.getUserId(), sid, refreshToken, jwtUtils.getRefreshTokenTtlMillis() ); ResponseCookie refreshTokenCookie = ResponseCookie.from("refreshToken", refreshToken) .httpOnly(true) .secure(cookieSecure) .sameSite("Lax") .path("/") .maxAge(Duration.ofMillis( jwtUtils.getRefreshTokenTtlMillis() )) .build(); ResponseCookie mediaTokenCookie = ResponseCookie.from("mediaToken", mediaToken) .httpOnly(true) .secure(cookieSecure) .sameSite("Lax") .path("/") .maxAge(Duration.ofMillis( jwtUtils.getMediaTokenTtlMillis() )) .build(); response.addHeader( HttpHeaders.SET_COOKIE, refreshTokenCookie.toString() ); response.addHeader( HttpHeaders.SET_COOKIE, mediaTokenCookie.toString() ); response.sendRedirect( frontendUrl + "/chat.html" ); } } 테스트중 구글에서 리다이렉트할때 잘못된 경로로 리다이렉트 하는 것을 확인 https://tggchat.com 이 아닌 http://127.0.0.1:8080 으로 리다이렉트 시도하려고 하고 있었음 이유를 찾아본 결과 nginx가 사용자의 요청을 Spring Boot로 전달하는데 별도의 설정이 없으면 upstream의 주소가 전달되며 외부 요청이 HTTPS였다는 정보도 Spring은 알 수 없어서 http://127.0.0.1:8080 으로 요청이 들어오는 것을 알게 됨 Spring Security가 외부 서비스 주소인 https://tggchat.com이 아니라, Nginx가 요청을 전달하는 Spring Boot의 내부 주소인 http://127.0.0.1:8080을 기준으로 callback URL을 생성하고 있었던 것이 문제였다. 그래서 외부 호스트와 프로토콜을 제대로 전달할 수 있도록 아래 설정을 추가함 proxy_set_header Host $host; # 외부 호스트인 tggchat.com 전달 proxy_set_header X-Forwarded-Proto $scheme; # 외부 요청 프로토콜인 https 전달 또한 Spring 에 nginx에서 전달한 내용을 반영해서 콜백주소를 만들도록 아래설정을 추가 server: forward-headers-strategy: native
What RADAR observed and classified to build this opportunity. It is what the source published, not a verification that the offer is still active.
google 소셜로그인 도입. 구글에 서비스 등록하기 Google Cloud 접속 Google Auth Platform 접속 프로젝트 만들기 구글 인증 플랫폼 구성 클라이언트 만들기 개발과 실서비스에서 사용할 redirect uri를 둘 다 등록 Spring Security가 Google 인증 페이지로 리다이렉트할 때 redirect_uri를 함께 전달하므로, Google은 인증 완료 후 어떤 콜백 URL로 사용자를 돌려보낼지 알 수 있다. 클라이언트 ID와 비밀번호 저장 창을 닫으면 사라지므로 보관한다. 클라이언트 ID와 비밀번호 인텔리제이 환경변수에 등록 프로젝트의 WhiteList에 추가 // Google 로그인 시작 new PermitRule(HttpMethod.GET,…
Open source