Загружаем каталог…
Загружаем каталог…
구글에 서비스 등록하기 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
То, что RADAR обнаружил и классифицировал для этой возможности. Это опубликованный источником текст, а не подтверждение, что предложение ещё действует.
google 소셜로그인 도입. 구글에 서비스 등록하기 Google Cloud 접속 Google Auth Platform 접속 프로젝트 만들기 구글 인증 플랫폼 구성 클라이언트 만들기 개발과 실서비스에서 사용할 redirect uri를 둘 다 등록 Spring Security가 Google 인증 페이지로 리다이렉트할 때 redirect_uri를 함께 전달하므로, Google은 인증 완료 후 어떤 콜백 URL로 사용자를 돌려보낼지 알 수 있다. 클라이언트 ID와 비밀번호 저장 창을 닫으면 사라지므로 보관한다. 클라이언트 ID와 비밀번호 인텔리제이 환경변수에 등록 프로젝트의 WhiteList에 추가 // Google 로그인 시작 new PermitRule(HttpMethod.GET,…
Открыть источник