많은 서비스에서 로그인 기능을 id/password 방식도 많이 사용하지만, 구글, 페이스북, 네이버, 카카오와 같은 소셜 로그인 기능을 많이 사용한다.
로그인 기능을 직접 구현할 경우에 배보다 배꼽이 더 커지는 경우가 많다. 직접 구현하게 되면, 로그인 시 보안, 비밀번호 찾기, 비밀번호 변경, 회원정보 변경, 회원가입 시 이메일 혹은 전화번호 인증 과 같은 기능들까지 설정해줘야 하지만, 소셜 로그인으로 구현하면 그럴 필요는 없어진다.
내가 이번 프로젝트에서 OAuth만으로 구현한 이유로는 '로그인 시 보안'의 원인이 크다. 보안에서 특히 부족한 게 많기 때문에, 1인 개발로 신경써서 운영하기는 힘들 것 같다는 판단이 들었다. 안 그래도 큰 기업에서도 해킹도 빈번하게 일어나는 소식을 접하다 보니, 더욱 조심해야겠다는 생각이 든다.
편의성 면에서도 사용자가 해당 소셜 아이디만 관리하면 되다 보니, 더 편하다는 면이 있다.
나의 경우에도 '로그인을 한다 -> 비번을 틀린다 -> 몇 번 틀린다 -> 비밀번호 찾기를 진행한다 -> 휴대폰 인증 -> 로봇이 아님 인증 -> 새로운 비밀번호 입력 -> 기존 비밀번호는 안 된다는 알림을 받는다/특수문자나 대문자를 포함하거나 10자 이상 입력하라는 알림을 받는다' 같은 굴레로 인해서 스트레스를 받는 일이 비일비재했다. 특히 저 특수문자/대문자 포함이나 글자 수와 같은 제한 때문에 수 개의 비밀번호를 기억해야하고, 매번 어떤 게 맞는 지 몇 번이나 시도해봐야하는 불편함을 겪고 있다. 이는 누구나 공감하지 않을까?
쌓인 울분이 많았기에, 서론도 길어졌다. 이제 본론으로 들어가보자.
OAuth는 인터넷 사용자들이 비밀번호를 제공하지 않고 다른 웹 사이트 상의 자신들의 정보에 대해 웹 사이트나 애플리케이션의 접근 권한을 부여할 수 있는 공통적인 수단으로서 사용되는 접근 위임을 위한 개방형 표준이다.
쉽게 말해서, 애플리케이션을 이용할 때 사용자가 해당 애플리케이션에 ID, PW 등의 정보를 제공하지 않고 신뢰할 수 있는 외부 어플리케이션(Naver, Kakao, Google, Facebook 등)의 Open API에 ID, PW를 입력하여 해당 애플리케이션이 인증 과정을 처리해주는 방식이다.
앞서 언급했듯이, 내가 굳이 OAuth2만으로 로그인 하도록 선택한 이유는 "보안"과 "사용자 경험(UX)" 모두 잡기 위해서다.
oauthId라는 식별자만 저장할 뿐이다.Spring Security는 막강한 인증(Authentication)과 인가(Authorization) 기능을 가진 프레임워크이다.
만약 Spring Security가 없었다면, 개발자가 직접 if (user == null) 검사, 비밀번호 암호화, 세션 관리를 모든 컨트롤러 메소드마다 해야 했다. Spring Security는 이 모든 번거롭고 위험한 보안 처리를 대신해준다.
JWT는 세션(Session)을 사용하지 않는 STATELESS(무상태) 서버 환경에서, 사용자를 인증하고 권한을 부여하기 위한 표준 규격이다.
JWT의 구조는 xxxxx.yyyyy.zzzzz와 같이 마침표로 구분되는 세 부분으로 구성되어 있다.
xxxxx부분이며, 토큰의 타입(JWT)과 어떤 암호화 알고리즘(예 : HS512)을 사용했는지에 대한 정보가 담겨있다.yyyyy부분이며, 토큰에 실질적으로 담고 싶은 데이터(정보)가 들어가는 부분이다. 이 데이터를 '클레임(Claim)'이라고 부른다. 참고로 Payload는 Base64로 인코딩되어 있을 뿐이고 암호화된 것이 아니어서 누구나 디코딩해서 내용을 볼 수 있기 때문에, 비밀번호 같은 민감 정보는 절대로 넣으면 안 된다.zzzzz부분이며, 이 토큰이 위조되지 않았음을 증명하는 전자 서명이다. Header와 Payload를 합친 뒤, application-oauth.properties에 저장된 jwt.secret-key(비밀 키)를 사용해 암호화한다. 이 비밀키를 아는 것은 오직 우리 서버 뿐이다.JWT가 STATELESS(무상태)인 이유는 다음과 같다.
기존 세션 방식 :
1. 사용자 로그인 -> 서버가 "로그인 성공" 기록을 서버 메모리(세션 저장소)에 저장한다.
JWT 방식 :
1. 사용자 로그인 -> 서버가 userId : xx 정보가 담긴 JWT를 발급한다. (서버는 아무것도 저장하지 않는다)
userId : xx임을 확인한다. (서버가 상태를 저장 안 함 = Stateless)JWT는 서버의 메모리나 DB를 매번 확인할 필요가 없이, 토큰 자체에 사용자의 정보와 유효성을 모두 담고 있는 "티켓"이다. 이 덕분에 여러 대의 서버로 확장하기에도 유리하다.