Coding History/project

로컬 로그인 폐기. 및 구글로그인 (JWT 발급 및 로직 통과 구현)

BlackBirdIT 2024. 9. 13. 15:46

로컬 로그인 Session 문제 여전히 해결 안됐다.

이건 내 능력 밖이라고 생각하고 그냥 버리려고 한다.

소셜로그인만 사용해도 별 문제 없으니까..

회원가입 페이지가 조금 아깝긴한데 그냥 버리자 이러다가 아무것도 진행못할 것 같다.

그렇게 결단을 내리고,

여태까지 쌓아왔던 클래스들을 지우니 마음이 좀 아팠다.(사실 속 시원했다)

삭제한 클래스

MemberController > doJoin - join - doLocalLogin - logout 메서드

CustomUserDetails : 로컬 로그인 시 사용자의 인증 정보를 담았던 클래스

CustomUserDetailsService : CustomUserDetails를 기반으로 사용자 정보를 조회하고 로컬 인증을 처리하던 서비스 클래스

CustomLoginFilter : 로그인이 필요한 서비스에 접근을 막는 클래스

CustomLogoutFilter : 로그 아웃이 필요한 서비스에 접근을 막는 클래스

Rq : 세션 정보를 관리하고, 로컬 로그인 시 SecurityContext에 인증 정보를 저장하거나 세션 관리를 처리하던 클래스

뭔가 많이 해놨는데 다 지우니까 좀 아깝긴한데 어떡하겠냐.. 기한을 맞춰야되니 그렇게 중요하지 않은 것에 집착하지 말자.


여튼간에 이젠 다시 소셜 로그인에 집중할 수 있게 되었고, 구글로그인 세션이 어느정도 구축이 된다면 이제 스포티파이 api를 사용한 로그인과 플레이어 재생을 시작할 것이다.

구축해야할 것

현재 세션 정보 저장은 잘 되는 상태이다. 로그아웃 하지 않고 서버를 껐다 켜도 로그인 했던 사용자의 이메일을 콘솔에 띄워준다.
이게 메인 페이지에서도 잃지 않는가 확인해야하고, 지금은 로그인 페이지를 로그인 상태에서도 접근할 수 있어서 콘솔을 보지 않는다면 로그인 상태인지 아닌지 확인할 수 없다.

일단은 이 문제들을 해결하고 스포티파이로 넘어 갈 것!!

브라우저 콘솔과 인텔리제이 프로젝트 콘솔에서 소셜 로그인한 사용자의 정보를 볼 수 있게 한번 만들어보자.


내가 로컬로그인에 집중하느라 몰랐는데, firebaseUserService에 회원가입 기능만 넣어두고 로그인 로직은 만들어놓지 않고 왜 시큐리티에서 OAuto2인증을 넘어가지 못하는가 확인한다고 꽤나 시간을 버렸다.

그러니까 회원가입만 만들었지 아직 로그인을 제대로 만들지는 않았던 것.
JWT 발급받아서 OAuto2 인증 절차를 통과시켜야함.

일단 firebaseUserService에 로그인 메서드를 만들어주자.

    public void googleLogin(String idToken) throws FirebaseAuthException {
        // Firebase에서 ID 토큰 검증
        FirebaseToken decodedToken = FirebaseAuth.getInstance().verifyIdToken(idToken);
        String uid = decodedToken.getUid();
        String email = decodedToken.getEmail();
        System.out.println("Firebase에서 검증된 사용자 UID: " + uid);
        System.out.println("Firebase에서 검증된 사용자 이메일: " + email);

        // 사용자 정보를 로컬 DB에서 찾기
        Optional<Member> localUser = memberRepository.findByGoogleLoginId(uid);

        if (localUser.isPresent()) {
            // 로컬 DB에 사용자 정보가 있으면 로그인 처리
            System.out.println("사용자가 이미 존재합니다. UID: " + uid);
            // 여기에 세션 설정이나 JWT 발급 등 로그인 처리 로직을 넣으면 됨
        } else {
            // 로컬 DB에 없으면 새로 회원가입 후 로그인 처리
            createUserInFirebase(email, null);
            // 이후 로그인 처리 (세션 설정 또는 JWT 발급)
        }
    }

우선 틀은 이렇게 짰고.

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

JWT 발급을 위해서 의존성을 추가해줘야한다.

이런 오류가 뜨네 뭔지 알아보자!

아 오류가 아니고 리빌드 시켜주면 된다. 코끼리 모양 클릭해서 리빌드 시켜주자!

이후에 코드를 따와서 일단 적용시켜보면.

private final String SECRET_KEY = "your_secret_key";

    public String googleLogin(String idToken) throws FirebaseAuthException {
        // Firebase에서 ID 토큰 검증
        FirebaseToken decodedToken = FirebaseAuth.getInstance().verifyIdToken(idToken);
        String uid = decodedToken.getUid();
        String email = decodedToken.getEmail();
        System.out.println("Firebase에서 검증된 사용자 UID: " + uid);
        System.out.println("Firebase에서 검증된 사용자 이메일: " + email);

        // 사용자 정보를 로컬 DB에서 찾기
        Optional<Member> localUser = memberRepository.findByGoogleLoginId(uid);

        if (localUser.isPresent()) {
            // 로컬 DB에 사용자 정보가 있으면 로그인 처리
            System.out.println("사용자가 이미 존재합니다. UID: " + uid);
            //JWT 발급 로그인 처리
            return generateToken(email);
        } else {
            // 로컬 DB에 없으면 새로 회원가입 후 로그인 처리
            createUserInFirebase(email, null);
            // 이후 JWT 발급 로그인 처리
            return generateToken(email);
        }
    }

    private String generateToken(String email) {
        return Jwts.builder()
                .setSubject(email)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10))  // 10시간 유효
                .signWith(SignatureAlgorithm.HS256, SECRET_KEY)
                .compact();
    }

이런 모양이다.

이제 your_secret_key 이 부분이 약간 환장 포인트가 될 것 같은데 원래는 암호화 해서(환경 변수를 건드려줘야한다.) 가져와야한다. 유저의 시크릿키를 하드코딩하면 안되기 때문에.. 근데 일단은 시키는대로 따라가보자.

your_secret_keyrandomkeygen같은 사이트에서 만들어서 사용하면 된다! 이 값은 절대 노출 시키면 안된다.

할거면 제대로 해야된다가 목표니까 yml파일에 암호화해서 들어가도록 만들어보자.

우선 나는 256비트 키를 임의로 가져왔다.

우선 간만에 yml 파일을 들여다보자.

jwt:
  secret: your_secret_key

원래는 이렇게 설정을 해주는데 (하드코딩).

jwt:
  secret: ${JWT_SECRET_KEY}

이렇게 환경변수를 참조하게끔 만든다는 것!

난 그냥 영구 적용을 해놓으려고 하기 때문에 그냥 터미널 최상위에서 적용하겠다.

우선 터미널을 열고,

nano ~/.zshrc

명령어를 입력하면 특정 파일로 접근할텐데,

이렇게 파일 맨 아래에 본인 비밀 키 작성후 저장해주면 된다.

저장하고 나온 뒤에는

source ~/.zshrc

를 해주면 적용된다!

시크릿키는 다시 환경변수 설정으로 들어가보면 되니까 굳이 따로 저장해줄 필요는 없을 것 같다. 불안해서 따로 보관하려면 text 파일을 나만 열어볼 수 있게 설정해서 갖고있으면 된다.

그리고 이거 설정하는 와중에 든 생각인데, JWT 관련된 걸 FirebaseUserService클래스에 다 박아넣는 것 보다,

JWT 클래스를 따로 빼서 관리하는게 편할 것 같다는 생각이 들어서 분리해주려고 한다.

@Component
public class JwtTokenProvider {

    @Value("${jwt.secret}")
    private String secretKey;

    // 토큰 유효 시간 설정 (예: 10시간)
    private final long tokenValidTime = 1000L * 60 * 60 * 10;

    // JWT 토큰 생성
    public String createToken(String email) {
        Claims claims = Jwts.claims().setSubject(email);
        Date now = new Date();
        Date expiration = new Date(now.getTime() + tokenValidTime);

        return Jwts.builder()
                .setClaims(claims)
                .setIssuedAt(now)
                .setExpiration(expiration)
                .signWith(SignatureAlgorithm.HS256, secretKey)
                .compact();
    }

    // JWT 토큰에서 이메일 정보 추출
    public String getUserEmailFromToken(String token) {
        return Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(token)
                .getBody()
                .getSubject();
    }

    // JWT 토큰의 유효성 검증
    public boolean validateToken(String token) {
        try {
            Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
            return true;
        } catch (Exception e) {
            // 토큰이 만료되거나 변조된 경우 false 반환
            return false;
        }
    }
}

이렇게 따로 만들어주었다.

서비스에서의 로직도 일단 정리 했으니까 이제 컨트롤러를 확인해보자.

대충 만들고 서버를 켜봤는데,

Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
2024-09-13T11:39:40.067+09:00 ERROR 10135 --- [  restartedMain] o.s.boot.SpringApplication : Application run failed

서버가 안켜지네!?

로그 한번 살펴보자.

JwtTokenProvider에서 jwt.secret 값을 받아오려고 할 때 오류가 생긴다고 한다.

환경 변수가 제대로 적용이 안됐는지 부터 확인해보자.

echo $JWT_SECRET_KEY

를 실행해보면


음.. 잘 가져오니까 프로젝트 내에서 문제구나.

혹시 과거의 내가 사고를 쳤을 수도 있으니, 환경변수의 이름을 바꿔보고 오겠다.


환경 변수 이름은 문제가 아니였고,

일단 아무리 찾아보고 이것 저것 시도를 해봐도 잘 안됐다.

그래서 환경변수는 일단 냅두고 프로젝트 내에서 설정하는 방법을 찾아서 설정해줬다.

그래도 안되길래, yml설정에서 jwt 위상을 이리저리 바꾸면서 제일 위로 올려보니까.

server:
  port: 8081

jwt:
  secret: ${BBLOFI_JWT_SECRET_KEY}
  token-validity-in-seconds: 3600

spring:

이제서야 되더라.. 터미널 환경에서 설정하는거 좀 더 하면 해결 될 것 같긴한데 이제 해결 할 수 있는 문제를 두고 다른 루트로 오래 고민하는 것은 하지 않기로 결심했기 때문에 이렇게 해결하고 넘어가겠다.

시작 전 오류가 분명 생길거라 생각해서 특정 부분 다 로그를 찍게끔 만들었다. 그런 다음 드디어 로그인 시도를 해봤는데, 당연히 오류가 떴고.. 로그는 이렇다.

원래 찾는 것 까지는 됐었으니 토큰을 검증하는 과정이 문제라는건데 뭐가 문제일까.

일단 많은걸 고쳤는데 정리를 안하면서 해서 기억이 잘 안난다..

<dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
    <version>2.3.1</version>
</dependency>

javax.xml.bind.DatatypeConverter 클래스가 누락되어 발생하는 오류에 대해서 해결을 위해서 의존성 추가를 해주었고,

프론트에서 JSON요청으로 처리하기 위해서

컨트롤러에서 String으로 응답하던 것을 ResultData로 바꿨다가 HTTP 상태 코드 제어할 수 있는 코드를 찾아서 ResponseEntity<ResultData<?>> 로 변경해줬다.

그렇게 여러가지를 Firebase ID 토큰을 받아서 검증하는 것까지, 그리고 로그에서 Firebase 토큰 검증 후에 로컬 DB에서 사용자 정보를 조회하고, JWT 토큰 생성까지! 는 어떻게 해냈다.

근데 프론트에서는

로그인 실패가 떴다. 진짜 거의 다 왔다.

저기 보면 302에러가 나 있어서 그게 뭔지 검토,,
302는 리다이렉션을 나타내는 코드라고 한다.

/firebaseUser?email=요청을 보냈을 때, 리다이렉션이 발생하면서 원래 의도했던 데이터를 받지 못해서 로그인 실패가 발생하는 것으로 추론.

여기서 내가 login.js에서 로그인 성공시에 main으로 리다이렉션하는 방식을 넣어뒀었는데 그게 문제인듯??

일단 로그를 다 찍어보자..

if (data.resultCode && data.resultCode.startsWith('S-'))여기 R이 대문자여서 문제가 생겼다.

이거 소문자로 고치니까 로그인 로직 모두 다 통과 완료!!

이제는 OAuto2 까지 적용해서 구현하면 된다.

다음 목표는 OAuto2 인증을 JWT와 병합해서 처리하면 된다.