이번 강의에서는
최초 로그인 중에 JWT 토큰을 생성하기 위한
로직을 정의해 봅시다
이는 엔드유저가 자격증명을 입력하여
웹 애플리케이션에 로그인하려 할 때마다
로그인이 성공적으로 완료 되자마자
JWT 토큰을 생성해야 한다는 뜻입니다
이를 위해 필터를 만들어 주겠습니다
아시다시피 해당 필터 패키지 속 필터들은
전부 다릅니다
이 패키지 안에 새로운 Java 클래스를 만들 것입니다
해당 Java 클래스의 이름은 JWTTokenGeneratorFilter 입니다
그리고 이 필터가
각 요청당 한 번만 실행되도록 원하기 때문에
Spring 프레임워크 내에 있는
extends과 OncePerRequestFilter을 이용할 수 있습니다
따라서 해당 필터를 extend하기 위해
어떤 메소드를 재정의 해야 하는 것일까요?
doFilterInternal
해당 컴파일 문제를 해결해 보도록 하겠습니다
해결하기 위해서는
추상 메소드를 구현해야 합니다
추상 메소드는 doFilterInternal 입니다
이게 메소드입니다
이 throws 구문을 다음 줄로 옮기겠습니다
JWT 토큰을 생성할 때 필요한
로직을 논의해볼 수 있도록
이 필터 안에 코드를 몇 줄 붙여넣겠습니다
보시다시피 몇 로직들은 정의가 되어 있습니다
줄별로 살펴보도록 합시다
첫번째 줄에는
이 필터를
basic authentication 필터 다음에 넣겠습니다
이는 해당 필터가 호출될 때
엔드 유저의 인증이 성공적일 것이라는 뜻입니다
그렇기 때문에
SecurityContextHolder.getContext().getAuthentication()
을 이용하여
현재 인증된 유저의 정보를
가지려는 것입니다
그리고 해당 authentication object이 null이 아닌 경우
우선적으로 이 SecurityConstants 내에
비밀 값을 기반으로
비밀 키를 생성하려고 합니다
따라서 별도의 SecurityConstants 인터페이스를
이미 만들었고
인터페이스 안에는 두개의 constant가 있습니다
첫째는 비밀 키 값을 가진 JWT 키이고
이 값은 백엔드 애플리케이션만 알고 있을 겁니다
이는 아주 아주 안전해야 합니다
여기서는 그냥 직접적으로
constant 파일들을 언급하고 있지만
이상적인 프로덕션 환경에서는
배포 시간에 DevOps 팀에게 요청하여
이 값을 런타임 중에 인젝션하도록 해야합니다
GitHub이나 Jenkins와 같은 CI/CD 도구를 사용하여
환경 변수로 설정하거나
프로덕션 서버 내에서
이를 환경 변수로
구성할 수도 있습니다
하지만 여기서는 그렇지 않아도 일단은 괜찮습니다
이 JWT KEY는 백엔드 애플리케이션
내에 있는 비밀 키인데
해당 Keys 클래스 속 메소드를 사용하여
Key를 생성하려고 합니다
이 Keys 클래스는 이전에 pom.xml에 넣어둔
JWT 토큰 라이브러리에서 왔습니다
이와 같이 Key를 생성했습니다
이제 나중에 Key를 한번 생성하고 나면
보시다시피 이 Jwts.builder을 이용하여
JWT 토큰을 만드려고 합니다
해당 builder 메소드 이후
몇몇 메소드들을 호출할 것입니다
첫 메소드는 setIssuer입니다
Issuer은 해당 JWT 토큰을 발행하는
개인 혹은 조직입니다
여기서 웹 퍼블리케이션 이름은 Eazy Bank라고 되어 있기 때문에
issuer을 그냥 Eazy Bank라고 쓰겠습니다
Issuer 이름 이후
setSubject를 JWT Token으로 둘 수도 있습니다
우선 setSubject를 JWT Token으로 둘 것이지만
해당 subject 안에는 원하는 어떤 값이든 넣어도 됩니다
그 다음에 claim 메소드를 사용하여
로그인된 유저의
ID, 권한을 채울 수 있습니다
ID의 경우
여기 인증 객체에서 getName을 이용하여 받아냈습니다
authorities를 채우기 위해
여기에 populate Authorities 메소드를 작성했고
해당 필터 안을 보시면 이 필터 자체에서
내 모든 권한을 읽어올 것입니다
그리고 이러한 권한으로부터
문자열 값을 형성할 것이고
모든 권한 정보는
쉼표라는 구분자로 구분됩니다
이게 해당 메소드의 역할입니다
클라이언트 메소드를 이용하여
ID와 authorites를 세팅할 것과 같이
해당 authorities와 claim 정보가 정해진 후
우리는 어떤 종류의 유저 정보를 설정할 수 있습니다
하지만 비밀번호 정보는 설정하지 않아야 합니다
왜냐하면 누군가 JWT 토큰을 쉽게 디코딩할 수 있기 때문입니다
그리고 클라이언트 애플리케이션은
엔드유저의 비밀번호에 대한 정보를 절대 요구하지 않아야 합니다
ID, 이메일, 권한에 대해서 물어보고 요구할 수 있지만
비밀번호는 안됩니다
그리고 이 setIssuedAt을 이용하여
클라이언트 애플리케이션에게
이 JWT 토큰이 발행될 때의 날짜 값을 설정하겠습니다
IssuedAt 값을 설정한 후
JWT 토큰의 만료 시간 역시
설정해야 합니다
setExpiration 메소드로
JWT 토큰이 언제 만료될 것인지에 대한 정보를
보내려고 합니다
보시다시피 여기에 현재 시간부터
이 시간까지로 작성하려 하는 것입니다
이 숫자만큼의 밀리초를 추가하려 합니다
해당 밀리초 값은
8시간 정도로 계산할 수 있는데
이는 8시간 만료 시간의
토큰을 생성한다는 뜻입니다
8시간 이후 해당 토큰은 만료되는 것입니다
마지막으로 signWith 메소드를 통해
JWT 토큰 속 모든 내용에
디지털 서명을 하는 것입니다
하지만 해당 sign 메소드를 이용하기 위해
여기에 생성된 비밀 key 값을 넣어야 합니다
해당 key를 넣어야 하고
마지막으로 이 compact 메소드를 호출해야 합니다
이렇게 하면 JWT 토큰이 생성되어
헤더를 설정하여
해당 토큰을 응답에 보냅니다
이 헤더의 이름은
SecurityConstants 인터페이스에서 왔고
여기서 헤더의 이름은 인가입니다
동일한 헤더 이름을
setExposedHeaders 안에 구성하여
브라우저가 이 헤더 값을 수락하도록 해야 합니다
토큰 생성 논리 이외에도
여기에 더 강조하고 싶은 메소드가 하나 더 있는데
그것은 shouldNotFilter입니다
이전에 OncePerRequestFilter를 살펴보며
해당 shouldNotFilter를 언급한 적이 있습니다
이 shouldNotFilter의 목적은
만약 이 메소드에 조건을 제공한다면
그 조건에 따라 필터를 실행하지 않도록 하는 것입니다
여기서 요구되는 바는
해당 JWT 토큰 생성 필터는
오로지 로그인 과정 중에만 실행되어야하는 것입니다
후속 요청에서 토큰이
계속해서 생성되는 것을 막기 위함입니다
이는 로그인 과정중에만 일어나게 하고 싶은 것입니다
그리고 Angular 애플리케이션의
로그인 과정 중에 호출할 작업이
무엇인지 아십니까?
이는 /user입니다
따라서 하고 싶은 말은
현재 요청 경로가 /user라면
해당 값은 true가 될 것이고
그렇지 않으면 false가 될 것입니다
그리고 여기에 기록된 false Blooean (블리언) 값에 대해서는
Spring Security는 해당 필터가
/user 요청 경로에서 실행되도록 할 것입니다
그렇다면 다른 요청들은 모두
여기 적힌 이 메소드의 값이 true가 될 것입니다
그 뜻은 즉슨 이 필터가 다른 요청들에 대해
실행되지 않길 바란다는 것입니다
해당 내용이 명확히 전달되었으면 좋겠습니다
그리고 이 필터를 만들고난 후
이 필터를 Spring Security FilterChain에
구성하거나 주입해야 합니다
이 체인의 경우
ProjectSecurityConfig 클래스로 들어가
addFilterAfter라는
메소드를 호출할 것입니다
해당 메소드를 호출하고 싶은 이유는
성공적인 승인 이후로 필터가
실행 되도록 하기 위함입니다
이 메소드에 addFilterAfter을 추가하는 이유입니다
아시다시피 자체의 커스텀 필터의
객체를 전달해야 하는데
이는 JWTTokenGeneratorFilter입니다
두번째로 전달해야하는 값은
BasicAuthenticationFilter.class입니다
이것을 통해 웹 애플리케이션 내에
인증이 성공적으로 이루어질 때마다
우리가 생성한 커스텀 필터의 도움으로
JWT 토큰이 생성됩니다
이러한 변화로 인해
JWT 토큰 생성까지는 어렵지 않을 것입니다
다음 강의에서는 필터를 하나 더 정의하고자 합니다
이것은 로그인 작업 이외의
모든 REST API 호출에 대해 UI 애플리케이션에서 받은
JWT 토큰을 검증하는 역할을 담당합니다
해당 내용이 명확히 전달되었기를 바랍니다
그럼 다음 강의에서 뵙겠습니다 감사합니다