무료
다른 수강생들이 자주 물어보는 질문이 궁금하신가요?
- 미해결스프링부트 시큐리티 & JWT 강의
OAuth 이론
OAuth 이론은 이전강의에서 들으라고 하셨는데 어떤 강의를 말씀하시는 건가요 ?
- 미해결스프링부트 시큐리티 & JWT 강의
Facebook 로그인 버튼만든 후 error(Sorry...) 발생시
인증 및 계정 만들기 > 수정 에서 Email 권한 추가해주어야 오류 안나네요.
- 미해결스프링부트 시큐리티 & JWT 강의
실수로 google auth 시크릿 키를 깃에 업로드 했어요
업로드 하고 구글 메일로 시크릿키가 노출되었다고 메일이 와서 10분 내로 대시보드에 있는 프로젝트를 닫았는데, 30일 뒤에 닫힌다고 알람이 오고, 다시 대시보드를 보니까 사진과 같이 뜨던데 이후에 문제가 발생할까요...?ㅠㅠㅠㅠ
- 해결됨스프링부트 시큐리티 & JWT 강의
@EnableGlobalMethodSecurity(securedEnabled = true)
@EnableGlobalMethodSecurity(securedEnabled = true)만 작성해줘도 PreAuthorize 어노테이션이 잘 작동되는 것 같은데 securedEnabled랑 prePostEnabled 둘 중에 하나만 적어주면 되는 건가요?
- 미해결스프링부트 시큐리티 & JWT 강의
/loginForm?error가 발생하며 로그인이 안 됩니다.
4강을 수강하는 중에 생긴 오류입니다. 회원가입은 정상적으로 작동하는데 로그인을 하려고 하면 아래 사진처럼 오류가 발생합니다.강의를 여러번 돌려보며 코드는 똑같이 따라했고, 콘솔에 찍히는 것을 보면 제가 로그인 하고자 하는 username도 잘 찍힙니다. (회원 정보를 찾는 것까지는 정상적인 거 같습니다)import org.springframework.boot.web.servlet.view.MustacheViewResolver; // 이 import문에서 reactive로 하라고 하셨는데 그걸로 바꾸면 아래의 setCharset, setContentType 등에서 오류가 발생하여 servlet으로 하였습니다. import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ViewResolverRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void configureViewResolvers(ViewResolverRegistry registry) { MustacheViewResolver resolver = new MustacheViewResolver(); resolver.setCharset("UTF-8"); resolver.setContentType("text/html;charset=UTF-8"); resolver.setPrefix("classpath:/templates/"); resolver.setSuffix(".html"); registry.viewResolver(resolver); } }@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean public BCryptPasswordEncoder encodePwd(){ return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable(); http.authorizeRequests() .antMatchers("/user/**").authenticated() .antMatchers("/manager/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_MANAGER')") .antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')") .anyRequest().permitAll() .and() .formLogin() .loginPage("/loginForm") .loginProcessingUrl("/login") .defaultSuccessUrl("/"); }
- 미해결스프링부트 시큐리티 & JWT 강의
스프링에서 설정 값 관리하는 방법 질문드립니다.
강사님 안녕하세요.스프링에서 토큰을 암호화, 복호화하기 위한 secret 키 값을 관리하는 방법을 알고 싶은데, 구글링해도 잘 나오지 않아 질문남깁니다. 스프링 부트에서는 .yml 파일 또는 .property 파일을 통해 설정값을 따로 관리하여 @value로 불러와서 사용하던데, 스프링에서는 어떤 파일을 통해 어떻게 사용할 수 있을까요?
- 미해결스프링부트 시큐리티 & JWT 강의
10강 시큐리티 로그인중 api 라이브 과정
처음에 기타가 아닌 걸로 했다가 다시 작성해봅니다.앱 라이브를 안할경우 페이스북 로그인시 뜨는 화면 라이브를 하려니 뜨는 에러개인정보처리방침 URL:https://www.facebook.com/privacy/policy/?entry_point=facebook_page_footer사용자 데이터 삭제 https://developers.facebook.com/docs/development/create-an-app/app-dashboard/data-deletion-callback 임의 작성 localhost:8080 에서 대충 www.naver.com으로 웹사이트 url 변경 앱 라이브 성공!! 일단 라이브 성공 상태에서 다시http://localhost:8080 로 웹사이트 url 변경 ??? 읭? 분명 비활성화에서 바뀌긴 했는데 페이스북 로그인이 안됨.. (개발자 계정과 다른 페북 아이디일경우) 개발자 페이스북 아이디 동일할경우. 여기까지 후 일단 페북 인증 잠정 중단.확인결과 비즈니스계정을 인증해야 api를 정상적으로 사용가능한데, 비즈니스계정 인증시 사업자 등록증 등 요구하는게 많아 포기.
- 미해결스프링부트 시큐리티 & JWT 강의
스프링 부트 시큐리티 6강-구글 로그인 준비
6강을 시작하면서 구글 클라우드 api 화면에서 새 프로젝트를 만들려고 여러 번 시도를 하였으나 만들어지지 않습니다.왜 그런 걸까요
- 미해결스프링부트 시큐리티 & JWT 강의
안녕하세요 강사님 질문있습니다.
만약 이 네이버 로그인까지 진행하고 로그인을 완료하면 index 페이지로 이동을 하는데,jwt토큰을 생성하려면 어떻게 해야 되나요?Service 에서 생성하고 적용하면 되는 건가요?
- 미해결스프링부트 시큐리티 & JWT 강의
커스텀 필터 적용 안됨
강의를 다 수강하고 깃허브 코드 참조하여 작성했습니다.현재 SecurityConfig 코드는 다음과 같습니다.@Configuration @EnableWebSecurity @RequiredArgsConstructor public class SecurityConfig{ private final UserRepository userRepository; private final CorsConfig corsConfig; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http .csrf(AbstractHttpConfigurer::disable) .sessionManagement((sessionManagement) -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS) ) .formLogin(withDefaults()) .httpBasic(withDefaults()) .apply(new MyCustomDsl()) .and() .authorizeRequests(requests -> requests .requestMatchers("/user/**").authenticated() .requestMatchers("/admin/**").access("hasAuthority('ADMIN')") .anyRequest().permitAll() ) .build(); } public class MyCustomDsl extends AbstractHttpConfigurer<MyCustomDsl, HttpSecurity> { @Override public void configure(HttpSecurity http) throws Exception { AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class); http .addFilter(corsConfig.corsFilter()) .addFilter(new JwtAuthenticationFilter(authenticationManager)) .addFilter(new JwtAuthorizationFilter(authenticationManager, userRepository)); } } } filterchain에서 and()에 오류가 발생합니다.'and()' is deprecated and marked for removal 로 나오는데 and가 deprecated된 거 같지는 않고 커스텀 필터를 적용하는 것에서 뭔가 문제가 있지 않을까 싶습니다만 이틀째 해결을 못하고 있어 문의남깁니다ㅠㅠ
- 미해결스프링부트 시큐리티 & JWT 강의
스프링에서도 JWT 구현가능한가요? (스프링 부트X)
안녕하세요 강사님.스프링 환경에서 작업 진행 중에 JWT를 구현하고자 하는데, 강사님 수업도 마찬가지이며 구글링해봐도 정보들 대부분이 스프링 부트 환경에서 JWT 구현하는 내용들이라 궁금합니다.스프링 부트가 아닌 스프링 환경에서도 JWT 구현이 가능한가요?가능하다면 스프링 부트에서 구현하는 방식을 따라도 될까요?
- 해결됨스프링부트 시큐리티 & JWT 강의
시큐리티 2강 SecurityConfig 설정시 로그아웃 -로그인 납치증상
SecurityConfig 작성후 계속하여 로그아웃 시도시 로그인 화면으로 납치되어 정상적인 학습이 불가능합니다. --> 로그아웃 시도할경우 로그인 string 리턴
- 미해결스프링부트 시큐리티 & JWT 강의
사용자 정보 변경 시 jwt 재발급
안녕하세요. jwt를 사용하다가 jwt의 subject는 email, 그 외에 사용자의 닉네임과 같은 정보를 claims 에 넣었습니다. 그런데 이렇게 사용자의 정보가 변경될 때 특히 email이 변경될 때는 토큰을 재발급 받아줘야 사용 가능한 토큰이 되는데 만약 claims에 없는 비밀번호와 같은 보안과 관련된 정보를 변경했을 때는 새 토큰을 발급 받아도 사용자가 이전에 발급 받은 토큰으로 서비스에 접근할 때 어떻게 불가능한 토큰이라고 알 수 있는지 모르겠습니다. 토큰의 유효성을 검사하는 코드에서 db에서 사용자의 정보 수정일 이전 발행된 토큰이면 유효하지 않은 토큰이라고 하는 편이 나을지 고민입니다. 만약 이렇게 한다면 사용자의 정보를 수정할 때마다 새로운 토큰을 발급해줘야 된다는 점이 신경쓰입니다.Date date = Date.from(user.getModifiedDate().atZone(ZoneId.systemDefault()).toInstant()); // 토큰 발행일이 유저 데이터 수정일 이전이면 유효하지 않은 토큰임 if(claims.getBody().getIssuedAt().before(date)) { throw new CustomException(ErrorCode.VALIDATION_ERROR, "유효하지 않은 토큰입니다."); }매번 새로운 토큰을 발급받게 하는 게 좋은 방법일까요?
- 미해결스프링부트 시큐리티 & JWT 강의
안녕하세요 강사님 핸들러 질문있습니다
현재 제 상황은 이러합니다Oauth2Service에서 검증을 하고여기서 회원 생성을 할 수 있습니다. (현재는 빼놓은 상태)그리고 successhandler까지 구현했습니다.이 상태입니다.근데 저는 로그인을 성공했을 경우,핸들러를 타지 않고 8080:/ 주소로 이동합니다. 이러한 경우에 어떻게 토큰을 발급하고 적용할 수 있는지 모르겠습니다.apply로 정의한 함수때문에 핸들러를 거치지 않는 걸까요?apply로 정의한 함수는 강사님 JWT 강의랑 똑같습니다.
- 미해결스프링부트 시큐리티 & JWT 강의
스프링부트 시큐리티 3강 - 시큐리티 회원가입에서 USER출력
안녕하세요 강사님 객체 전달관련해서 질문이 있습니다. 강의에서 USER 객체를 출력했을 시,아래와 같이 객체 내부값이 출력되었는데,저는 `com.want.project.domain.user.domain.Users@6bfd8b8c`이런식으로 출력이 되네요..혹시 tostring을 오버라이딩하신걸까요??그리고 제 객체를 getter로 찍어보면 내부에 값이 아닌 null이 저장되어 있는데 이유가 무엇일까요?
- 미해결스프링부트 시큐리티 & JWT 강의
안녕하세요 강사님 /login 질문입니다.
제가 수업을 놓쳤는지 모르겠는데...시큐리티가 기본으로 /login을 캐치한다고 들었는데이 기본값을 바꿀 수 있는 방법이 존재하나요?/login으로 Post 요청을 보내면attemptAuthentication 메서드를 실행해서 로그인 하는 걸로 알고있는데/login 값을 제가 원하는 /api/login 처럼 변경할 수 있는지 궁금합니다.
- 미해결스프링부트 시큐리티 & JWT 강의
안녕하세요 강사님 로그인 질문있씁니다!
public class LoginFilter extends UsernamePasswordAuthenticationFilter이 작업을 통해서 login으로 들어오는 것을@Override public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { try { User user = objectMapper.readValue(request.getInputStream(), User.class); UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword()); return authenticationManager.authenticate(authenticationToken); } catch (IOException e) { log.error("{}", e); } return super.attemptAuthentication(request, response); }이 작업을 통해서 로그인 하는 걸로 알고있는데,이러한 경우에 컨트롤러 테스트 코드를 어떻게 작성할 수 있는 건지 궁금합니다. api가 아니라 자꾸 에러가 발생하는데혹시 이러한 경우에 api 테스트 코드 작성 팁좀 가르쳐주실 수 있나요? 이러한 예외가 발생합니다.이유를 모르겠습니다. Postman으로 할 때는 잘 동작합니다 ㅜㅜ그냥 /api/login 이라는 api를 생성해서 테스트해야 될까요?
- 미해결스프링부트 시큐리티 & JWT 강의
권한 인증 403가 뜹니다
https://github.com/bgseong/Security-test public class JwtAuthorizationFilter extends BasicAuthenticationFilter { private TokenService tokenService; public JwtAuthorizationFilter(AuthenticationManager authenticationManager, TokenService tokenService) { super(authenticationManager); this.tokenService = tokenService; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { super.doFilterInternal(request, response, chain); String token = tokenService.resolveToken(request); if(token == null){ chain.doFilter(request, response); return; } if (tokenService.validateToken(token)) { Authentication authentication = tokenService.getAuthentication(token); SecurityContextHolder.getContext().setAuthentication(authentication); System.out.println(SecurityContextHolder.getContext().getAuthentication()); } chain.doFilter(request,response); } }@EnableWebSecurity @Configuration @RequiredArgsConstructor @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig { @Autowired TokenService tokenService; @Autowired CorsConfig corsConfig; @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{ http .csrf().disable() .httpBasic().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .formLogin().disable() .apply(new MyCustomDsl()) .and() .authorizeHttpRequests(authorize -> authorize .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll() // 특정 정적 리소스 허용 .requestMatchers("/api/v1/user/**").hasAnyRole("ADMIN", "MANAGER") .requestMatchers("/api/v1/manager/**").hasRole("ADMIN") .requestMatchers("/api/v1/admin/**").hasRole("ROLE_ADMIN") .anyRequest().permitAll()); return http.build(); } public class MyCustomDsl extends AbstractHttpConfigurer<MyCustomDsl, HttpSecurity> { @Override public void configure(HttpSecurity http) throws Exception { AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class); http .addFilter(corsConfig.corsFilter()) .addFilter(new LoginFilter(authenticationManager,tokenService)) .addFilter(new JwtAuthorizationFilter(authenticationManager,tokenService)); } } }@Component public class TokenService implements InitializingBean { private final UserRepository usersrepository; private final Logger logger = LoggerFactory.getLogger(TokenService.class); private static final String AUTHORITIES_KEY = "auth"; private final String secret; private final long accessTokenValidityInMilliseconds; private final long refreshTokenValidityInMilliseconds; public static final String AUTHORIZATION_HEADER = "Authorization"; public static final String REFRESHTOKEN_HEADER = "RefreshToken"; private Key key; public TokenService( UserRepository usersrepository, @Value("${spring.jwt.secret}") String secret, @Value("${spring.jwt.token-validity-in-seconds}") long tokenValidityInSeconds) { this.usersrepository = usersrepository; this.secret = secret; this.accessTokenValidityInMilliseconds = tokenValidityInSeconds * 500; this.refreshTokenValidityInMilliseconds = tokenValidityInSeconds * 1000 * 336; } @Override public void afterPropertiesSet() { byte[] keyBytes = Decoders.BASE64.decode(secret); this.key = Keys.hmacShaKeyFor(keyBytes); } public String createAccessToken(PrincipalDetails principalDetails) { return createAccessToken(principalDetails.getUser().getEmail(), principalDetails.getAuthorities()); } public String createRefreshToken(PrincipalDetails principalDetails) { return createRefreshToken(principalDetails.getUser().getEmail(), principalDetails.getAuthorities()); } public String createAccessToken(String email, Collection<? extends GrantedAuthority> inputAuthorities) { String authorities = inputAuthorities.stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.joining(",")); long now = (new Date()).getTime(); String accessToken = Jwts.builder() .setSubject(email) .claim(AUTHORITIES_KEY, authorities) .signWith(key, SignatureAlgorithm.HS512) .setExpiration(new Date(now + this.accessTokenValidityInMilliseconds)) .compact(); return accessToken; } public String createRefreshToken(String email, Collection<? extends GrantedAuthority> inputAuthorities) { String authorities = inputAuthorities.stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.joining(",")); long now = (new Date()).getTime(); String Token = Jwts.builder() .setSubject(email) .claim(AUTHORITIES_KEY, authorities) .signWith(key, SignatureAlgorithm.HS512) .setExpiration(new Date(now + this.refreshTokenValidityInMilliseconds)) .compact(); return Token; } public Authentication getAuthentication(String token) { Claims claims = Jwts .parserBuilder() .setSigningKey(key) .build() .parseClaimsJws(token) .getBody(); User user = usersrepository.findByEmail(claims.get("sub",String.class)); Collection<? extends GrantedAuthority> authorities = Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(",")) .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); PrincipalDetails principal = new PrincipalDetails(user); return new UsernamePasswordAuthenticationToken(principal, null, authorities); } public String resolveToken(HttpServletRequest request) { String bearerToken = request.getHeader(AUTHORIZATION_HEADER); if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { return bearerToken.substring(7); } return null; } public boolean validateToken(String token) { try { Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token); return true; } catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) { logger.info("worng JWT sign"); } catch (ExpiredJwtException e) { logger.info("expire JWT"); } catch (UnsupportedJwtException e) { logger.info("No support JWT"); } catch (IllegalArgumentException e) { logger.info("JWT is worng"); } return false; } }이렇게 구성해 놨습니다. 그런데 모든 권한이 적용된 url에 접근을 하면 403 에러가 뜹니다.필터에서 SecurityContextHolder를 출력하면 아래와 같이 출력이 되는 걸 확인했고[Principal=com.securitytest.Securitytest.auth.PrincipalDetails@45095607, Credentials=[PROTECTED], Authenticated=true, Details=null, Granted Authorities=[ROLE_ADMIN]]컨트롤러에서 PrincipalDetails를 호출해보니, null이라서 오류가 난다고 뜹니다. 무엇이 문제일까요ㅠㅠ..
- 미해결스프링부트 시큐리티 & JWT 강의
1강 환경설정을 따라하는데 run하면 에러가 나요
설정 후 run 하면If you want an embedded database (H2, HSQL or Derby), please put it on the classpath. 라는 문구가 뜹니다. 그래서 mysql에 다시 한번 sql문구를 쓰고 다시 처음부터 강의대로 해봤는데도 안되서 구글링해도 모르겠어서요ㅠㅠ
- 미해결스프링부트 시큐리티 & JWT 강의
소셜 로그인 후 JWT 발급
아무리 해도 해결이 안되네요...package com.example.project1.config.oauth2; import com.example.project1.config.auth.PrincipalDetails; import com.example.project1.config.oauth2.provider.GoogleUserInfo; import com.example.project1.config.oauth2.provider.NaverUserInfo; import com.example.project1.config.oauth2.provider.OAuth2UserInfo; import com.example.project1.domain.member.UserType; import com.example.project1.entity.member.MemberEntity; import com.example.project1.repository.member.MemberRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Service; import java.util.Map; @Service @Slf4j @RequiredArgsConstructor public class PrincipalOauth2UserService extends DefaultOAuth2UserService { private final BCryptPasswordEncoder bCryptPasswordEncoder; private final MemberRepository memberRepository; // 구글로부터 받은 userReuest 데이터에 대한 후처리되는 함수 @Override public PrincipalDetails loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { // registrationId로 어떤 OAuth로 로그인 했는지 확인가능 log.info("clientRegistration in PrincipalOauth2UserService : " + userRequest.getClientRegistration() ); log.info("accessToken in PrincipalOauth2UserService : " + userRequest.getAccessToken().getTokenValue() ); OAuth2User oAuth2User = super.loadUser(userRequest); // 구글 로그인 버튼 클릭 →구글 로그인 창 → 로그인 완료 → code 를 리턴(OAuth-Client 라이브러리) → AccessToken 요청 // userRequest 정보 → 회원 프로필 받아야함(loadUser 함수 호출) → 구글로부터 회원 프로필을 받아준다. log.info("getAttributes in PrincipalOauth2UserService : " + oAuth2User.getAttributes()); // 회원가입을 강제로 진행 OAuth2UserInfo oAuth2UserInfo = null; if(userRequest.getClientRegistration().getRegistrationId().equals("google")) { log.info("구글 로그인 요청"); oAuth2UserInfo = new GoogleUserInfo(oAuth2User.getAttributes()); } else if(userRequest.getClientRegistration().getRegistrationId().equals("naver")) { log.info("네이버 로그인 요청"); // 네이버는 response를 json으로 리턴을 해주는데 아래의 코드가 받아오는 코드다. // response={id=5SN-ML41CuX_iAUFH6-KWbuei8kRV9aTHdXOOXgL2K0, email=zxzz8014@naver.com, name=전혜영} // 위의 정보를 NaverUserInfo에 넘기면 oAuth2UserInfo = new NaverUserInfo((Map)oAuth2User.getAttributes().get("response")); } else { log.info("구글과 네이버만 지원합니다."); } // 사용자가 로그인한 소셜 서비스(provider)를 가져옵니다. // 예를 들어, "google" 또는 "naver"와 같은 값을 가질 수 있습니다. String provider = oAuth2UserInfo.getProvider(); // 사용자의 소셜 서비스(provider)에서 발급된 고유한 식별자를 가져옵니다. // 이 값은 해당 소셜 서비스에서 유니크한 사용자를 식별하는 용도로 사용됩니다. String providerId = oAuth2UserInfo.getProviderId(); // 예) google_109742856182916427686 String userName = provider + "_" + providerId; String password = bCryptPasswordEncoder.encode("get"); // 사용자의 이메일 주소를 가져옵니다. 소셜 서비스에서 제공하는 이메일 정보를 사용합니다. String email = oAuth2UserInfo.getEmail(); // 사용자의 권한 정보를 설정합니다. UserType. // 여기서는 소셜로그인으로 가입하면 무조건 User로 권한을 주는 방식으로 했습니다. UserType role = UserType.USER; // 이메일 주소를 사용하여 이미 해당 이메일로 가입된 사용자가 있는지 데이터베이스에서 조회합니다. MemberEntity member = memberRepository.findByUserEmail(email); if(member == null) { log.info("OAuth 로그인이 최초입니다."); log.info("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓"); log.info("OAuth 자동 회원가입을 진행합니다."); member = MemberEntity.builder() .userName(userName) .userPw(password) .userEmail(email) .userType(role) .provider(provider) .providerId(providerId) .build(); log.info("userEmail in PrincipalOauth2UserService : " + member.getUserEmail()); log.info("userName in PrincipalOauth2UserService : " + member.getUserName()); log.info("userPw in PrincipalOauth2UserService : " + member.getUserPw()); log.info("userType in PrincipalOauth2UserService : " + member.getUserType()); log.info("provider in PrincipalOauth2UserService : " + member.getProvider()); log.info("providerId in PrincipalOauth2UserService : " + member.getProviderId()); memberRepository.save(member); } else { log.info("로그인을 이미 한적이 있습니다. 당신은 자동회원가입이 되어 있습니다."); MemberEntity findUser = memberRepository.findByUserEmail(email); log.info("findUser in PrincipalOauth2UserService : " + findUser); } OAuth2User oAuth2User1 = super.loadUser(userRequest); log.info("getAttributes in PrincipalOauth2UserService : " + oAuth2User1.getAttributes()); // attributes가 있는 생성자를 사용하여 PrincipalDetails 객체 생성 // 소셜 로그인인 경우에는 attributes도 함께 가지고 있는 PrincipalDetails 객체를 생성하게 됩니다. PrincipalDetails principalDetails = new PrincipalDetails(member, oAuth2User.getAttributes()); log.info("principalDetails in PrincipalOauth2UserService : " + principalDetails); return principalDetails; } }package com.example.project1.config.oauth2.provider; public interface OAuth2UserInfo { String getProviderId(); String getProvider(); String getEmail(); String getName(); }package com.example.project1.config.oauth2.provider; import java.util.Map; public class GoogleUserInfo implements OAuth2UserInfo{ // getAttributes()를 받음 private Map<String, Object> attributes; public GoogleUserInfo(Map<String, Object> attributes) { this.attributes = attributes; } @Override public String getProviderId() { return (String) attributes.get("sub"); } @Override public String getProvider() { return "google"; } @Override public String getEmail() { return (String) attributes.get("email"); } @Override public String getName() { return (String) attributes.get("name"); } }package com.example.project1.config.oauth2.provider; import java.util.Map; public class NaverUserInfo implements OAuth2UserInfo{ // oauth2User.getAttributes()를 받음 private Map<String,Object> attributes; // PrincipalOauth2UserService에서 new NaverUserInfo((Map)oAuth2User.getAttributes().get("response"))로 // Oauth2 네이버 로그인 정보를 받아온다. // → {id=5SN-ML41CuX_iAUFH6-KWbuei8kRV9aTHdXOOXgL2K0, email=zxzz8014@naver.com, name=전혜영} public NaverUserInfo(Map<String, Object> attributes) { this.attributes = attributes; } @Override public String getProviderId() { return (String)attributes.get("id"); } @Override public String getProvider() { return "naver"; } @Override public String getEmail() { return (String)attributes.get("email"); } @Override public String getName() { return (String)attributes.get("name"); } }package com.example.project1.config.auth; import com.example.project1.entity.member.MemberEntity; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.oauth2.core.user.OAuth2User; import java.util.ArrayList; import java.util.Collection; import java.util.Map; @Setter @Getter @ToString @NoArgsConstructor @Slf4j public class PrincipalDetails implements UserDetails, OAuth2User { // 일반 로그인 정보를 저장하기 위한 필드 private MemberEntity member; // OAuth2 로그인 정보를 저장하기 위한 필드 // attributes는 Spring Security에서 OAuth2 인증을 수행한 후에 사용자에 대한 추가 정보를 저장하는 데 사용되는 맵(Map)입니다. // OAuth2 인증은 사용자 인증 후에 액세스 토큰(Access Token)을 발급받게 되는데, // 이 토큰을 사용하여 OAuth2 서비스(provider)로부터 사용자의 프로필 정보를 요청할 수 있습니다. // 예를 들어, 소셜 로그인을 사용한 경우에는 attributes에는 사용자의 소셜 서비스(provider)에서 제공하는 프로필 정보가 담겨 있습니다. // 소셜 로그인 서비스(provider)마다 제공하는 프로필 정보가 다를 수 있습니다. // 일반적으로 attributes에는 사용자의 아이디(ID), 이름, 이메일 주소, 프로필 사진 URL 등의 정보가 포함됩니다. /* * 구글의 경우 * { "sub": "100882758450498962866", // 구글에서 발급하는 고유 사용자 ID "name": "John Doe", // 사용자 이름 "given_name": "John", // 이름(이름 부분) "family_name": "Doe", // 성(성(성) 부분) "picture": "https://lh3.googleusercontent.com/a/AAcHTtdzQomNwZCruCcM0Eurcf8hAgBHcgwvbXEBQdw3olPkSg=s96-c", // 프로필 사진 URL "email": "johndoe@example.com", // 이메일 주소 "email_verified": true, // 이메일 주소 인증 여부 "locale": "en" // 지역 설정 } * */ private Map<String, Object> attributes; // 일반 로그인 public PrincipalDetails(MemberEntity member) { this.member = member; } // OAuth2 로그인 public PrincipalDetails(MemberEntity member, Map<String, Object> attributes) { this.member = member; this.attributes = attributes; } // 해당 유저의 권한을 리턴하는 곳 @Override public Collection<? extends GrantedAuthority> getAuthorities() { Collection<GrantedAuthority> collection = new ArrayList<>(); collection.add(new SimpleGrantedAuthority("ROLE_" + member.getUserType().toString())); log.info("collection : " + collection); return collection; } // 사용자 패스워드를 반환 @Override public String getPassword() { log.info("password : "+ member.getUserPw()); return member.getUserPw(); } // 사용자 이름 반환 @Override public String getUsername() { log.info("id : " + member.getUserEmail()); return member.getUserEmail(); } // 계정 만료 여부 반환 @Override public boolean isAccountNonExpired() { // 만료되었는지 확인하는 로직 // true = 만료되지 않음 return true; } // 계정 잠금 여부 반환 @Override public boolean isAccountNonLocked() { // true = 잠금되지 않음 return true; } // 패스워드의 만료 여부 반환 @Override public boolean isCredentialsNonExpired() { // 패스워드가 만료되었는지 확인하는 로직 // true = 만료되지 않음 return true; } // 계정 사용 가능 여부 반환 @Override public boolean isEnabled() { // 계정이 사용 가능한지 확인하는 로직 // true = 사용 가능 return true; } @Override public Map<String, Object> getAttributes() { log.info("attributes : " + attributes); return attributes; } @Override // OAuth2 인증에서는 사용되지 않는 메서드이므로 null 반환 public String getName() { return null; } } // Oauth2 google로 JWT 발급 @GetMapping("/success-oauth") public ResponseEntity<?> createTokenForGoogle(@AuthenticationPrincipal OAuth2User oAuth2User , @RequestBody MemberDTO member) { Object email = oAuth2User.getAttribute("email"); log.info("oAuth2User : " + email); if(oAuth2User == null) { log.info("받아올 정보가 없습니다 ㅠㅠ"); return ResponseEntity.status(HttpStatus.NOT_FOUND).body("정보가 없어...."); } else { // OAuth2User에서 필요한 정보를 추출하여 UserDetails 객체를 생성합니다. ResponseEntity<TokenDTO> token = memberService.createToken((String) email, member); log.info("token : " + token); return ResponseEntity.ok().body(token); } } // 소셜 로그인 성공시 jwt 반환 // OAuth2User에서 필요한 정보를 추출하여 UserDetails 객체를 생성하는 메서드 public ResponseEntity<TokenDTO> createToken(String userEmail, MemberDTO member) { MemberEntity findUser = memberRepository.findByUserEmail(userEmail); log.info("findUser in MemberService : " + findUser); findUser = MemberEntity.builder() // id를 식별해서 수정 // 이거 없으면 새로 저장하기 됨 // findUser꺼를 쓰면 db에 입력된거를 사용하기 때문에 // 클라이언트에서 userEmail을 전달하더라도 서버에서 기존 값으로 업데이트가 이루어질 것입니다. // 이렇게 하면 userEmail을 수정하지 못하게 할 수 있습니다. .userId(findUser.getUserId()) .userEmail(findUser.getUserEmail()) .userPw(passwordEncoder.encode(findUser.getUserPw())) .userName(findUser.getUserName()) .nickName(member.getNickName()) .userType(findUser.getUserType()) .address(AddressEntity.builder() .userAddr(member.getAddressDTO().getUserAddr()) .userAddrDetail(member.getAddressDTO().getUserAddrDetail()) .userAddrEtc(member.getAddressDTO().getUserAddrEtc()) .build()) .build(); memberRepository.save(findUser); // 권한 정보 추출 List<GrantedAuthority> authorities = getAuthoritiesForUser(findUser); // UserDetails 객체 생성 (사용자의 아이디 정보를 활용) // 첫 번째 인자 : username 사용자 아이디 // 두 번째 인자 : 사용자의 비밀번호 // 세 번째 인자 : 사용자의 권한 정보를 담은 컬렉션 UserDetails userDetails = new User(userEmail, null, authorities); log.info("userDetails in MemberService : " + userDetails); TokenDTO token = jwtProvider.createToken2(userDetails); log.info("token in MemberService : " + token); return ResponseEntity.ok().body(token); } // 소셜 로그인 성공시 JWT 발급 public TokenDTO createToken2(UserDetails userDetails) { long now = (new Date()).getTime(); Date now2 = new Date(); // userDetails.getAuthorities()는 사용자의 권한(authorities) 정보를 가져오는 메서드입니다. // claims.put("roles", userDetails.getAuthorities()) 코드는 사용자의 권한 정보를 클레임에 추가하는 것입니다. // 클레임에는 "roles"라는 키로 사용자의 권한 정보가 저장되며, 해당 권한 정보는 JWT의 페이로드 부분에 포함됩니다. Claims claims = Jwts.claims().setSubject(userDetails.getUsername()); claims.put(AUTHORITIES_KEY, userDetails.getAuthorities()); log.info("claims : " + claims); // access token Date accessTokenExpire = new Date(now + this.accessTokenTime); String accessToken = Jwts.builder() .setSubject(userDetails.getUsername()) .setClaims(claims) .setIssuedAt(now2) .setExpiration(accessTokenExpire) .signWith(key,SignatureAlgorithm.HS256) .compact(); // RefreshToken 생성 Date refreshTokenExpire = new Date(now + this.refreshTokenTime); String refreshToken = Jwts.builder() .setIssuedAt(now2) .setClaims(claims) .setExpiration(refreshTokenExpire) .signWith(key, SignatureAlgorithm.HS256) .compact(); TokenDTO tokenDTO = TokenDTO.builder() .grantType("Bearer ") .accessToken(accessToken) .refreshToken(refreshToken) .userEmail(userDetails.getUsername()) .build(); log.info("tokenDTO in JwtProvider : " + tokenDTO); return tokenDTO; } http // oauth2Login() 메서드는 OAuth 2.0 프로토콜을 사용하여 소셜 로그인을 처리하는 기능을 제공합니다. .oauth2Login() // OAuth2 로그인 성공 이후 사용자 정보를 가져올 때 설정 담당 .userInfoEndpoint() // OAuth2 로그인 성공 시, 후작업을 진행할 서비스 .userService(principalOauth2UserService) .and() .defaultSuccessUrl("/success-oauth");소셜 로그인 후 쇼핑몰 프로젝트라 주소가 필 수라서 JSON을 추가로 받아서 주소를 DB에 저장 후 JWT를 반환해주는 로직을 구성하려고 합니다. 소셜 로그인을 했을 때 PrincipalOauth2UserService여기서 모든 값이 제대로 들어갔고 PrincipalDetails principalDetails = new PrincipalDetails(member, oAuth2User.getAttributes()); log.info("principalDetails in PrincipalOauth2UserService : " + principalDetails); return principalDetails;로그를 찍어본 결과 제대로 값이 로그에 찍혔습니다. 이거를 principalDetails에 보내줬으니 principalDetails 클래스에서도 제대로 받아졌는지 확인해봤습니다. Oaut2User를 상속받아서 오버라이드 한 @Override public Map<String, Object> getAttributes() { log.info("attributes : " + attributes); return attributes; }여기서도 로그에 제대로 값이 나왔습니다.여기서부터가 문제인데 컨트롤러에서 @AuthenticationPrincipal OAuth2User oAuth2User로 소셜 로그인한 정보를 받아와서 토큰을 만들려고 하는데 null이 뜹니다. 별방법을 다했는데 다른 부분은 다 해결을 했는데 이부분이 해결이 안되네요 ㅠㅠ