묻고 답해요
156만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결카카오,구글 SNS 로그인(springboot3, vue3)
소셜 로그인 설계
안녕하세요 강사님 혹시 oauth2 를 이용한 로그인을 한 다음에 사용자 프로필을 받는 것이 서비스 플로우인데 이럴경우 웹이 아닌 앱을 사용할경우 (로직은 다른 거 알고 있습니다.) (PCKE 방식 사용 예정입니다.)oauth2 를 해서 사용자 db에 저장이 되고 이렇게 처음에 저장이 되고 그 다음에 프로필을 받으면 저 값이 수정이 되게 만들어 주는 게 맞을지 질문드립니다. 사용자엔티티에 Provider 나 socialId 로 값을 받습니다. (구글과 애플을 사용할 예정입니다.) @Entity @Table(name = "users") @Getter @NoArgsConstructor @Setter public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "user_id") private Long id; @Column(name = "name", nullable = false) private String name; @Enumerated(EnumType.STRING) @Column(name = "SEX", nullable = false) private Sex sex; @Column(name = "age", nullable = false) private Integer age; @Column(name = "nationality", nullable = false) private String nationality; @Column(name = "introduction", length = 40, nullable = false) private String introduction; @Column(name = "visit_purpose", length = 40, nullable = false) private String visitPurpose; @Column(name = "languages", nullable = false) private String languages; @Column(name = "hobby", nullable = false) private String hobby; @Column(name = "created_at", nullable = false) private Instant createdAt; @Column(name = "updated_at", nullable = false) private Instant updatedAt; @Column(name = "Provider", nullable = false) private String provider; @Column(name = "social_id", nullable = false) private String socialId; @Column(name = "email", nullable = false) private String email; @Builder public User(String name, Sex sex, Integer age, String nationality, String introduction, String visitPurpose, String languages, String hobby, String provider, String socialId, String email) { this.name = name; this.sex = sex; this.age = age; this.nationality = nationality; this.introduction = introduction; this.visitPurpose = visitPurpose; this.languages = languages; this.hobby = hobby; this.provider = provider; this.socialId = socialId; this.email = email; this.createdAt = Instant.now(); this.updatedAt = Instant.now(); } public void updateProfile(UserUpdateDTO dto) { if (dto.getName() != null && !dto.getName().trim().isEmpty()) { this.name = dto.getName().trim(); } if (dto.getSex() != null) { this.sex = dto.getSex(); } if (dto.getAge() != null) { this.age = dto.getAge(); } if (dto.getNationality() != null && !dto.getNationality().trim().isEmpty()) { this.nationality = dto.getNationality().trim(); } if (dto.getIntroduction() != null && !dto.getIntroduction().trim().isEmpty()) { this.introduction = dto.getIntroduction().trim(); } if (dto.getVisitPurpose() != null && !dto.getVisitPurpose().trim().isEmpty()) { this.visitPurpose = dto.getVisitPurpose().trim(); } if (dto.getLanguages() != null && !dto.getLanguages().trim().isEmpty()) { this.languages = dto.getLanguages().trim(); } if (dto.getHobby() != null && !dto.getHobby().trim().isEmpty()) { this.hobby = dto.getHobby().trim(); } this.updatedAt = Instant.now(); // 수정 시각 갱신 } }
-
미해결카카오,구글 SNS 로그인(springboot3, vue3)
카카오 인가코드 요청
카카오 인가 코드 요청 url이 변경 된 것 같습니다!
-
해결됨카카오,구글 SNS 로그인(springboot3, vue3)
구글 로그인 프론트 화면
안녕하세요 구글 로그인 구현 중 질문있어서 질문드립니다! googleUrl, googleClientId, googleRedirectUrl, googleScope, googleResponseType 을 const auth_url = `${this.googleUrl}?client_id=${this.googleClientId}&redirect_url=${this.googleRedirectUrl}&response_type=code&scope=${this.googlescope}`; window.location.href = auth_url; 구글로그인 화면을 누르면 리다이렉트 url에 오류가 있다고 하는데 동일한 리다이렉트 url로 해도 잘 안됩니다 이럴 경우 또 어떤 것을 확인해보는게 좋을까요??
-
미해결카카오,구글 SNS 로그인(springboot3, vue3)
github 주소 어디서 확인가능한가요?
소스코드 올라온 github 주소 어디서 확인이 가능할까요?
-
미해결카카오,구글 SNS 로그인(springboot3, vue3)
안녕하세요 선생님
혹시 다음 강의도 준비 중이신가요 ? 준비 중이시라면 어떤 강의 인지 알 수 있을까요
-
해결됨카카오,구글 SNS 로그인(springboot3, vue3)
application.yml에 동일 OAuth2 제공업체의 redirect-uri를 여러 개 등록할 수 있을지 궁금합니다.
안녕하세요.application.yml에 동일 OAuth2 제공업체의 redirect-uri를 여러 개 등록할 수 있을지 궁금합니다. 현재 OAuth 제공업체에 개발 및 테스트용과 배포용으로 redirect-uri를 여러 개 등록하였습니다.해당 redirect-uri를 application.yml에 복수로 등록하여 사용할 수 있을까에 대한 의견이 궁금합니다.
-
미해결카카오,구글 SNS 로그인(springboot3, vue3)
안녕하세요 섹션2. 인가코드(프론트에서 발급) 부분에서 질문 있습니다.
안녕하세요 먼저 좋은 강의 정말 감사드립니다.섹션2 인가코드(프론트에서 발급) 강의와 강의 자료에서 get 요청을 통해 인가코드 전달을 한다고 설명하셨는데, 프론트엔드 코드에서는 post로 요청하고 있고, http body로 jwt 토큰을 받으려면 post로 요청해야하는게 맞는 것 같은데, 제가 잘못 이해하고 있는건지 잘못 설명하신건지 헷갈려서 질문글 남깁니다. const response = await axios.post("http://localhost:8080/member/google/doLogin", {code});
-
미해결카카오,구글 SNS 로그인(springboot3, vue3)
filterchain 구성에서 포스트맨으로 테스트시
안녕하세요 레디스 강의에 이어 잘 수강하고 있습니다!filterchain 구성에서 포스트맨으로 테스트 시에 raw password가 그대로 컨트롤러에 전달되는데, 실무에서는 (Q.1) 이렇게 하는 방식이 허용되는지 궁금합니다. (Q.2) 안된다면 어떤식으로 해결하는지 궁금하네요!
-
해결됨카카오,구글 SNS 로그인(springboot3, vue3)
프론트엔드에서 Auth.js를 사용했을 때, 질문 있습니다.
안녕하세요. 좋은 품질의 강의를 제작해주셔서 감사합니다. 프론트엔드 부분을 vue.js에서 Next.js로 변경하여 프로젝트를 진행하고 있습니다. 해당 과정에서 막힘이 있어 질문을 드립니다. Next.js를 기반으로 Auth.js 라이브러리를 사용하여 프론트엔드 소셜 로그인을 구현하고 있습니다.소셜 로그인 흐름을 다음과 같이 생각하고 있습니다.1. 유저가 소셜 로그인 버튼을 누른다. 2. 프론트엔드에서 해당 소셜 로그인 화면을 리다이렉트 시킨다. 3. 유저는 소셜 로그인을 진행한다. 4. 소셜 로그인 성공 시, 프론트엔드 서버는 인증 코드를 소셜 서버로부터 받은 후, 백엔드로 소셜 로그인 API를 인증 코드 첨부하여 요청한다. 5. 백엔드 서버에서 인증 코드를 가지고 액세스 토큰 발급, 엑세스 토큰으로 유저 정보를 받아서 JWT를 만들어 프론트엔드로 반환해준다. 하지만 다음 피드백을 받았습니다. "Auth.js를 사용하면 소셜 서버로부터 인가코드를 직접 받아올 수 없어 백엔드 서버로 인가코드 요청을 할 수 없다."위 의견이 맞다면, 백엔드 서버에서 소셜 로그인 전 과정을 진행하고 JWT 값만 프론트엔드로 반환하는 방법 밖에 없는걸까요? 강의에서 사용되지 않은 기술에 대해서 질문하여 죄송합니다.감사합니다.
-
미해결카카오,구글 SNS 로그인(springboot3, vue3)
[질문] 소셜 로그인한 적이 없다면 회원가입 시키기
안녕하세요. 강의 감사히 잘 듣고 있습니다.강의를 듣다가 궁금한 점이 생겨 질문 드리고 싶습니다. 강의 16분쯤에 회원가입이 되어있지 않다면 회원가입한다고 말씀하셨습니다.이때 socialId 로 회원가입 여부를 판단하신다고 하셨는데, 왜 email 로는 하지 않으셨는지 궁금합니다. email 도 unique 제약조건을 걸었기 때문에 email 도 가능하다고 생각이 들었는데 강사님의 의견은 어떠한지 여쭤보고 싶습니다. 질문 읽어주셔서 감사합니다.
-
해결됨카카오,구글 SNS 로그인(springboot3, vue3)
토큰 생성강의 코드 실행 문의
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/oauthdb?useSSL=false username: root password: jinwook219 jpa: database: mysql # InnoDB?? database-platform: org.hibernate.dialect.MySQL8Dialect generate-ddl: true hibernate: ddl-auto: create show_sql: true jwt: # 32글자 이상 인코딩된 문자열 : oauthserversecretaccesstokenoauthserversecretaccesstokenoauthserversecretaccesstoken secret: b2F1dGhzZXJ2ZXJzZWNyZXRhY2Nlc3N0b2tlbm9hdXRoc2VydmVyc2VjcmV0YWNjZXNzdG9rZW5vYXV0aHNlcnZlcnNlY3JldGFjY2Vzc3Rva2Vu expiration: 3000 #분단위 package com.example.oauth.auth; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import javax.crypto.spec.SecretKeySpec; import java.security.Key; import java.util.Date; @Component public class JwtTokenProvider { private final String secretKey; private final int expiration; private Key SECRET_KEY; public JwtTokenProvider(@Value("${jwt.secret}") String secretKey, @Value("${jwt.expiration}") int expiration) { this.secretKey = secretKey; this.expiration = expiration; this.SECRET_KEY = new SecretKeySpec(java.util.Base64.getDecoder().decode(secretKey), SignatureAlgorithm.HS512.getJcaName()); } public String createToken(String email, String role){ // claims는 jwt토큰의 payload부분을 의미 Claims claims = Jwts.claims().setSubject(email); claims.put("role", role); Date now = new Date(); String token = Jwts.builder() .setClaims(claims) .setIssuedAt(now) .setExpiration(new Date(now.getTime()+ expiration*60*1000L)) .signWith(SECRET_KEY) .compact(); return token; } } Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled. 2025-04-29T00:35:55.742+09:00 ERROR 3891 --- [ main] o.s.boot.SpringApplication : Application run failed org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jwtTokenProvider' defined in file [/Users/macforjinwook/Desktop/oauth/build/classes/java/main/com/example/oauth/auth/JwtTokenProvider.class]: Unexpected exception during bean creation at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:536) ~[spring-beans-6.2.5.jar:6.2.5] at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:339) ~[spring-beans-6.2.5.jar:6.2.5] at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:347) ~[spring-beans-6.2.5.jar:6.2.5] at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:337) ~[spring-beans-6.2.5.jar:6.2.5] at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-6.2.5.jar:6.2.5] at org.springframework.beans.factory.support.DefaultListableBeanFactory.instantiateSingleton(DefaultListableBeanFactory.java:1155) ~[spring-beans-6.2.5.jar:6.2.5] at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingleton(DefaultListableBeanFactory.java:1121) ~[spring-beans-6.2.5.jar:6.2.5] at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:1056) ~[spring-beans-6.2.5.jar:6.2.5] at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:987) ~[spring-context-6.2.5.jar:6.2.5] at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:627) ~[spring-context-6.2.5.jar:6.2.5] at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.4.4.jar:3.4.4] at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:752) ~[spring-boot-3.4.4.jar:3.4.4] at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:439) ~[spring-boot-3.4.4.jar:3.4.4] at org.springframework.boot.SpringApplication.run(SpringApplication.java:318) ~[spring-boot-3.4.4.jar:3.4.4] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1361) ~[spring-boot-3.4.4.jar:3.4.4] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1350) ~[spring-boot-3.4.4.jar:3.4.4] at com.example.oauth.OauthApplication.main(OauthApplication.java:10) ~[main/:na] Caused by: org.springframework.util.PlaceholderResolutionException: Could not resolve placeholder 'jwt.secret' in value "${jwt.secret}" at org.springframework.util.PlaceholderResolutionException.withValue(PlaceholderResolutionException.java:81) ~[spring-core-6.2.5.jar:6.2.5] at org.springframework.util.PlaceholderParser$ParsedValue.resolve(PlaceholderParser.java:423) ~[spring-core-6.2.5.jar:6.2.5] at org.springframework.util.PlaceholderParser.replacePlaceholders(PlaceholderParser.java:128) ~[spring-core-6.2.5.jar:6.2.5] at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:118) ~[spring-core-6.2.5.jar:6.2.5] at org.springframework.util.PropertyPlaceholderHelper.replacePlaceholders(PropertyPlaceholderHelper.java:114) ~[spring-core-6.2.5.jar:6.2.5] at org.springframework.core.env.AbstractPropertyResolver.doResolvePlaceholders(AbstractPropertyResolver.java:255) ~[spring-core-6.2.5.jar:6.2.5] at org.springframework.core.env.AbstractPropertyResolver.resolveRequiredPlaceholders(AbstractPropertyResolver.java:226) ~[spring-core-6.2.5.jar:6.2.5] at org.springframework.context.support.PropertySourcesPlaceholderConfigurer.lambda$processProperties$0(PropertySourcesPlaceholderConfigurer.java:201) ~[spring-context-6.2.5.jar:6.2.5] at org.springframework.beans.factory.support.AbstractBeanFactory.resolveEmbeddedValue(AbstractBeanFactory.java:971) ~[spring-beans-6.2.5.jar:6.2.5] at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1577) ~[spring-beans-6.2.5.jar:6.2.5] at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1555) ~[spring-beans-6.2.5.jar:6.2.5] at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:913) ~[spring-beans-6.2.5.jar:6.2.5] at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791) ~[spring-beans-6.2.5.jar:6.2.5] at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:240) ~[spring-beans-6.2.5.jar:6.2.5] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1381) ~[spring-beans-6.2.5.jar:6.2.5] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1218) ~[spring-beans-6.2.5.jar:6.2.5] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:563) ~[spring-beans-6.2.5.jar:6.2.5] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:523) ~[spring-beans-6.2.5.jar:6.2.5] ... 16 common frames omitted 종료 코드 1(으)로 완료된 프로세스 Caused by: org.springframework.util.PlaceholderResolutionException: Could not resolve placeholder 'jwt.secret' in value "${jwt.secret}"이 부분을 보면 yml에 있는 jwt.secret을 인식 못하는 것 같은데 원인을 못 찾겠습니다 강사님 깃허브 코드를 붙혀넣어봐도 왜 이런지 잘 모르겠습니다. 혹시 이런 경우에는 어떤 것을 찾아봐야하나요??
-
해결됨카카오,구글 SNS 로그인(springboot3, vue3)
refresh 와 naver login 도 두번째 강의도 만들어주시면 어떨까요
이 수업 듣고 완전 이해 했습니다.refresh와 로그인도 해주시면 진짜 감사합니당ㅎㅎㅎ kotlin하고 nextjs 로 뚝딱뚝딱 만들었는데 이 정도만 하고 aws 에 올리면 이직 시 포트폴리오로 제출해도 문제 없을 것 같아요.
-
해결됨카카오,구글 SNS 로그인(springboot3, vue3)
백엔드 서버에서 소셜 로그인을 전부 처리하는 것에서 질문 있습니다.
"인가코드(백엔드에서 발급)" 강의에서 백엔드에서 소셜 로그인을 처리 부분에 질문이 있습니다.해당 방식의 단점이 JWT 반환 시, redirect 방식을 사용하므로 JWT 토큰 값이 노출될 수 있어 보안상 문제가 될 수 있다고 이해했습니다.하지만 JWT 토큰 값 자체는 브라우저 로컬 스토리지에 저장하면 어떻게든 유저에게 노출되는 것 아닌가 라는 생각이 듭니다.이에 대한 강사님의 생각이 궁금합니다.
-
미해결카카오,구글 SNS 로그인(springboot3, vue3)
강의 내용 문의 입니당
안녕하세요수업 들으면서 동시에 코틀린으로 코딩하다보니 중간중간 놓치는 부분이 있어서 코드 제공이 가능할까요?코드 제공 주신 다는 내용은 들었는데 자료에 안보여서 동영상을 여기저기 반복 재생하니 진도가 느려지네요
-
미해결
Flutter Android에서 딥링크로 callback 받기
Oauth2 인증 java서버에서 인증이 성공한 후 Flutter Android에서 설정한 딥링크로 go_router로 callback을 받는 기능을 만들고 싶어서 노력중입니다. 근데 인터넷검색을 통해 딥링크 설정도 해보고 그랬지만 플러터에서 콜백을 받지를 못하는 것같은데 어떤 설정이 필요한지 잘 모르겠습니다. 현재 AndroidManifest.xml에<intent-filter> <action android:name="android.intent.action.VIEW"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.BROWSABLE"/> <data android:scheme="testapp"/> </intent-filter>설정도 되어있고 List<GoRoute> get routes => [ // GoRoute( // path: '/', // name: RootTab.routeName, // builder: (context, state) => RootTab(), // ), GoRoute( path: '/profile', name: ProfileScreen.routeName, builder: (context, state) => ProfileScreen(), ), GoRoute( path: '/login', name: LoginScreen.routeName, builder: (context, state) => LoginScreen(), ), GoRoute( path: '/callback', name: CallbackScreen.routeName, redirect: (context, state) async { final code = state.queryParameters["code"]; if(code != null){ return "/"; } else { //code없으면 실패한걸로 간주하고 다시 login페이지로 redirect return "/login"; } }, ), ]; 어떤 설정을 더 해야 콜백을 받을 수 있을 까요? callback을 받으면 나는 에러는 DioException [unknown]: null Error: Invalid argument(s): Unsupported scheme 'testapp' in URI testapp://callback?이렇게 납니다조언해주시면 감사합니다.
-
미해결스프링부트 시큐리티 & JWT 강의
안녕하세요! 질문있습니당!
11강 네이버 로그인까지 완료하고 @AuthenticationPrincipal을 이용해서 로그인한 정보를 가져오려고 하는데, 구글로 로그인했을 때는 정보가 출력되는데, 네이버로 로그인했을 때는 null로 나와서 구글링하며 찾아보았는데 해결이 안 되어서…. 질문 남깁니다.
-
미해결
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페이지가 뜹니다. 로컬 코드를 그대로 배포한건데 왜 안될까요?
-
미해결
배포 후 소셜 로그인
로컬에서는 구글 소셜로그인이 제대로 돌아가고 로그인시 가입, JWT 발급까지 제대로 돌아가는데 프로젝트를 EC2에 배포하고 개발자센터에 승인된 URI에 등록하고 yml에 redirect-uri 똑같이 등록을 했는데 배포시에는 에러가 발생합니다.발생한 에러:구글 아이디들이 나오기는 하는데 클릭을 하면이 에러가 발생합니다. 이게 로컬에서도 안되면 이해가 가는데 로컬에서는 에러없이 잘돌아갑니다. 대체 무슨 문제일까요... ㅠㅠ 급합니다.
-
해결됨NodeFull Stack 개발 가이드
강의 자료
안녕하세요 수업 잘 듣고 있습니다!그런데 노션에 올려주신 수업자료 수강생들에게만 공개하신다고 하셨는데 제가 학습방법까지 들었는데 어떻게 접근하는지를 잘 모르겠어서 질문드립니당!