권한 인증 에러 및 loadUser 메소드 호출이안됨

문의 시, 사용하시는 개발환경과 디벨로퍼스 앱ID를 알려주세요.


사용하는 개발환경은 스프링입니다. 앱 ID는 1117371입니다.

loadUser 메소드가 호출이 안돼서
핸들러까지 가지 못해서 오류가 발생하는 것으로 추측이 됩니다.
하지만 loadUser 메소드가 호출이 안되는 이유를 모르겠습니다.

2024-08-08 23:15:39 - CustomOauth2UserService 빈 등록 완료
2024-08-08 23:15:39 - Kakao Client ID: --- ( 정확함 ! )
2024-08-08 23:15:39 - Kakao Redirect URI: http://localhost:8080/auth/kakao/login/callback
2024-08-08 23:15:39 - Kakao Client Secret: --- ( 정확함 ! )
2024-08-08 23:15:40 - spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2024-08-08 23:15:40 - Global AuthenticationManager configured with AuthenticationProvider bean with name authenticationProvider
2024-08-08 23:15:40 - Global AuthenticationManager configured with an AuthenticationProvider bean. UserDetailsService beans will not be used for username/password login. Consider removing the AuthenticationProvider bean. Alternatively, consider using the UserDetailsService in a manually instantiated DaoAuthenticationProvider.
2024-08-08 23:15:40 - OAuth2 UserService 설정됨
2024-08-08 23:09:52 - 카카오 로그인 콜백 호출됨, code: Y7q2BjArT2XztyH8LGv7UEW-WixVunHIJsRHSRhlwYuKTNrpYEwCwAAAAAQKKiURAAABkTJTGPop9hBbJybEWQ, state: 3s0asdt0
2024-08-08 23:09:52 - 카카오 토큰 요청 응답: <200 OK OK,{"access_token":"TYTJx6NkJSuHA-0PU0Vl_D59gWDVjj13AAAAAQo9cusAAAGRMlMaMUPPWzORmYVE","token_type":"bearer","refresh_token":"lF18mHJgYpNMFCs5JMQQOBbhZNHdBBwfAAAAAgo9cusAAAGRMlMaLkPPWzORmYVE","id_token":"eyJraWQiOiI5ZjI1MmRhZGQ1ZjIzM2Y5M2QyZmE1MjhkMTJmZWEiLCJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiJlMzQzZWY5ZjY5NWJkMjZkOThhZTU2ZDBmYTI0ZTk2YSIsInN1YiI6IjM2NTYyODQ3OTQiLCJhdXRoX3RpbWUiOjE3MjMxMjYxOTIsImlzcyI6Imh0dHBzOi8va2F1dGgua2FrYW8uY29tIiwibmlja25hbWUiOiLsnbTtmITsmrAiLCJleHAiOjE3MjMxNDc3OTIsImlhdCI6MTcyMzEyNjE5MiwicGljdHVyZSI6Imh0dHBzOi8vdDEua2FrYW9jZG4ubmV0L2FjY291bnRfaW1hZ2VzL2RlZmF1bHRfcHJvZmlsZS5qcGVnLnR3Zy50aHVtYi5SMTEweDExMCJ9.lg3iAVqFfZK55RlL1cACaEXDGuOHBO-sgQgvWuBCjo6GIaGmrhZzC94qf6Ac70ZaQ21kyE2F_WVQq9bcF1JzgKKDet6jVwYm6hbMVSaZ30YGGf3Yw52XK1pnuimXg2cioLkueBPko6CUiggBcQSuRhlL5IUZX-AQmf22pXsUzvmC04jjkvl2c-5_7t-ZUNL2C26E2Yt7gI1ZmWuvCs--ZJSQEXNFkx55OVKqrSTZFMRDzn7KkzoDjHU_qld9DWeN48ZU3v4jPtI4LSV1Q3mQn-Vgx1L72vPL-sZHk819ocnCuPJcZ4__vGwXpJPUvYYcEp9mq4pN4DH5jk--p50Y5w","expires_in":21599,"scope":"profile_image openid profile_nickname","refresh_token_expires_in":5183999},[Date:"Thu, 08 Aug 2024 14:09:52 GMT", Content-Type:"application/json;charset=utf-8", Transfer-Encoding:"chunked", Connection:"keep-alive", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-XSS-Protection:"1; mode=block", X-Frame-Options:"DENY", X-Content-Type-Options:"nosniff", Kakao:"Talk", Access-Control-Allow-Origin:"*", Access-Control-Allow-Methods:"GET, POST, OPTIONS", Access-Control-Allow-Headers:"Authorization, KA, Origin, X-Requested-With, Content-Type, Accept"]>
2024-08-08 23:09:52 - 카카오 엑세스 토큰 요청 성공
2024-08-08 23:09:52 - 카카오 사용자 정보 요청 응답: <200 OK OK,{id=3656284794, connected_at=2024-08-08T09:12:59Z, properties={nickname=이현우, profile_image=http://t1.kakaocdn.net/account_images/default_profile.jpeg.twg.thumb.R640x640, thumbnail_image=http://t1.kakaocdn.net/account_images/default_profile.jpeg.twg.thumb.R110x110}, kakao_account={profile_nickname_needs_agreement=false, profile_image_needs_agreement=false, profile={nickname=이현우, thumbnail_image_url=http://t1.kakaocdn.net/account_images/default_profile.jpeg.twg.thumb.R110x110, profile_image_url=http://t1.kakaocdn.net/account_images/default_profile.jpeg.twg.thumb.R640x640, is_default_image=true, is_default_nickname=false}}},[Date:"Thu, 08 Aug 2024 14:09:52 GMT", Server:"Apache", Access-Control-Allow-Origin:"*", Access-Control-Allow-Methods:"GET, POST, PUT, DELETE, OPTIONS", Access-Control-Allow-Headers:"Content-Type,X-Requested-With,Accept,Authorization,Origin,KA,Cache-Control,Pragma", X-Request-ID:"74a5e752-54bc-4c55-ba35-9adbfbd894d7", Quota-Type:"INC_AND_CHECK", Content-Type:"application/json;charset=UTF-8", Content-Length:"673", Keep-Alive:"timeout=10, max=500", Connection:"Keep-Alive"]>
2024-08-08 23:09:52 - 카카오 로그인 성공, 사용자 정보: {id=3656284794, connected_at=2024-08-08T09:12:59Z, properties={개인정보 대체}, kakao_account={profile_nickname_needs_agreement=false, profile_image_needs_agreement=false, profile={개인정보 대체}}}
2024-08-08 23:09:53 - Creation of SecureRandom instance for session ID generation using [SHA1PRNG] took [148] milliseconds.
2024-08-08 23:09:53 - SecurityContextHolder 설정 완료, 인증 정보: UsernamePasswordAuthenticationToken [Principal={id=3656284794, connected_at=2024-08-08T09:12:59Z, properties={정보 받아옴. 개인정보라 대체}, kakao_account={profile_nickname_needs_agreement=false, profile_image_needs_agreement=false, profile={정보 받아옴]]
2024-08-08 23:09:53 - Securing GET /home_user
2024-08-08 23:09:53 - Set SecurityContextHolder to anonymous SecurityContext
2024-08-08 23:09:53 - Saved request http://localhost:8080/home_user?continue to session
2024-08-08 23:09:53 - 권한 인증 에러 error: Full authentication is required to access this resource

시큐리티설정 다 잘 한거 같은데 어디가 문제인지 모르겠습니다.
살려주세요…

http
                .oauth2Login(oauth -> oauth
                        .loginPage("/home/login")
                        .userInfoEndpoint(userInfo -> {
                            log.info("OAuth2 UserService 설정됨");
                            userInfo.userService(customOauth2UserService);
                        })
                        .successHandler((request, response, authentication) -> {
                            log.info("OAuth2 로그인 성공: " + authentication.getName());
                            SecurityContextHolder.getContext().setAuthentication(authentication);
                            new SimpleUrlAuthenticationSuccessHandler("/home_user").onAuthenticationSuccess(request, response, authentication);
                        })
                        .failureHandler((request, response, exception) -> {
                            log.error("OAuth2 로그인 실패: " + exception.getMessage());
                            response.sendRedirect("/home/login?error=true");
                        })
        );

        http
                .exceptionHandling(exception -> {
                    log.info("Configuring exceptionHandling");
                    exception.authenticationEntryPoint((request, response, authException) -> {
                        **log.error("권한 인증 에러 error: {}", authException.getMessage());**
                        response.sendRedirect("/home/login?error=true");
                    });
                });
@Slf4j
@Controller
public class NaverCallbackHandler {

    @Autowired
    NaverApi naverApi;

    private static final String FIXED_STATE = "3s0asdt0";

    @GetMapping("/auth/naver/login/callback")
    public String handleNaverCallback(@RequestParam("code") String code,
                                      @RequestParam("state") String state,
                                      HttpServletRequest request) {
        log.info("네이버 로그인 콜백 호출됨, code: {}, state: {}", code, state);

        if (!FIXED_STATE.equals(state)) {
            log.error("잘못된 state 값입니다.");
            return "redirect:/home/login?error=true";
        }

        String accessToken = getAccessToken(code, state);

        if (accessToken == null) {
            log.error("액세스 토큰을 가져오지 못했습니다.");
            return "redirect:/home/login?error=true";
        }
        // 이후 액세스 토큰을 사용하여 사용자 정보를 받아오는 로직을 추가합니다.

        Map<String, Object> userInfo = getUserInfo(accessToken);

        if (userInfo == null) {
            log.error("사용자 정보를 가져오지 못했습니다.");
            return "redirect:/home/login?error=true";
        }

        log.info("로그인 성공, 사용자 정보: {}", userInfo);

        // 세션에 사용자 정보 저장
        request.getSession().setAttribute("user", userInfo);

        Authentication authentication = new UsernamePasswordAuthenticationToken(userInfo, null,
                List.of(new SimpleGrantedAuthority("ROLE_USER")));
        SecurityContextHolder.getContext().setAuthentication(authentication);


        log.info("SecurityContextHolder 설정 완료, 인증 정보: {}", SecurityContextHolder.getContext().getAuthentication());


        return "redirect:/home_user";
    }

    private String getAccessToken(String code, String state) {
        RestTemplate restTemplate = new RestTemplate();
        String tokenUrl = "https://nid.naver.com/oauth2.0/token";
        UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromHttpUrl(tokenUrl)
                .queryParam("grant_type", "authorization_code")
                .queryParam("client_id", naverApi.getNaverClientId())
                .queryParam("client_secret", naverApi.getNaverClientSecret())
                .queryParam("code", code)
                .queryParam("state", state);

        ResponseEntity<Map<String, Object>> response = restTemplate.exchange(
                uriBuilder.toUriString(),
                HttpMethod.POST,
                null,
                new ParameterizedTypeReference<>() {});

        log.info("토큰 요청 응답: {}", response);


        if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) {
            Map<String, Object> body = response.getBody();
            log.info("토큰 응답 바디: {}", body);
            return (String) body.get("access_token");
        } else {
            log.error("액세스 토큰 요청 실패: {}, 응답 바디: {}", response.getStatusCode(), response.getBody());
            return null;
        }
    }

    private Map<String, Object> getUserInfo(String accessToken) {
        RestTemplate restTemplate = new RestTemplate();
        String userInfoUrl = "https://openapi.naver.com/v1/nid/me";

        HttpHeaders headers = new HttpHeaders();
        headers.setBearerAuth(accessToken);

        HttpEntity<String> entity = new HttpEntity<>("", headers);
        ResponseEntity<Map<String, Object>> response = restTemplate.exchange(
                userInfoUrl,
                HttpMethod.GET,
                entity,
                new ParameterizedTypeReference<>() {});

        log.info("사용자 정보 요청 응답: {}", response);

        if (response.getStatusCode() == HttpStatus.OK && response.getBody() != null) {
            return (Map<String, Object>) response.getBody().get("response");
        } else {
            log.error("사용자 정보 요청 실패: {}", response.getStatusCode());
            return null;
        }
    }
}
@Service
@Slf4j
@RequiredArgsConstructor
public class CustomOauth2UserService extends DefaultOAuth2UserService {

    private final UserRepository userRepository;

    @PostConstruct
    public void init() {
        log.info("CustomOauth2UserService 빈 등록 완료");
    }

    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        log.info("loadUser 메서드 호출됨");
        OAuth2User oAuth2User = super.loadUser(userRequest);

        log.info("userRequest.getAttributes : {}", super.loadUser(userRequest).getAttributes());


        String provider = userRequest.getClientRegistration().getClientName();

        OAuth2UserInfo oAuth2UserInfo = null;

        if(provider.equals("Naver")){
            log.info("네이버 로그인");
            oAuth2UserInfo = new NaverUserDetails(oAuth2User.getAttributes());
        } else if(provider.equals("Kakao")){
            log.info("카카오 로그인");
            oAuth2UserInfo = new KaKaoUserDetails(oAuth2User.getAttributes());
        }

        String providerId = oAuth2UserInfo.getProviderId();
        String userId = provider + "_" + providerId;
        String name = oAuth2UserInfo.getName();
        String nickname = oAuth2UserInfo.getNickname();
        String role = "ROLE_USER";

        Optional<User> userOptional = Optional.ofNullable(userRepository.findByUserId(userId));
        User user;

        if (userOptional.isEmpty()) {
            user = new User();
            user.setUserId(userId);
            user.setName(name);
            user.setPassword(null);
            user.setRole(role);
            user.setNickname(nickname);
            user.setProvider(provider);
            userRepository.save(user);
            log.info("새로운 사용자 저장: {}", userId);
        } else {
            user = userOptional.get();
            log.info("기존 사용자 로그인: {}", userId);
        }

        return new CustomUserDetail(user, oAuth2User.getAttributes());
    }
}

애플리케이션 설정은 잘 했습니다. 여러번 검토 했습니다!!

카카오측에는 별다른 에러가 없네요.

개발하시는 시스템에서 발생하는 에러라 기재하신 내용으로 확인하기 어렵습니다.

아래 내용 참고하셔서 다른 로직과 분리하고 독립적으로 확인해보시면 좋을 것 같습니다.

Spring에서 카카오 로그인 사용하기 - Spring Security 5, OAuth 2 - deprecated 대응 2023년 6월 27일

1개의 좋아요