묻고 답해요
141만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결
EC2 배포후 구글 로그인 안됨
스프링부트 REST + OAuth2 + JWT를 사용하고 있는 상황인데로컬에서는 잘 돌아갑니다.로컬에서 소셜로그인이 성공하면 OAuth2SuccessHandler에서 바로 JSON으로 반환해주는 형태입니다. 즉, 컨트롤러가 딱히 무슨 역할을 하지 않아도 바로 반환을 해줍니다.하지만 EC2에 배포하고 구글 개발자 센터, yml에 EC2 퍼블릭을 제대로 입력해주고 제대로 일치하는 것을 확인했고버튼을 클릭하면 아이디들이 제대로 뜹니다. 하지만 로그인한 결과 로그인할 아이디를 클릭을 하면Whitelabel Error Page 404페이지가 뜹니다. 그래서 실패했을 때 JSON으로 반환시켜주는 로직을 추가해서 확인한 결과"error 발생 : ": "[authorization_request_not_found] "이러한 오류가 발생했습니다. 항상 체크해야하는요소yml 체크함구글 개발자 센터 확인함→ 1, 2번은 일치함EC2 인스턴스의 보안 그룹이 OAuth2 콜백 URL로 요청을 수신할 수 있도록 올바르게 설정되었는지 확인 → 이거는 어떻게 다른 설정법이 있을까요?application.yml 또는 application.properties에 설정된 값들이 프로덕션 환경에 맞게 정확히 설정이렇게 환경변수를 받아서 ec2 배포시 사용할 수 있도록 설정했는데 추가적으로 또 뭔가를 해줘야 하나요? 혹시 OAuth2 google을 테스터로 해놓고 http로 해놓으면 ec2 배포시에는 사용하지 못하나요?
-
미해결
배포후 소셜로그인 에러
스프링부트 REST + OAuth2 + JWT를 사용하고 있는 상황인데로컬에서는 잘 돌아갑니다.로컬:<a id="google-login" href="/oauth2/authorization/google">구글 로그인</a> <a id="naver-login" href="/oauth2/authorization/naver">네이버 로그인</a> @Service @Log4j2 @RequiredArgsConstructor public class PrincipalOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> { private final MemberRepository memberRepository; private final JwtProvider jwtProvider; private final TokenRepository tokenRepository; @Override public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { // userRequest.getClientRegistration()은 인증 및 인가된 사용자 정보를 가져오는 // Spring Security에서 제공하는 메서드입니다. ClientRegistration clientRegistration = userRequest.getClientRegistration(); log.info("clientRegistration : " + clientRegistration); // 소셜 로그인 accessToken String socialAccessToken = userRequest.getAccessToken().getTokenValue(); log.info("소셜 로그인 accessToken : " + socialAccessToken); OAuth2UserService<OAuth2UserRequest, OAuth2User> oAuth2UserService = new DefaultOAuth2UserService(); log.info("oAuth2UserService : " + oAuth2UserService); // 소셜 로그인한 유저정보를 가져온다. OAuth2User oAuth2User = oAuth2UserService.loadUser(userRequest); log.info("oAuth2User : " + oAuth2User); log.info("getAttribute : " + oAuth2User.getAttributes()); // 회원가입 강제 진행 OAuth2UserInfo oAuth2UserInfo = null; String registrationId = clientRegistration.getRegistrationId(); log.info("registrationId : " + registrationId); if(registrationId.equals("google")) { log.info("구글 로그인"); oAuth2UserInfo = new GoogleUser(oAuth2User, clientRegistration); } else if(registrationId.equals("naver")) { log.info("네이버 로그인"); oAuth2UserInfo = new NaverUser(oAuth2User, clientRegistration); } else { log.error("지원하지 않는 소셜 로그인입니다."); } // 사용자가 로그인한 소셜 서비스를 가지고 옵니다. // 예시) google or naver 같은 값을 가질 수 있다. String provider = oAuth2UserInfo.getProvider(); // 사용자의 소셜 서비스(provider)에서 발급된 고유한 식별자를 가져옵니다. // 이 값은 해당 소셜 서비스에서 유니크한 사용자를 식별하는 용도로 사용됩니다. String providerId = oAuth2UserInfo.getProviderId(); String name = oAuth2UserInfo.getName(); // 사용자의 이메일 주소를 가지고 옵니다. // 소셜 서비스에서 제공하는 이메일 정보를 사용합니다. String email = oAuth2UserInfo.getEmail(); // 소셜 로그인의 경우 무조건 USER 등급으로 고정이다. Role role = Role.USER; MemberEntity findUser = memberRepository.findByEmail(email); if(findUser == null) { log.info("소셜 로그인이 최초입니다."); log.info("소셜 로그인 자동 회원가입을 진행합니다."); findUser = MemberEntity.builder() .email(email) .memberName(name) .provider(provider) .providerId(providerId) .memberRole(role) .nickName(name) .build(); log.info("member : " + findUser); findUser = memberRepository.save(findUser); } else { log.info("로그인을 이미 한적이 있습니다."); } // 권한 가져오기 List<GrantedAuthority> authorities = getAuthoritiesForUser(findUser); // 토큰 생성 TokenDTO tokenForOAuth2 = jwtProvider.createTokenForOAuth2(email, authorities, findUser.getMemberId()); // 기존에 이 토큰이 있는지 확인 TokenEntity findToken = tokenRepository.findByMemberEmail(tokenForOAuth2.getMemberEmail()); TokenEntity saveToken; // 기존의 토큰이 없다면 새로 만들어준다. if(findToken == null) { TokenEntity tokenEntity = TokenEntity.tokenEntity(tokenForOAuth2); saveToken = tokenRepository.save(tokenEntity); log.info("token : " + saveToken); } else { // 기존의 토큰이 있다면 업데이트 해준다. tokenForOAuth2 = TokenDTO.builder() .grantType(tokenForOAuth2.getGrantType()) .accessToken(tokenForOAuth2.getAccessToken()) .accessTokenTime(tokenForOAuth2.getAccessTokenTime()) .refreshToken(tokenForOAuth2.getRefreshToken()) .refreshTokenTime(tokenForOAuth2.getRefreshTokenTime()) .memberEmail(tokenForOAuth2.getMemberEmail()) .memberId(tokenForOAuth2.getMemberId()) .build(); TokenEntity tokenEntity = TokenEntity.updateToken(findToken.getId(), tokenForOAuth2); saveToken = tokenRepository.save(tokenEntity); log.info("token : " + saveToken); } // 토큰이 제대로 되어 있나 검증 if(StringUtils.hasText(saveToken.getAccessToken()) && jwtProvider.validateToken(saveToken.getAccessToken())) { Authentication authenticationToken = jwtProvider.getAuthentication(saveToken.getAccessToken()); log.info("authentication : " + authenticationToken); SecurityContextHolder.getContext().setAuthentication(authenticationToken); UserDetails userDetails = new User(email, "", authorities); log.info("userDetails : " + userDetails); Authentication authenticationUser = new UsernamePasswordAuthenticationToken(userDetails, authorities); log.info("authentication1 : " + authenticationUser); SecurityContextHolder.getContext().setAuthentication(authenticationUser); } else { log.info("검증 실패"); } // attributes가 있는 생성자를 사용하여 PrincipalDetails 객체 생성 // 소셜 로그인인 경우에는 attributes도 함께 가지고 있는 PrincipalDetails 객체를 생성하게 됩니다. PrincipalDetails principalDetails = new PrincipalDetails(findUser, oAuth2User.getAttributes()); log.info("principalDetails : " + principalDetails); return principalDetails; } // 권한 가져오기 로직 private List<GrantedAuthority> getAuthoritiesForUser(MemberEntity findUser) { Role role = findUser.getMemberRole(); List<GrantedAuthority> authorities = new ArrayList<>(); authorities.add(new SimpleGrantedAuthority("ROLE_" + role.name())); log.info("권한 : " + role.name()); return authorities; } } @Log4j2 @RequiredArgsConstructor @Component public class OAuth2SuccessHandler implements AuthenticationSuccessHandler { private final MemberRepository memberRepository; private final TokenRepository tokenRepository; // Jackson ObjectMapper를 주입합니다. private final ObjectMapper objectMapper; @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { try { log.info("OAuth2 Login 성공!"); // 소셜 로그인 이메일 가져오기 String email = authentication.getName(); log.info("email : " + email); // 토큰 조회 TokenEntity findToken = tokenRepository.findByMemberEmail(email); log.info("token : " + findToken); // 토큰 DTO 반환 TokenDTO tokenDTO = TokenDTO.toTokenDTO(findToken); // 회원 조회 MemberEntity findUser = memberRepository.findByEmail(email); // 회원 DTO 반환 ResponseMemberDTO memberDTO = ResponseMemberDTO.socialMember(findUser); // 헤더에 담아준다. response.addHeader("email", memberDTO.getEmail()); // 바디에 담아준다. Map<String, Object> responseBody = new HashMap<>(); responseBody.put("providerId", memberDTO.getProviderId()); responseBody.put("provider", memberDTO.getProvider()); responseBody.put("accessToken", tokenDTO.getAccessToken()); responseBody.put("refreshToken", tokenDTO.getRefreshToken()); responseBody.put("email", tokenDTO.getMemberEmail()); responseBody.put("memberId", tokenDTO.getMemberId()); responseBody.put("grantType", tokenDTO.getGrantType()); // JSON 응답 전송 response.setContentType("application/json"); response.setCharacterEncoding("UTF-8"); response.getWriter().write(objectMapper.writeValueAsString(responseBody)); } catch (Exception e) { // 예외가 발생하면 클라이언트에게 오류 응답을 반환 response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); response.getWriter().write("OAuth 2.0 로그인 성공 후 오류 발생: " + e.getMessage()); response.getWriter().flush(); } } }로컬에서 소셜로그인이 성공하면 OAuth2SuccessHandler에서 바로 JSON으로 반환해주는 형태입니다. 즉, 컨트롤러가 딱히 무슨 역할을 하지 않아도 바로 반환을 해줍니다. 하지만 EC2에 배포하고 구글 개발자 센터, yml에 EC2 퍼블릭을 제대로 입력해주고로그인한 결과 아이디들 제대로 뜨는데 로그인할 아이디를 클릭을 하면Whitelabel Error Page 404페이지가 뜹니다. 로컬 코드를 그대로 배포한건데 왜 안될까요?
-
미해결
소셜로그인 accessToken 검증
security에서 OAuth2 클라이언트를 사용해서 구글이나 네이버 api를 사용하지 않고 자동으로 소셜 로그인 accessToken을 검증해주고 정보를 가져올 수 있다는 기능이 있는걸로 알고있어서 gpt에 검색해보니package com.example.social.config.security; import com.example.social.config.jwt.JwtAccessDeniedHandler; import com.example.social.config.jwt.JwtAuthenticationEntryPoint; import com.example.social.config.jwt.JwtProvider; import com.example.social.config.oauth2.PrincipalOauth2UserService; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.DelegatingPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistrations; import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository; import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.user.DefaultOAuth2User; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.web.SecurityFilterChain; import java.util.HashMap; import java.util.Map; @Configuration @RequiredArgsConstructor @EnableWebSecurity public class SecurityConfig { private final PrincipalOauth2UserService principalOauth2UserService; private final JwtProvider jwtProvider; @Value("${spring.security.oauth2.client.registration.google.client-id}") private String googleClientId; @Value("${spring.security.oauth2.client.registration.google.client-secret}") private String googleClientSecret; @Value("${spring.security.oauth2.client.registration.naver.client-id}") private String naverClientId; @Value("${spring.security.oauth2.client.registration.naver.client-secret}") private String naverClientSecret; @Bean // InMemoryClientRegistrationRepository를 생성하고 반환합니다. // InMemoryClientRegistrationRepository는 OAuth 2.0 클라이언트 등록 정보를 // 메모리에 보관하고 관리하는 데 사용됩니다. public InMemoryClientRegistrationRepository clientRegistrationRepository() { // ClientRegistration 객체를 생성하고 OAuth 2.0 클라이언트의 등록 정보를 설정합니다. ClientRegistration googleRegistration = ClientRegistration .withRegistrationId("google") .clientId(googleClientId) .clientSecret(naverClientSecret) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .redirectUri("http://localhost:8080/login/oauth2/code/google") // Google의 authorizationUri .authorizationUri("https://accounts.google.com/o/oauth2/auth") .tokenUri("https://oauth2.googleapis.com/token") .scope("openid", "profile", "email") .clientName("Google") .build(); ClientRegistration naverRegistration = ClientRegistration .withRegistrationId("naver") .clientId(naverClientId) .clientSecret(naverClientSecret) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .redirectUri("http://localhost:8080/login/oauth2/code/naver") // Naver의 authorizationUri .authorizationUri("https://nid.naver.com/oauth2.0/authorize") .tokenUri("https://nid.naver.com/oauth2.0/token") .scope("openid", "profile", "email") .clientName("Naver") .build(); return new InMemoryClientRegistrationRepository(googleRegistration, naverRegistration); } @Bean // OAuth2UserService 타입의 빈을 생성합니다. 이 빈은 OAuth2 로그인 처리에 사용됩니다. // OAuth2UserService는 OAuth2 로그인 후에 사용자 정보를 가져오고 처리하는 인터페이스입니다. public OAuth2UserService<OAuth2UserRequest, OAuth2User> googleOAuth2UserOAuth2UserService() { // new DefaultOAuth2UserService() { ... }: // OAuth2UserService 인터페이스를 구현하는 익명 클래스를 생성합니다. // 이 클래스는 OAuth2 로그인 처리에 사용될 사용자 서비스를 정의합니다. return new DefaultOAuth2UserService() { @Override // loadUser(OAuth2UserRequest userRequest) { ... }: // OAuth2UserService 인터페이스의 loadUser 메서드를 오버라이드합니다. // 이 메서드는 OAuth2 로그인 후에 호출되며, 사용자 정보를 가져옵니다. public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { // super.loadUser(userRequest)를 호출하여 OAuth2 로그인 후에 사용자 정보를 가져옵니다. // OAuth2User 객체는 사용자의 인증된 속성 및 권한을 포함하고 있습니다. OAuth2User user = super.loadUser(userRequest); // OAuth2 로그인 후에 반환되는 OAuth2User 객체를 수정하거나 구성하여 반환합니다. // 여기에서는 사용자의 권한, 속성 및 고유 식별자(sub)를 포함하는 // DefaultOAuth2User 객체를 반환하고 있습니다. return new DefaultOAuth2User( user.getAuthorities(), user.getAttributes(), // 사용자의 고유 식별자 (일반적으로 'sub'라는 속성에 저장됨) "sub" ); } }; } // naver @Bean @Qualifier("naver") public OAuth2UserService<OAuth2UserRequest, OAuth2User> naverOAuth2UserService() { return new DefaultOAuth2UserService() { @Override public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { OAuth2User user = super.loadUser(userRequest); // 사용자 정보 처리 // user.getName(), user.getAttributes()를 사용하여 필요한 정보를 가져올 수 있음 String uniqueIdentifier = user.getAttribute("id"); return new DefaultOAuth2User( user.getAuthorities(), user.getAttributes(), uniqueIdentifier ); } }; } @Bean public SecurityFilterChain filterChain( HttpSecurity http) throws Exception { http // HTTP 기본 인증 비활성화 .httpBasic().disable() // CSRF(Cross-Site Request Forgery) 공격 방어 비활성화 .csrf().disable() // 폼 기반 로그인 비활성화 .formLogin().disable() // 로그아웃 관련 설정 비활성화 .logout().disable() // 세션 관리를 STATELESS로 설정하여 세션을 사용하지 않도록 설정 // JWT를 사용할거기 때문 .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); http .authorizeRequests() .antMatchers("/api/v1/users/**").permitAll(); http // JWT를 위한 Filter를 아래에서 만들어 줄건데 // 이 Filter를 어느위치에서 사용하겠다고 등록을 해주어야 Filter가 작동이 됩니다. // JWT를 검증하기 위한 JwtSecurityConfig를 적용하고 // jwtProvider를 사용하여 JWT 검증을 수행합니다. .apply(new JwtSecurityConfig(jwtProvider)); // 에러 방지 http .exceptionHandling() // 인증 에러 핸들링을 위한 커스텀 JwtAuthenticationEntryPoint 등록. .authenticationEntryPoint(new JwtAuthenticationEntryPoint()) // 권한 에러 핸들링을 위한 커스텀 JwtAccessDeniedHandler 등록 .accessDeniedHandler(new JwtAccessDeniedHandler()); // OAuth2 http // oauth2Login() 메서드는 OAuth 2.0 프로토콜을 사용하여 소셜 로그인을 처리하는 기능을 제공합니다. .oauth2Login() .clientRegistrationRepository(clientRegistrationRepository()) // OAuth2 로그인 성공 이후 사용자 정보를 가져올 때 설정 담당 .userInfoEndpoint() // OAuth2 로그인 성공 시, 후작업을 진행할 서비스 .userService(principalOauth2UserService); // http // // Spring Security에게 OAuth2 리소스 서버를 설정하도록 지시하는 부분입니다. // // 즉, 이 설정 아래에서 JWT를 검증하는 데 필요한 구성을 수행합니다. // .oauth2ResourceServer() // // OAuth2 리소스 서버가 JWT 토큰을 사용한다고 알려줍니다. // // Spring Security에 JWT 검증을 수행하도록 설정합니다. // .jwt() // // JWT 디코더를 설정합니다. // // JWT 디코더는 JWT 토큰을 검증하고 내용을 추출하는 데 사용됩니다. // .decoder(this.jwtDecoder()); return http.build(); } @Bean PasswordEncoder passwordEncoder() { String idForEncode = "bcrypt"; Map<String, PasswordEncoder> encoders = new HashMap<>(); encoders.put(idForEncode, new BCryptPasswordEncoder()); return new DelegatingPasswordEncoder(idForEncode, encoders); } // JWT 디코더를 생성하는 메서드를 정의합니다. // Google의 공개 키를 가져와 JWT 토큰의 서명을 검증하기 위해 사용합니다. // withJwkSetUri 메서드를 통해 Google의 공개 키를 가져오는 URI를 지정합니다. // 이러한 구성을 통해 Spring Security는 Google OAuth2에서 제공하는 JWT 토큰의 유효성을 검사하고, // 토큰이 유효하면 해당 사용자에 대한 정보를 추출할 수 있습니다. // @Bean // public JwtDecoder jwtDecoder() { // return NimbusJwtDecoder.withJwkSetUri("https://www.googleapis.com/oauth2/v3/certs") // .build(); // } } 이런식으로 나오더군요. 생각하는 로직은 프론트에서 소셜로그인이 성공하면 accessToken을 받고 그거를 바로 서버에 보내줘서 헤더에 담긴 accessToken을 security 기능으로 검증하고 정보를 빼오려고 했습니다. 일반로그인시 제가 만든 JWT를 반환해서 accessToken을 JwtAuthenicationFilter에서 검증한다면 소셜 로그인 accessToken은 security가 지원하는 기능으로 검증하려고 했는데 제가 잘못생각하고 있는건지 소셜 로그인 accessToken이 계속 JwtAuthenicationFilter에서 검사하고 틀린 JWT라고 나오네요.git : https://github.com/YuYoHan/social_login질문 1:제가 생각하고 있는 흐름이 맞나요?질문 2:1번이 맞다면 security 기능으로 사용해서 어떻게 소셜 로그인 accessToken을 검사할 수 있나요?질문 3:1번이 틀렸다면 어느 부분이 틀리고 어떻게 수정해야할까요? 현재 JWT 발급과 JWT를 검증하는 부분은 성공했지만 소셜 로그인 accessToken을 검증해서 정보를 빼와서 JWT를 만들어 반환해주는 부분이 막혀있는 상태입니다 ㅠㅠ 도와주세요
-
미해결[리뉴얼] Node.js 교과서 - 기본부터 프로젝트 실습까지
클라이언트 서버에서 소셜 로그인
현재 상황클라이언트와 서버를 각자 다른 도메인에 배포했습니다.소셜 로그인은 passport 모듈과 session을 사용했습니다.서버에서 소셜 로그인이 성공해도 클라이언트에는 세션 쿠키가 전달되지 않습니다.클라이언트axios get 요청을 보내면 cors 에러가 떠서 a 태그를 사용했습니다.<a href="http://서버ip/auth/github">깃 헙 로그인<a/>서버아래의 코드는 강의의 내용과 동일합니다. passport 모듈을 활용해 소셜 로그인을 구현했습니다.import express from "express"; import passport from "passport"; import { isLoggedIn, isNotLoggedIn } from "../middlewares/authMiddleware.js"; const router = express.Router(); router.get("/github", passport.authenticate("github")); router.get( "/github/callback", passport.authenticate("github", { failureRedirect: "/", }), (req, res) => { res.redirect(`http://localhost:3000`); } ); router.get("/logout", isLoggedIn, (req, res) => { req.logout(); req.session.destroy(); res.json({ message: "logout" }); }); export default router;소셜 로그인에 성공하면 클라이언트 서버의 메인 페이지로 redirect 시키고 있습니다.문의서버에서 소셜 로그인 성공시 클라이언트 서버에도 session 쿠키를 전달하고 싶습니다.두 개의 다른 서버에서 소셜 로그인을 어떻게 구현해야될지 모르겠습니다.
-
미해결Do It! 장고+부트스트랩: 파이썬 웹개발의 정석
소셜 로그인 관련 질문 있습니다.
강사님 덕분이 장고 공부 잘 하고 있습니다. 장고 소셜 로그인 할 때 저는 다음 창이 나오네요? 강사님은 로그인할 때 안나오던데요. 로그인, 로그아웃 할 때도 안나오게 하고싶은데 방법이 있을까요? 로그인 버튼 눌렀을 때 나오는 첫화면 로그아웃 버튼 눌렀을 때 나오는 화면 관련 문의는 1:1 문의하기를 이용해주세요.
-
미해결
소셜 로그인 쉽게 붙이는법
카카오나 네이버같이 소셜 로그인 연동 관련해서 도움 받을 수 있을 만한 곳 있을까요?? SNS 로그인 API 관련 강의는 없네요ㅠㅠ