Challenge, and Growth ! Introduce
                                                                                                             

Welcome to my blog 🙌🏻

Total

Today

Yesterday













Tutorial

[SpringBoot] REST API 카카오 로그인 개발하는 법 - ✅ 글 하나로 끝내기 (코드포함, Spring Security JWT 로그인)

뽀시라운 2024. 11. 7. 10:50
반응형
SMALL

[목차여기]

 

SpringBoot를 이용하여 카카오 로그인을 구현해 보겠습니다.





 

카카오 로그인에 대해서 설명되어 있는 공식 문서입니다. 공식 문서에 설명이 정말 잘 되어있어서, 꼭 읽어보시길 추천드립니다. 😀

 

Kakao Developers

카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.

developers.kakao.com

 

 

 

 

 

🎯 카카오 로그인이 실행되는 순서 

  1. 프론트가 카카오로부터 인가코드를 발급받는다.
  2. 프론트가 백에게 인가코드를 전달한다.
  3. 은 전달받은 인가코드를 이용하여 카카오로부터 토큰을 발급받는다.
  4. 은 토큰을 이용하여 카카오로부터 사용자 정보를 얻는다.
  5. 은 사용자 정보를 저장하고, jwt 토큰을 프론트에게 전달한다.

 

 

 

5번은 여러분의 서비스에 맞춰서 하지 않아도 됩니다. 이 글에서는 끝에서 함께 설명하도록 하겠습니다.

 

프론트에서 인가코드를 발급받는 방법이 일반적이긴 하지만,

로그인 구현이 잘 되었는지 확인하기 위해서 백에서도 인가코드를 발급받는 것을 구현하도록 하겠습니다.

 

 

 

 

 

0. 앱 등록하기

 

카카오에게 내가 만들고자 하는 앱을 등록합니다. 

 

🔗 등록하러 가기

 

 

 

 

[앱 키]에서 생성된 REST API 키를 확인할 수 있습니다.

카카오에 호출할 때 필요한 키이므로 저는 IntelliJ 환경변수에 미리 넣어두었습니다.

 

🔗 IntelliJ 환경변수 추가하는 법

 

 

 

[카카오 로그인]에서 로그인을 활성화합니다.

 

 

 

[카카오 로그인]에서 Redirect URI를 등록합니다.

Redirect URI는 최대 10개를 등록할 수 있습니다. 언제든 수정할 수 있으니 아래의 주소를 하나 등록해 주세요.

http://localhost:8080/api/kakao/callback

 

 

 

[카카오 로그인] > [동의항목]에서 개인정보 동의항목 심사 신청 버튼을 클릭합니다.

 

 

 

그러면 비즈 앱 전환이라는 버튼이 보이고 이것을 클릭합니다.

 

 

 

따로 사업자가 없기 때문에, 개인 개발자 비즈 앱 전환 버튼을 클릭합니다.

 

 

 

전환이 완료되면 다시 [카카오 로그인] > [동의항목]에서 내가 필요한 개인 정보를 선택합니다.

저는 이메일만 필요하기 때문에 이메일을 필수 동의 상태로 설정했습니다.

 

 

 

1. 인가 코드 받기

인가코드를 발급받는 코드는 매우 간단합니다. KakaoLoginController.java 에 코드를 작성해 주세요.

 

KakaoLoginController.java

@RestController
@RequiredArgsConstructor
@RequestMapping("api/kakao")
public class KakaoLoginController {

    @Operation(summary = "인가코드 발급 API")
    @GetMapping("/callback")
    public ApiResponse<?> callback(@RequestParam("code") String code){
        return ApiResponse.onSuccess(Status.KAKAO_CODE_SUCCESS, code);
    }
    
    // Swagger와 ApiResponse를 사용하지 않는다면 아래 코드로 작성해주세요.
    // 개인적으로 Swagger와 ApiResponse를 사용하는 것을 추천드립니다.
    @GetMapping("/callback")
    public String callback(@RequestParam("code") String code){
        return code;
    }

}

 

❗️확인하기❗️

  • Swagger를 사용하지 않으신다면 @Operation 어노테이션 사용이 안되므로 삭제해 주세요.
  • ApiResponse는 API 응답 통일성을 위해 제가 만들어 놓은 클래스입니다. 이 글에서는 ApiResponse를 사용해서 설명하겠습니다.


저는 컨트롤러 코드를 작성할 때 응답 통일을 위해서 ApiResponse를 만들어서 공통으로 사용하고 있습니다. 그리고 프론트와의 협업을 위해서 Swagger를 꼭 사용하는데요, 정말 간단하니! 두 가지를 적용하는 것을 추천드립니다.

 

✅ Swagger : 🔗 Swagger v3 적용하는 법

✅ ApiResponse : (아래 코드 확인)

 

ApiResponse.java

@Getter
@AllArgsConstructor
@JsonPropertyOrder({"code", "result", "message", "data"})
public class ApiResponse<T> {
    private final String code;
    private final String result;
    private final String message;
    @JsonInclude(JsonInclude.Include.NON_NULL)
    private T data;

    // 성공한 경우 응답 생성
    public static <T> ApiResponse<T> onSuccess(Status status, T data){
        return new ApiResponse<>(status.getCode(), status.getResult(), status.getMessage(), data);
    }

    // 실패한 경우 응답 생성
    public static <T> ApiResponse<T> onFailure(Status status){
        return new ApiResponse<>(status.getCode(), status.getResult(), status.getMessage(), null);
    }

    // 실패한 경우 응답 생성
    public static <T> ApiResponse<T> onFailure(Status status, T data){
        return new ApiResponse<>(status.getCode(), status.getResult(), status.getMessage(), data);
    }
}

 

 

Status.java

@Getter
@RequiredArgsConstructor(access = PRIVATE)
public enum Status {

    //예시
    TEMP_SUCCESS("200", "SUCCESS", "임시 API 접근에 성공했습니다."),

    KAKAO_CODE_SUCCESS("200", "SUCCESS", "카카오 인가코드를 발급받았습니다."),
    KAKAO_LOGIN_SUCCESS("200", "SUCCESS", "카카오 로그인을 성공했습니다.");
    private final String code;
    private final String result;
    private final String message;
}

 

 

🏁 인가코드 발급 확인하기

 

아래의 주소로 접속하여 확인합니다. 중괄호{ } 안에 해당하는 값을 넣어주셔야 합니다.

https://kauth.kakao.com/oauth/authorize?client_id={이곳에 REST API 키를 넣어주세요}&redirect_uri={이곳에 redirect_uri를 넣어주세요}&response_type=code

 

 

해당 화면이 보이면 성공입니다. 동의 버튼을 누르면 인가코드를 확인할 수 있습니다.

 

프론트와 백이 연동했다고 가정했을 때, 프론트가 백에게 이 인가코드를 넘겨준다고 생각하시면 됩니다.

지금 저희는 직접 인가코드 발급받는 것을 구현한 것입니다.

 

 

2. 토큰 발급받기

토큰을 발급받기 위해서 순서대로 코드를 작성해 주세요.


KakaoLoginDto.java

public class KakaoLoginDto {
    @Getter
    @AllArgsConstructor
    @NoArgsConstructor
    public static class KakaoTokenResponseDto{
        private String access_token;
        private String token_type;
        private String refresh_token;
        private int expires_in;
        private String scope;
        private int refresh_token_expires_in;
    }

    @Getter
    @AllArgsConstructor
    @NoArgsConstructor
    public static class KakaoUserInfoResponseDto{
        private Long id;
        private String connected_at;
        private KakaoAccount kakao_account;
        private Properties properties;
        @Getter
        @AllArgsConstructor
        @NoArgsConstructor
        public static class KakaoAccount{
            private Boolean has_email;
            private Boolean email_needs_agreement;
            private Boolean is_email_valid;
            private Boolean is_email_verified;
            private String email;
        }

    }
}

 

KakaoTokenResposneDto는 토큰을 발급받을 때 사용하고, KakaoUserInfoResponseDto는 사용자 정보를 읽을 때 사용합니다.

미리 작성해 두겠습니다.

 

❗️ 주의 ❗️ 

저는 로그인 시 이메일 값만 필요해서 이메일 값만 추가했습니다. 이메일 값 외에 다른 값을 로그인 동의항목에 추가했다면, KakaoUserInfoResponseDto에도 추가해주셔야 합니다. 속성 이름은 공식문서를 참고하세요.

 

 

 

KakaoLoginService.java

public interface KakaoLoginService {

    String getKakaoToken(String code);
    KakaoLoginDto.KakaoUserInfoDto getKakaoUserInfo(String token);
    
}

 

getKakaoToken()과 getKakaoUserInfo() 함수를 interface 클래스에서 선언합니다.

 

 

 

KakaoLoginServiceImpl.java

@Service
@RequiredArgsConstructor
@Transactional
public class KakaoLoginServiceImpl implements KakaoLoginService{

    @Value("${security.kakao.client_id:default_client_id}")
    String clientId;
    @Value("${security.kakao.redirect_uri:default_redirect_uri}")
    String redirectUri;

    @Override
    public String getKakaoToken(String code) {
        RestTemplate tokenRt = new RestTemplate(); // Http

        HttpHeaders tokenHeader = new HttpHeaders(); // Http header
        tokenHeader.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8"); // key=value

        MultiValueMap<String, String> params = new LinkedMultiValueMap<>(); // Http body
        params.add("grant_type", "authorization_code");
        params.add("client_id", clientId);
        params.add("redirect_uri", redirectUri);
        params.add("code", code);

        HttpEntity<MultiValueMap<String, String>> kakaoTokenRequest = new HttpEntity<>(params, tokenHeader);

        ResponseEntity<String> response = tokenRt.exchange(
                "https://kauth.kakao.com/oauth/token",
                HttpMethod.POST,
                kakaoTokenRequest,
                String.class
        ); // Request to Kakao

        ObjectMapper objectMapper = new ObjectMapper();
        KakaoLoginDto.KakaoTokenResponseDto tokenResponseDto = new KakaoLoginDto.KakaoTokenResponseDto();

        try{
            tokenResponseDto = objectMapper.readValue(response.getBody(), KakaoLoginDto.KakaoTokenResponseDto.class);
        }catch(JsonMappingException e){
            e.printStackTrace();
        }catch (JsonProcessingException e){
            e.printStackTrace();
        }
        return tokenResponseDto.getAccess_token();
    }

 

getKakaoToken()을 구현합니다. 

 

clientId와 redirectId 값은 민감 정보이므로 환경변수 처리를 했습니다. 환경변수 처리하는 방법은 위에서 언급했으므로 넘어가겠습니다.

 

 

KakaoLoginController.java

@RestController
@RequiredArgsConstructor
@RequestMapping("api/kakao")
public class KakaoLoginController {

    private final KakaoLoginService kakaoLoginService;

    @Operation(summary = "인가코드 발급 API")
    @GetMapping("/callback")
    public ApiResponse<?> callback(@RequestParam("code") String code){
        return ApiResponse.onSuccess(Status.KAKAO_CODE_SUCCESS, code);
    }
	
    // 계속해서 업데이트
    @Operation(summary = "프론트로부터 카카오 인가코드 전달받기")
    @Parameter(name = "code", description = "카카오에서 받은 인카코드, RequestParam")
    @PostMapping("/login")
    public ApiResponse<?> kakaoLoginCode(@RequestParam("code") String code) {
        String token = kakaoLoginService.getKakaoToken(code);
        return ApiResponse.onSuccess(Status.KAKAO_LOGIN_SUCCESS, token);
    }
}

 

kakaoLoginCode를 추가했습니다. 이 컨트롤러는 글을 진행하며 계속해서 업데이트하겠습니다.

 

 

 

🏁 토큰 발급 확인하기

 

발급받은 인가코드를 복사합니다.

❗️ 주의 : 한 번 사용한 인가코드는 오류가 발생할 수도 있으니 다시 링크에 접속하셔서 재발급받는 것을 추천드립니다.

https://kauth.kakao.com/oauth/authorize?client_id={이곳에 REST API 키를 넣어주세요}&redirect_uri={이곳에 redirect_uri를 넣어주세요}&response_type=code

 

그리고 Swagger에 접속합니다. Swagger v3는 아래의 주소입니다.

http://localhost:8080/swagger-ui/index.html#/

 

data에 토큰값이 잘 담기는 것을 확인할 수 있습니다.

 

 

3. 사용자 정보 요청

사용자 정보를 요청하기 위해서 순서대로 코드를 업데이트해 주세요.

 

 

KakaoLoginServiceImpl.java

@Service
@RequiredArgsConstructor
@Transactional
public class KakaoLoginServiceImpl implements KakaoLoginService{

    @Value("${security.kakao.client_id:default_client_id}")
    String clientId;
    @Value("${security.kakao.redirect_uri:default_redirect_uri}")
    String redirectUri;

    @Override
    public String getKakaoToken(String code) {
        // (생략 : 2번에서 구현했습니다.)
    }

    @Override
    public KakaoLoginDto.KakaoUserInfoDto getKakaoUserInfo(String token) {
        RestTemplate userRt = new RestTemplate(); // Http

        HttpHeaders userHeader = new HttpHeaders(); // Http header
        userHeader.add("Authorization", "Bearer " + token); // Bearer + 토큰
        userHeader.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");

        HttpEntity<MultiValueMap<String, String>> kakaoProfileRequest = new HttpEntity<>(userHeader);


        ResponseEntity<String> response = userRt.exchange(
                "https://kapi.kakao.com/v2/user/me",
                HttpMethod.POST,
                kakaoProfileRequest,
                String.class
        );

        ObjectMapper objectMapper = new ObjectMapper();
        KakaoLoginDto.KakaoUserInfoResponseDto kakaoUserInfoResponseDto = new KakaoLoginDto.KakaoUserInfoResponseDto();

        try{
            kakaoUserInfoResponseDto = objectMapper.readValue(response.getBody(), KakaoLoginDto.KakaoUserInfoResponseDto.class);
        }catch(JsonMappingException e){
            e.printStackTrace();
        }catch (JsonProcessingException e){
            e.printStackTrace();
        }

        KakaoLoginDto.KakaoUserInfoDto dto = new KakaoLoginDto.KakaoUserInfoDto().builder()
                .email(kakaoUserInfoResponseDto.getKakao_account().getEmail())
                .build();

        return dto;
    }
}

 

 

KakaoLoginController.java

@RestController
@RequiredArgsConstructor
@RequestMapping("api/kakao")
public class KakaoLoginController {

    private final KakaoLoginService kakaoLoginService;

    @Operation(summary = "인가코드 발급 API")
    @GetMapping("/callback")
    public ApiResponse<?> callback(@RequestParam("code") String code){
        return ApiResponse.onSuccess(Status.KAKAO_CODE_SUCCESS, code);
    }

    @Operation(summary = "프론트로부터 카카오 인가코드 전달받기")
    @Parameter(name = "code", description = "카카오에서 받은 인카코드, RequestParam")
    @PostMapping("/login")
    public ApiResponse<?> kakaoLoginCode(@RequestParam("code") String code) {
        String token = kakaoLoginService.getKakaoToken(code);
        KakaoLoginDto.KakaoUserInfoDto kakaoUserInfoDto = kakaoLoginService.getKakaoUserInfo(token);
        return ApiResponse.onSuccess(Status.KAKAO_LOGIN_SUCCESS, kakaoUserInfoDto);

    }
}

 

 

🏁 사용자 정보 확인하기

 

인가코드를 복사합니다.

https://kauth.kakao.com/oauth/authorize?client_id={이곳에 REST API 키를 넣어주세요}&redirect_uri={이곳에 redirect_uri를 넣어주세요}&response_type=code

 

그리고 Swagger에 접속합니다. 

http://localhost:8080/swagger-ui/index.html#/

 

data에 사용자 정보가 담긴 것을 확인할 수 있습니다.

저는 이메일만 로그인 동의항목에 추가했기 때문에 이메일 값 하나만 보이는 것입니다.

 

 

3. 사용자 정보 저장 & JWT 토큰 발급

지금부터는 여러분의 서비스에 맞춰서 구현이 필요합니다.

 

사용자 정보를 저장하는 방법은 일반 회원가입 구현할 때와 동일합니다.

 

JWT 토큰 발급에 대한 자세한 설명은 이 글에서 하지 않겠습니다. 글이 너무 길어질 거 같아요 😭

대신 코드는 모두 작성해 두었습니다. 

 

 

📌 구현 순서

1. 사용자 정보(이메일)를 이용하여 이미 회원가입이 된 사용자인지 확인한다.

1-1. 회원가입이 되어 있지 않다면 데이터베이스에 저장한 뒤, 로그인하고 jwt 토큰을 발급한다.

1-2. 회원가입이 이미 되어 있다면, 로그인하고 jwt 토큰을 발급한다.

 

📌 구현에 필요한 클래스 

User.java ➡️ user 테이블을 구현한 entity입니다. 

UserCustomDto.java ➡️
사용자 인증에 필요한 정보를 담습니다.
KakaoLoginDto.java ➡️ (위에서 사용함) 사용자 정보를 읽을 때 필요합니다.

JWTUtil.java ➡️
JWT 토큰 생성, 파싱, 검증 기능을 수행합니다.
AppLoginFilter.java ➡️
로그인 요청 시 인증을 처리하는 필터입니다.
JWTFilter.java ➡️ 사용자 요청이 들어올 때 JWT 토큰을 검증하는 필터입니다.
SecurityConfig.java ➡️ Spring Security의 보안 설정을 담당합니다.

 

 

Build.gradle

dependencies {
    // JWT
    implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
    implementation 'io.jsonwebtoken:jjwt-impl:0.11.5'
    implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5' // JSON 파싱을 위해 Jackson 사용
    implementation 'org.springframework.boot:spring-boot-starter-security'
}

 

JWT를 사용하기 위해서 Build.gradle에 코드를 추가해 주세요.

 

 

UserCustomDto.java

public class UserCustomDto implements UserDetails {
    
    private final User user;
    public UserCustomDto(User user) {this.user = user;}

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities(){

        Collection<GrantedAuthority> collection = new ArrayList<>();

        collection.add(new GrantedAuthority() {
            @Override
            public String getAuthority() {
                return null;
            }
        });

        return collection;
    }

    @Override
    public String getPassword() { return user.getEmail(); }

    @Override
    public String getUsername() { return user.getEmail(); }

    public Long getUserId(){
        return user.getId();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }



KakaoLoginDto.java

public class KakaoLoginDto {
    
    // (생략) 위에서 구현함

    @Getter
    @Builder
    @AllArgsConstructor
    @NoArgsConstructor
    public static class KakaoUserInfoDto{ // 카카오 토큰을 이용해서 읽어온 유저 정보
        private String email;
    }

    @Getter
    @Builder
    @AllArgsConstructor
    @NoArgsConstructor
    public static class LoginResponseDto{
        private Long userId;
        private String token;
    }

    @Getter
    @AllArgsConstructor
    @NoArgsConstructor
    public static class LoginRequestDto{
        public String email;
    }
}

 

KakaoLoginDto는 이미 생성한 클래스인데요, 3개의 static class를 추가해 줍니다.

 

 

JWTUtil.java

@Component
public class JWTUtil {

    private SecretKey secretKey;

    public JWTUtil(@Value("${spring.jwt.secret}")String secret) {
        this.secretKey = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
    }

    public Long getUserId(String token) {
        Claims claims = Jwts.parserBuilder()
                .setSigningKey(secretKey)
                .build()
                .parseClaimsJws(token)
                .getBody();

        return claims.get("userId", Long.class);
    }

    public Boolean isExpired(String token) {

        Date expiration = Jwts.parserBuilder()
                .setSigningKey(secretKey)
                .build()
                .parseClaimsJws(token)
                .getBody()
                .getExpiration();

        return expiration.before(new Date());
    }

    public String createJwt(Long userId, String nickname, Long expiredMs) {

        return Jwts.builder()
                .claim("userId", userId)
                .claim("nickname", nickname)
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + expiredMs))
                .signWith(secretKey, SignatureAlgorithm.HS256)
                .compact();
    }
}

 

secretKey 값은 환경변수 처리를 해주었습니다.

secretKey 변수 값으로는 그냥 아주 어렵고 긴 값을 마음대로 넣어주세요.

예시 : dksfhak;jsdjflks1234323948758493029847rdsmnkfaknlsdmfalknflk;samdnklga;g0294758932903

 

 

 

JWTFilter.java

public class JWTFilter extends OncePerRequestFilter {

    private final JWTUtil jwtUtil;
    public JWTFilter(JWTUtil jwtUtil){
        this.jwtUtil = jwtUtil;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        String authorization = request.getHeader("Authorization");

        if(authorization == null || !authorization.startsWith("Bearer ")){
            filterChain.doFilter(request, response);
            return; //조건이 해당되면 메소드 종료(필수)
        }

        String token = authorization.split(" ")[1]; //Bearer 부분 제거 후 순수 토큰만 획득

        if (jwtUtil.isExpired(token)) {
            filterChain.doFilter(request, response);
            return; // 조건이 해당되면 메소드 종료 (필수)
        }

        Long userId = jwtUtil.getUserId(token);

        User user = User.builder()
                .id(userId)
                .build();

        UserCustomDto userCustomDto = new UserCustomDto(user);
        Authentication authToken = new UsernamePasswordAuthenticationToken(userCustomDto, null, userCustomDto.getAuthorities()); //스프링 시큐리티 인증 토큰 생성

        SecurityContextHolder.getContext().setAuthentication(authToken); //세션에 사용자 등록

        filterChain.doFilter(request, response);
    }
}

 

 

AppLoginFilter.java

public class AppLoginFilter extends UsernamePasswordAuthenticationFilter {

    private final AuthenticationManager authenticationManager;

    //JWTUtil 주입
    private final JWTUtil jwtUtil;

    public AppLoginFilter(AuthenticationManager authenticationManager, JWTUtil jwtUtil){
        this.authenticationManager = authenticationManager;
        this.jwtUtil = jwtUtil;
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {

        try {
            ObjectMapper objectMapper = new ObjectMapper();
            KakaoLoginDto.LoginRequestDto loginRequest = objectMapper.readValue(request.getInputStream(), KakaoLoginDto.LoginRequestDto.class);

            UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
                    loginRequest.getEmail(),
                    loginRequest.getEmail() // 비밀번호 대신 이메일 사용
            );
            return authenticationManager.authenticate(authToken);
        } catch (IOException e) {
            throw new RuntimeException("Error reading login request", e);
        }
    }

    // 로그인 성공시 실행하는 메소드 -> 여기서 JWT가 발급됨.
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) {

        UserCustomDto userCustomDto = (UserCustomDto) authentication.getPrincipal();

        String token = jwtUtil.createJwt(userCustomDto.getUserId(), 60 * 60 * 1000L); // 만료 시간 1시간 설정

        response.addHeader("Authorization", "Bearer " + token);
    }

    // 로그인 실패시 실행하는 메소드
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) {

        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.setContentType("application/json");
        try {
            response.getWriter().write("{\"error\": \"Invalid login credentials\"}");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 

카카오 로그인은 소셜 로그인이므로 따로 회원가입 시 비밀번호를 생성하지 않습니다.

본인 서비스 로직을 어떻게 설계하느냐에 따라 다를 수 있습니다.

 

그런데 jwt 토큰을 발급하려면 무조건 비밀번호가 필요해서, 저는 비밀번호 대신 이메일을 사용했습니다.

만약 일반 로그인을 구현할 때 jwt 토큰을 발급한다면 진짜 비밀번호를 넣어주면 되는 것입니다.

 

 

SecurityConfig.java

@Configuration
public class SecurityConfig {

    private static final String[] PERMIT_URL_ARRAY = {
            /* swagger v3 */
            "/v3/api-docs/**",
            "/swagger-ui/**"
    };

    private final AuthenticationConfiguration authenticationConfiguration;

    private final JWTUtil jwtUtil;

    public SecurityConfig(AuthenticationConfiguration authenticationConfiguration, JWTUtil jwtUtil) {

        this.authenticationConfiguration = authenticationConfiguration;
        this.jwtUtil = jwtUtil;

    }

    //AuthenticationManager Bean 등록
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {

        return configuration.getAuthenticationManager();
    }

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {

        return new BCryptPasswordEncoder();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        http.csrf((auth) -> auth.disable());

        http.formLogin((auth) -> auth.disable());

        http.httpBasic((auth) -> auth.disable());

        http.authorizeHttpRequests((auth) -> auth
                .requestMatchers("/**", "/").permitAll()
                .requestMatchers(PERMIT_URL_ARRAY).permitAll()
                .anyRequest().authenticated());

        http.addFilterAt(new AppLoginFilter(authenticationManager(authenticationConfiguration), jwtUtil), UsernamePasswordAuthenticationFilter.class); //JWTUtil 인수 추가
        http.addFilterAt(new AppLoginFilter(authenticationManager(authenticationConfiguration), jwtUtil), UsernamePasswordAuthenticationFilter.class);

        http.addFilterBefore(new JWTFilter(jwtUtil), AppLoginFilter.class);

        http.sessionManagement((session) -> session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS));

        return http.build();
    }
}

 

 

KakaoLoginController.java

@RestController
@RequiredArgsConstructor
@RequestMapping("api/kakao")
public class KakaoLoginController {

    private final KakaoLoginService kakaoLoginService;
    private final UserAccountService userAccountService;
    private final JWTUtil jwtUtil;

    @Operation(summary = "인가코드 발급 API")
    @GetMapping("/callback")
    public ApiResponse<?> callback(@RequestParam("code") String code){
        return ApiResponse.onSuccess(Status.KAKAO_CODE_SUCCESS, code);
    }

    @Operation(summary = "프론트로부터 카카오 인가코드 전달받기")
    @Parameter(name = "code", description = "카카오에서 받은 인카코드, RequestParam")
    @PostMapping("/login")
    public ApiResponse<?> kakaoLoginCode(@RequestParam("code") String code) {
        String token = kakaoLoginService.getKakaoToken(code);
        KakaoLoginDto.KakaoUserInfoDto kakaoUserInfoDto = kakaoLoginService.getKakaoUserInfo(token);
        
        // 이미 회원가입된 사용자인지 확인한다.
        if(userAccountService.findByEmail(kakaoUserInfoDto.getEmail()).isEmpty()) userAccountService.saveKakaoUser(kakaoUserInfoDto.getEmail());
        User user = userAccountService.findByEmail(kakaoUserInfoDto.getEmail()).get();
		
        // JWT 토큰 발급
        String jwtToken = jwtUtil.createJwt(user.getId(), 3600000L);
        
        KakaoLoginDto.LoginResponseDto dto = new KakaoLoginDto.LoginResponseDto().builder()
                .userId(user.getId())
                .token(jwtToken)
                .build();

        return ApiResponse.onSuccess(Status.LOGIN_SUCCESS, dto);
    }
}

 

마지막으로 Controller를 구현하면 끝입니다!!! 🤤🤤🤤

 

이미 회원가입된 사용자인지 확인하는 로직은 일반 회원가입과 동일합니다.

 

 

 

 

 

 

 

긴 글 읽어주셔서 감사합니다 😊

 

 

 

 

 

 

반응형
LIST
loading