카카오 데브톡 응답에 따르면 프론트/백엔드 분할 책임 방식이 지양한다고 합니다. 오히려 백에서 로그인 구현을 일임하는 것을 권장합니다. https://devtalk.kakao.com/t/oauth/136448 해당 블로그에서도 책임을 프론트와 백엔드가 나누어 가지는 방식 이 잘못되었다고 합니다. https://cafe.naver.com/xxxjjhhh/296 카카오 로그인 REST API에서도 백에서 인가 코드를 받습니다. https://developers.kakao.com/docs/ko/kakaologin/rest-api#before-you-begin-process 어떤 방식이 맞는지 헷갈립니다.
안녕하세요 강사님, 다름이 아니라 강의 들으면서 카카오 oauth 설정 및 테스트 중인데 카카오 REST API 키에도 클라이언트 시크릿이 생긴 것 같습니다. 구글처럼 yaml에 kakao.client.secret 키 넣어주고 요청 바디에 같이 보내니까 그제서야 액세스 토큰 및 프로필 정보 응답이 왔습니다. 강의 촬영 당시와 현재의 카카오 developers UI 및 메뉴 구성이 많이 바뀌어서 제가 모종의 설정을 놓친건지, 아니면 새롭게 카카오 클라이언트 시크릿이 추가되어서 이제는 구글처럼 시크릿 키를 요청 바디에 넣고 보내는 게 맞는 방법이 된 건지 말씀 여쭤보고자 질문 드립니다. 양질의 강의 항상 감사드립니다.
안녕하세요. 질문 먼저 요약하자면, id token 에 scope에 포함된 유저 정보가 같이 포함되어 넘어오는게 일반적인지, idtoken 검증만 진행한 뒤 액세스토큰으로 따로 조회하는게 일반적인지 궁금합니다. 1년쯤 전에, 소셜 로그인의 흐름을 이해해보기 위해 스프링 시큐리티 없이 카카오, 네이버, 구글 로그인을 적용해본 적이 있습니다. 원래 카카오와 구글은 OIDC를 지원했고, 네이버는 OIDC가 없었는데 최근에 추가되었더라구요. 그래서 OAuth2 스펙으로만 구성된 네이버 로그인 코드를 OIDC를 적용한 코드로 수정하고 있었는데.. 그러다가 의문이 생겨서 질문을 드리게 되었습니다. 저는 여태까지 필요한 유저 정보의 scope를 "openid" 항목과 함께 프로바이더에 전달하고, id token을 프로바이더로부터 전달받고, 그걸 검증한 뒤 id token에 담긴 유저 정보를 사용하면 된다고 생각하고 있었습니다. 실제로 카카오와 구글은 유저의 email 정보가 id token 페이로드에 담겨있었구요. 네이버 역시 https://nid.naver.com/.well-known/openid-configuration 의 메타데이터 중 scopes_supported 항목을 확인했을때 profile이라는 scope가 존재해서, 코드 요청할때 scope=openid+profile 을 파라미터로 전달했습니다. 그런데 id token을 확인해도 유저의 정보가 나오지 않더군요. 처음에는 단순히 네이버가 이 부분을 미구현했나? 생각을 했는데, 좀 찾아보니 id token에는 최소한의 신원만을 포함하고 세부적인 프로필은 따로 조회하는게 맞다는 말도 있더라구요. 그래서 질문을 올리게 되었습니다.
안녕하세요 구글 로그인 구현 중 질문있어서 질문드립니다! 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로 해도 잘 안됩니다 이럴 경우 또 어떤 것을 확인해보는게 좋을까요??
안녕하세요. application.yml에 동일 OAuth2 제공업체의 redirect-uri를 여러 개 등록할 수 있을지 궁금합니다. 현재 OAuth 제공업체에 개발 및 테스트용과 배포용으로 redirect-uri를 여러 개 등록하였습니다. 해당 redirect-uri를 application.yml에 복수로 등록하여 사용할 수 있을까에 대한 의견이 궁금합니다.
안녕하세요 먼저 좋은 강의 정말 감사드립니다. 섹션2 인가코드(프론트에서 발급) 강의와 강의 자료에서 get 요청을 통해 인가코드 전달을 한다 고 설명하셨는데, 프론트엔드 코드에서는 post로 요청하고 있고, http body로 jwt 토큰을 받으려면 post로 요청해야하는게 맞는 것 같은데, 제가 잘못 이해하고 있는건지 잘못 설명하신건지 헷갈려서 질문글 남깁니다. const response = await axios.post("http://localhost:8080/member/google/doLogin", {code});
안녕하세요 레디스 강의에 이어 잘 수강하고 있습니다! filterchain 구성에서 포스트맨으로 테스트 시에 raw password가 그대로 컨트롤러에 전달되는데, 실무에서는 (Q.1) 이렇게 하는 방식이 허용되는지 궁금합니다. (Q.2) 안된다면 어떤식으로 해결하는지 궁금하네요!
안녕하세요. 좋은 품질의 강의를 제작해주셔서 감사합니다. 프론트엔드 부분을 vue.js에서 Next.js로 변경하여 프로젝트를 진행하고 있습니다. 해당 과정에서 막힘이 있어 질문을 드립니다. Next.js를 기반으로 Auth.js 라이브러리를 사용하여 프론트엔드 소셜 로그인을 구현하고 있습니다. 소셜 로그인 흐름을 다음과 같이 생각하고 있습니다. 1. 유저가 소셜 로그인 버튼을 누른다. 2. 프론트엔드에서 해당 소셜 로그인 화면을 리다이렉트 시킨다. 3. 유저는 소셜 로그인을 진행한다. 4. 소셜 로그인 성공 시, 프론트엔드 서버는 인증 코드를 소셜 서버로부터 받은 후, 백엔드로 소셜 로그인 API를 인증 코드 첨부하여 요청한다. 5. 백엔드 서버에서 인증 코드를 가지고 액세스 토큰 발급, 엑세스 토큰으로 유저 정보를 받아서 JWT를 만들어 프론트엔드로 반환해준다. 하지만 다음 피드백을 받았습니다. "Auth.js를 사용하면 소셜 서버로부터 인가코드를 직접 받아올 수 없어 백엔드 서버로 인가코드 요청을 할 수 없다." 위 의견이 맞다면, 백엔드 서버에서 소셜 로그인 전 과정을 진행하고 JWT 값만 프론트엔드로 반환하는 방법 밖에 없는걸까요? 강의에서 사용되지 않은 기술에 대해서 질문하여 죄송합니다. 감사합니다.
안녕하세요. 강의 감사히 잘 듣고 있습니다. 강의를 듣다가 궁금한 점이 생겨 질문 드리고 싶습니다. 강의 16분쯤에 회원가입이 되어있지 않다면 회원가입한다고 말씀하셨습니다. 이때 socialId 로 회원가입 여부를 판단하신다고 하셨는데, 왜 email 로는 하지 않으셨는지 궁금합니다. email 도 unique 제약조건을 걸었기 때문에 email 도 가능하다고 생각이 들었는데 강사님의 의견은 어떠한지 여쭤보고 싶습니다. 질문 읽어주셔서 감사합니다.
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을 인식 못하는 것 같은데 원인을 못 찾겠습니다 강사님 깃허브 코드를 붙혀넣어봐도 왜 이런지 잘 모르겠습니다. 혹시 이런 경우에는 어떤 것을 찾아봐야하나요??
"인가코드(백엔드에서 발급)" 강의에서 백엔드에서 소셜 로그인을 처리 부분에 질문이 있습니다. 해당 방식의 단점이 JWT 반환 시, redirect 방식을 사용하므로 JWT 토큰 값이 노출될 수 있어 보안상 문제가 될 수 있다고 이해했습니다. 하지만 JWT 토큰 값 자체는 브라우저 로컬 스토리지에 저장하면 어떻게든 유저에게 노출되는 것 아닌가 라는 생각이 듭니다. 이에 대한 강사님의 생각이 궁금합니다.
11강 네이버 로그인까지 완료하고 @ AuthenticationPrincipal을 이용해서 로그인한 정보를 가져오려고 하는데, 구글로 로그인했을 때는 정보가 출력되는데, 네이버로 로그인했을 때는 null로 나와서 구글링하며 찾아보았는데 해결이 안 되어서…. 질문 남깁니다.
스프링부트 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 배포시에는 사용하지 못하나요?