본문 바로가기
spring security

spring security - #25 (2) jwt를 위해서 로그인 진행하기 / ObjectMapper 사용 json 파싱

by letsDoDev 2024. 1. 28.

/login 시

로그인 함수를 수행할

JwtAuthenticationFilter

클래스의

attemptAuthentication

메소드를 수정하면서

테스트해보자!

 

- JwtAuthenticationFilter.java

전체 코드는 맨 마지막에 올릴 예정

 

# 추가 부분

 

포스트맨 실행 결과

콘솔

 

잘 뜬다

위에 노란 박스가

저 스트림 바이트 안에 username과 password가 담겨져 있다는 것

 

더 자세하게 확인해보자

위 노란박스 부분을 아래 노란 박스 부분으로 수정한 다음

- JwtAuthenticationFilter.java

포스트맨으로 다시 로그인 시도!

콘솔 결과 ▼

포스트맨으로 보냈던 username과 password가 출력된 것을 볼 수 있다.

---

처 부분을 다시 parsing 해보자 

여기서 유의할 점은 저 username과 password는 request.getParameter() 로 받을 수 없다.

--

포스트맨으로 로그인이 x-www-form-unrlencoded 로 id와 pw를 전송했었다.

json 타입으로 보낸다면 어떻게 될까?

 

콘솔 결과 ▼

위 스크린샷 처럼 json 타입으로 넘어오게 된다!

 

---

그럼 이제 내가 로그인 시도할 때 어떻게 하냐에 따라 달라질 것이라는 것을 알 수 있다!

일반적으로 web 클라이언트에서 로그인을 시도하면 x-www-formurlencoded 로 전송된다.

(안드로이드에서는 대부분 json, 자바스크립트도 json인 경우 많다)

--

넘어오는 값이

json 이라면 더 쉬운 방법으로

이 부분을 대체할 수 있다

한 번 해보자!! 기존에 작성한 거는 주석처리하고 하겠다!

 

아래처럼 수정했다.

ObjectMapper를 사용했다!

 

- JwtAuthenticationFilter.java

다시 포스트맨으로 username 과 password를 json으로 넘겨준 후 요청하면

콘솔창은 ▼

근데 여기서 실제로 로그인을 해보려면

토큰을 생성해주어야 한다 원래 폼로그인하면 자동으로 생성되는 건데

우리는 직접해야한다 지금은,,

 

- JwtAuthenticationFilter.java

 

이제 서버 재시작 후 postman으로 다시 로그인을 해보자!

 

 

(application.yml 의 jpa.hibernate.ddl-auto : create 때문에

기존에 회원가입 시켜놓은 계정이 사라졌다,,

update로 바꾸고 포스트맨을 통해 재가입을 진행하였다.

)

 

 

PrincipalDetails principalDetails = (PrincipalDetails) authentication.getPrincipal();
System.out.println(principalDetails.getUsername());

구문으로 출력한 나의 로그인 정보 중 username 이 콘솔에 잘 출력된 것을 볼 수 있다!!

--

 

 

그 이후 단계에 jwt 토큰을 생성해서 로그인 request한 사용자에게 response로 jwt 토큰을 보내야 한다.

순서와 메소드 확인 부분이 너무 길어

수정 완료한 이번 게시물 최종 코드를 올려 놓겠다

- JwtAuthenticationFilter.java

package com.cos.jwt.jwt;

import com.cos.jwt.auth.PrincipalDetails;
import com.cos.jwt.model.User;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

// 스프링 시큐리티에서 UsernamePasswordAuthenticationFilter 이 필터가 있는데
// 원래 이 필터는 /login 요청해서 username과 password를 전송하면(post)
// 이 필터가 동작한다.
// 근데 지금 동작하지 안했던 이유는
// spring security config에서 formLogin.disable() 해뒀기 때문
// 그렇다면 어떻게 하면 작동시킬 수 있을까?
// JwtAuthenticationFilter 이 필터를 사디
// SecruityConfig에 필터로 등록해주면 된다.

@RequiredArgsConstructor
public class JwtAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    // 로그인을 진행하는 필터
    private final AuthenticationManager authenticationManager;

    // 로그인을 수행하는 함수
    // login 요청을 하면 로그인 시도를 위해 실행되는 함수
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        System.out.println("JwtAuthenticationFilter:로그인 시도중");

        //id, pw를 확인해서 db로 확인해서 이 id와 pw가 정상이면 ▼
        // 1. username, password 를 받아서

        try{
            /*
            // x-www-formurlencoded와 json 읽어와서 콘솔 찍어본 방법
            BufferedReader br = request.getReader();
            String input = null;
            while ((input = br.readLine()) != null){
                System.out.println(input);
            */

            ObjectMapper om = new ObjectMapper(); // view로부터 넘어온 json을 파싱해주는 객체
            User user = om.readValue(request.getInputStream(), User.class);
            // ┗▶ 이렇게 하면 알아서 json 읽어온 값을 User 객체로 만들어준다.
            System.out.println(user);

            // 포스트맨으로 테스트하기 때문에 (폼로그인이 아니라서) 직접 임의 토큰 만들기
            UsernamePasswordAuthenticationToken authenticationToken =
                    new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword()); // 첫번째 파마미터 username 두번째 파라미터 password
            // 이렇게 임의로 만든 토큰으로 로그인 시도를 한 번 해보자!
            // 토큰을 날리자 -> PrincipalDetailsService의 loadUserByUsername() 함수가 실행됨(username만 던짐 pw db에서 알아서 해줌 스프링이)
            // PrincipalDetailsService의 loadUserByUsername()가 실행된 후 정상이면 authenctication이 리턴된다.
            // 리턴되었다면 -> DB에 있는 username과 password가 일치한다는 뜻
            // DB에 있는 username과 password가 일치한다.
            Authentication authentication =    // 내가 로그인한 정보가 담긴다.
                    authenticationManager.authenticate(authenticationToken);
            // 제대로 로그인 되었다면 authentication 객체가 session 영역에 저장될 것이다! 그리고 꺼내봤을 때 null 이 아닐 것이다!
            // 꺼내서 확인해보자! -> sysout로 확인해보자! username만 꺼내서 확인해보자!
            PrincipalDetails principalDetails = (PrincipalDetails) authentication.getPrincipal();
            System.out.println("로그인 완료됨" + principalDetails.getUsername()); // 값이 있다면 로그인 정상적으로 되었다는 뜻.
            // authentication 객체가 session 영역에 정장을 해야하고 그 방법이 return 해주면 됨.
            // 리턴의 이유는 권한 관리를 security 가 대신 해주기 때문에 편하려고 하는 거임.
            // 굳이 JWT 토큰을 사용하면서 세션을 만들 이유가 없음. 근데 단지 권한 처리 때문에 session에 넣어줍니다.

            // 넣기 직전에 해야 할 것이 있다 바로
            // JWT 토큰 만들기 (이 과정을 여기 정의 말고 아래 정의 successfulAuthentication 메소드에서 수행됨.)

            // 제대로 로그인이 되었다면 아래 값이 반환될 것이다.
            // 이렇게 return 될 때 객체가 session에 저장된다.
            return authentication;

        }catch (Exception e){
            e.printStackTrace();
        }
        // (-------------------------------------------------------------------------------------------------------
        // 2. 정상인지 로그인 시도를 해본다. -> authenticationManager로 로그인 시도를 하면 PrincipalDetailsService가 호출된다.
        // 그러면 PrincipalDetailseService에 있는 loadUserByUsername 메소드가 자동으로 실행된다.
        // 여기서 정상적으로 PrincipalDetails가 return이 되면S
        // 3. 그 principalDetails를 세션에 담고 (oauth2 방식과 다르게 Authentication 에 담지 않고 바로 세션에 담는다 ,,,,)
        // -> 굳이 세션에 담는 이유: 세션에 담지 않으면 권한 관리가 되지 않기 떄문
        //      하지만 권한관리가 필요없는 경우라면 (물론 그런 경우는 거의 없겠지만) principalDetails를 세션에 담지 않아도 된다.
        // 4. jwt 토큰을 만들어서 응답해주면 됨.
        // ------------------------------------------------------------) ━▶ try {} 영역안 주석인데 길어서 아래로 뺀 거임

        // 제대로 로그인이 되지 않았다면 null을 반환한다.
        return null;
    }

    // 실행 순서
    // attemptAuthentication 실행 후 인증이 정상적으로 되었으면 successfulAuthentication 함수가 실행됨
    // jwt 토큰을 만들어서 request 요청한 사용자에게 jwt 토큰을 response해주면 됨.
    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
        System.out.println("successfulAuthentication 실행된 : 인증이 완료되었다는 뜻임");
        super.successfulAuthentication(request, response, chain, authResult);
    }
}

 

서버 재시작 후 로그인을 다시 시도해보면

정상적으로 로그인 성공 후 jwt 토큰 생성하는 메소드(오버라이드함) 인 successfulAuthentication이 실행된 것을 볼 수 있다!!!

자동으로 실행된다 별도로 try 영역 안에서 실행시키지 않고 메소드 오버라이드만 해주면 됨!!

 

--

추가

잘못된 계정으로 로그인 하면!!

successfulAuthentication 이 실행되지 않았다는 것을 알 수 있다!!

 

임의 토큰을 만들었었다..

다음 게시물에서는 진짜로 jwt 토큰을 만들어서 반환하는 식으로 해보겠다!!