<총학생회 웹사이트 개발 기록>
목차
1. 기술스택 & 패키지 구조
2. AWS EC2, RDS, S3 구축
3. JWT 이용한 Google Login System
4. 암호화 & 복호화
5. SSL 적용과 Nginx reverse proxy server 구축
6. SWAP 메모리 설정
OAuth 2.0(Open Authorization 2.0, OAuth2)은 인증을 위한 개방형 표준 프로토콜입니다.
이 프로토콜에서는 Third-Party 프로그램에게 리소스 소유자를 대신하여 리소스 서버에서 제공하는 자원에 대한 접근 권한을 위임하는 방식을 제공합니다.
구글, 페이스북, 카카오, 네이버 등에서 제공하는 간편 로그인 기능도 OAuth2 프로토콜 기반의 사용자 인증 기능을 제공하고 있으며, MDS인텔리전스의 IoT 플랫폼인 NeoIDM(네오아이디엠) 또한 OAuth2.0 인증 프레임워크를 기반으로 하여 사용자 인증을 수행합니다.
[출처] OAuth 2.0 동작 방식의 이해|작성자 MDS인텔리전스
위 사진에서 User (Resource Onwer)는 로그인 하는 유저, Client (Application)는 총학생회 웹사이트, Authorization Server는 Google, Kakao, Naver, etc. Resource Server는 RESTfulAPI 서버가 된다.
총학생회 웹사이트는 학교 이메일을 통한 로그인한 허용하기로 했기 때문에 Google OAuth2.0을 사용하기로 했다.
Google OAuth 2.0와 JWT를 사용한 로그인 시스템은 아래와 같다.
1. 웹사이트에서 구글 로그인
2. Google Authorization Server에서 Authorization Code 발급
3-1. Server에 Authorization Code 전달하며 Login 요청
3-2. Google Resource Server에 access token 요청
4. Google Resource Server 에서 Token 발급
5. access token으로 구글 계정 정보 요청 ( Name, Email 등)
6. 구글 계정 정보제공
7. DB에서 유저정보조회
8. 유저의 정보 및 권한 확인
9. 유저 정보를 담고있는 JWT 발급 후 클라이언트로 Response 보내줌
** Client에서는 JWT를 API Authorization token으로 활용 (Bearer token) **
이 글에서는 3~9까지의 과정을 처리하는 Spring Boot 설정 및 로직 구현을 소개하려고한다.
application.properties에 있는 Oauth provider 관련 정보가져옴
- > @ConfigurationProperties Annotation 사용!!
* 다른 Oauth Provider를 사용하는 경우를 대비하여 확장성 있도록 설계하였다
Oauth 관련 Configuration
OauthProperties에 있는 ConfigurationProperties로 받아온
OauthProviders의 이름을 Key, 정보를 Value로 Map에 담아주고, new ApiRequester 객체와 함께
OauthHandler 생성자로 보내준다.
OauthHandler는 OauthProvider 정보를 담은 Map과 Client에서 전달해준
Authorization Token으로 Access Token을 요청하는 로직을 수행한다.
getUserInfoFromCode : Authorization code와 oauthProvider 정보를 사용하여 Access Token을 발급받는다. apiRequester.getUserInfo()로 받은 계정 정보를 Map에 담아놓고, OauthAttributes.extract()로 추출해준다
(Google Oauth2.0만 사용할거면 바로 OauthUserInfo에 넣어주도록 설계하면된다)
getOauthProvider : provider의 이름을 전달받아 map의 key값으로 넣어주어 해당 provider의 정보를 가져온다.
Map.get("key") 사용
OauthProvider 정보로 Access Token, 계정정보를 호출하는 객체
토큰 생성, 토큰 파서, 토큰 유효성 검사 등 토큰 관련 로직을 담고 있는 객체
이외에도 토큰 파서 생성, 유효성 검사 로직이 구현되어있다.
path variable로 Oauth Provider 이름, Request Param으로 Authorization 토큰을 받아서 login 로직을 수행.
정상적으로 토큰 발급까지 된다면 이를 loginResponse로 TokenResponse 객체로 생성하여 Response 보내준다.
로직 설명:
getStudentInfo() -> oauthHandler.getUserInfoFromCode()
-> apiRequester.getUserInfo()
-> ApiRequester.getToken()// Access Token 발급
-> ApiRequester.getUserInfoByToken() // Access Token으로 구글 계정정보요청
-> OauthAttributes.extract // Google-Access Token 추출
-> DataEncryption.encrypt() -> 개인정보 암호화
-> DB에 저장된 유저정보 조회 + 권한에 따라 JWT 토큰 생성 및 발급
이외에도 중간중간에 생량된 부분이 많은데 해당 부분은 Github에서 확인할 수 있습니다.
이렇게 발급된 토큰과 Interceptor, Argument Resolver를 통해 로그인 시스템을 완성했다.
API 별로 호출자의 권한을 확인하고 특정 권한만 호출할 수 있도록 기능을 구현해야했다.
http header에 Authorization 토큰을 받고, 이를 유저 정보를 추출하여 권한을 확인 하는 작업을 Controller에 있는 모든
API에서 반복되는 것을 방지할 수 있는 것이 Argument Resolver이다.
어떠한 요청이 컨트롤러에 들어왔을 때, 요청에 들어온 값으로부터 원하는 객체를 만들어내는 일을 ArgumentResolver이 간접적으로 해줄 수 있다.
예를 들어, 어떤 사용자가 로그인 되어 있다고 가정하자.
사용자가 자신의 정보를 조회하거나 수정하는 것과 같은 민감한 요청을 하는 경우, 우리는 이 사용자가 올바른 사용자인지 확인을 해야 한다.
뿐만 아니라, 사용자가 가진 토큰이 유효한 토큰인지 검증을 거친 후에 토큰에 저장된 id를 꺼내 LoginUser라는 객체로 만들어내는 과정이 필요하다.
ArgumentResolver는 HandlerMethodArgumentResolver를 구현함으로써 시작된다.
Spring에서 설명하는 HandlerMethodArgumentResolver는 다음과 같다.
Strategy interface for resolving method parameters into argument values in the context of a given.
Spring에서는 ArgumentResolver를 하나의 전략 인터페이스로 설명하고 있다. 인터페이스는 아래 두 메서드를 구현하도록 명시하고 있다.
boolean supportsParameter(MethodParameter parameter);
@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;를 입력하세요
이번 프로젝트 경우 권한이 크게 4가지 있다
Interceptor를 HandlerInterceptor 인터페이스를 구현하여 사용할 수 있다.
인터페이스는 preHandle, postHandle, afterCompletion을 구현하도록 명시되어 있다.
preHandle은 조건에 맞는지 boolean을 반환해 true면 실행하고 false면 실행하지 않도록 한다.
postHandle과 afterCompletion은 실행 후에 추가적으로 공통된 처리를 하고 싶을 때 사용한다.
hasAnnotation()에 있는 Annotation이 있는 method에 한해서 Interceptor가 발생한다. 로그인 권한이 필요없는 API들을 Interceptor에서 제외된다.
각각 권한에 따라 Valid한 토큰인지 확인하고 권한도 추가로 확인한다.
구현하고나니 어차피 권한은 Argument resolver에서 확안하기 때문에 굳이 안해도
됐을거같은데.. 이건 추후에 수정해야할 것 같다.
Argument Resolver를 사용하는 경우 Interceptor에서는 토큰의 유효성 검사만 진행해도 충분하다고 생각한다.