작성
·
1.2K
·
수정됨
0
안녕하세요 jwt,시큐리티 / ci/cd 강의에 이어서 계속해서 수강하고 있는 학생입니다.
좋은 강의 퀄리티 덕분에 어려운 개념들을 쉽게 정리할 수 있었습니다. 정말 감사드립니다.
Spring security 서버(백엔드)가 Web page가 아닌 Mobile app과 연동하는 REST API 서버로 구현하는 방법에 대해 질문이 있습니다.
조사한 방법은 총 2가지인데요,
공통 : 회원 테이블에 provider와 provider_id칼럼이 등록되어있습니다.
1안
https://www.inflearn.com/questions/537402/react-ios-android-등-spring-boot-가-api-서버일-경우-oauth2-연동-방법
안드로이드/ios에서 oauth서버로 인증을 진행한뒤 정보를 그대로 스프링으로 던짐(액세스 토큰 포함)
스프링서버에서는 액세스 토큰을 활용해 해당 OAuth 제공자에서 제공하는 API를 사용하여 검증
provider와 provider_id가 유효할 경우에만 내부 회원 db에서 조회 후 jwt토큰 발급
카카오공식 답변에서는 액세스 토큰을 직접 보내면 안된다하더라고요
https://devtalk.kakao.com/t/oauth2/128079
그러면 결국 프론트에서 인증 진행 후 정보를 받아 provider와 providerid만을 보내야하는 이야기인데 (2안)
2안
안드로이드/ios에서 oauth서버로 인증을 진행한뒤 http에 provider와 provider_id만 담아서 스프링 서버로 전송
스프링 서버에서는 provider와 provider_id만을 가지고 내부 회원 db에서 조회 후 jwt토큰 발급
2안의 경우 proivder와 provider_id를 무차별 조합해서 뚫는게 가능하지 않을까 싶어요
질문은 다음과 같습니다.
oauth와 jwt를 활용해서 서버를 구축할 때 2안으로 구현하는게 맞는지 (서버에 provider와 provider_id만 보내기)
1-1. 만약 아니라면 어떤 방식으로 구현해야하는지
2안이 맞다면 provider와 provider_id를 무차별적으로 조합해서 보내면 쉽게 뚫리는 것이 아닐지
액세스 토큰을 직접 보내 서버에서 검증하게 될 경우(1안)은 왜 위험한지가 궁금합니다.
답변 1
1
1안
https://www.inflearn.com/questions/537402/react-ios-android-등-spring-boot-가-api-서버일-경우-oauth2-연동-방법
안드로이드/ios에서 oauth서버로 인증을 진행한뒤 정보를 그대로 스프링으로 던짐(액세스 토큰 포함)
스프링서버에서는 액세스 토큰을 활용해 해당 OAuth 제공자에서 제공하는 API를 사용하여 검증
provider와 provider_id가 유효할 경우에만 내부 회원 db에서 조회 후 jwt토큰 발급
1안은 크리덴셜 방식이네요!!
앱에서 카카오/네이버로 인증진행
앱은 회원프로필정보와 토큰을 받음
(첫째)앱은 스프링에게 본인의 회원프로필 정보를 준다. (신뢰할 수 없자나요!!)
(둘째)앱은 스프링에게 본인의 엑세스 토큰을 준다. (이것도 아직은 신뢰할 수 없어요)
HTTPS 서버로 구축된 상태에서 스프링은 엑세스토큰을 카카오/네이버에게 다시 전달해서 해당 토큰이 유효한지 확인한다. (이때 프로필 정보 받으면 되요)
프로필 정보 받으면, 회원가입을 하든, 로그인을 해서 JWT 토큰 만들고 앱에게 전달한다.
이제부터 앱은 스프링과 통신한다. JWT 토큰으로!!
이렇게 하시면 되요 ㅎ
다른 방식으로는 코드 방식이 있어요!! 그것도 궁금하시면 질문 따로 남겨주세요!
3안
안드로이드에서 카카오로 username, password를 날린다.
카카오는 302로 Location 헤더에 callback주소를 응답해준다.
카카오는 쿼리스트링으로 ?code=임시코드값 을 응답해준다.
안드로이드는 header에 Location 헤더를 열어서 해당 주소로 다시 request요청한다.
안드로이드는 code값을 스프링 서버로 request 요청한다.
이때 코드값은 카카오가 클라이언트쪽으로 임시로 보내주는 임시 티켓이다.
해당 코드로는 회원정보를 받을 수 없다. 해당 코드를 그냥 스프링 서버에 던지고, 스프링 서버는 해당 코드가 유효한지를 판단하는 것이다.
해당 주소가 스프링서버의 /callback 이라고 가정한다면!!
스프링서버는 /callback으로 code를 받는다.
해당 코드를 받은 스프링서버는 code검증을 위해 카카오에 code를 던진다.
카카오는 code를 받고, 유효하면 accessToken, refreshToken을 응답해준다.
스프링은 해당 accessToken을 받으면 회원정보를 요청해서 받는다.
회원정보를 받으면 이제 카카오에게 받은 accessToken을 쓸모 없으니 버려도 된다.
refreshToken도 버려도 된다.
이제 스프링서버는 본인 서버의 JWT토큰을 대칭키로 만든다.(생성과 검증을 스프링 서버만 하기 때문에 RSA 로 만들필요가 없다)
JWT토큰 == AccessToken이다.
RefreshToken도 같이 만든다.
AccessTOken은 header의 Authorization에 담고
RefreshToken은 header의 cookie에 안전하게 담아서 안드로이드에게 응답한다.
그리고 accesstoken과 refreshtoken을 디비에 저장해둔다. 이때 디바이스 정보도 같이 저장해주면 좋다. 왜냐하면 여러 디바이스로 동시에 로그인할 수 있기 때문에 디바이스가 다르면 동시 로그인을 허용해야 될 수 있기 때문이다.(핸드폰, 갤럭시탭, 맥북, 데스크탑)
하지만 동일한 디바이스가 아닌 경우에는 무조건 사용자에게 메일을 보내서 새로운 디바이스에서 로그인이 있었다고 알려줘야하고, 만약 이것이 잘못되었으면 알려주세요 라는 이메일 링크같은 것을 만들어줘야 한다.
사용자가 이것을 스프링 서버에게 보내면 저장된 RefreshToken, AccessToken을 디비에서 그냥 날려버리고, 모든 디바이스에서 다시 로그인할 수있게 해야한다.
이때 AccessToken을 들고 있는 탈취자는 잠깐 동안은 서버에 접근할 수 있기 때문에 AccessToken의 시간은 짧아야 한다.
안드로이드는 해당 AccessToken을 안전한 저장소에 보관하다가 인증이 필요한 자원을 요청할때마다 header의 Authorization에 담아서 요청한다.
어느 특정시간이 지나면 AccessToken이 만료될 수 있다.
그래서 안드로이드는 AccessToken을 Base64로 디코딩해서 만료시간을 체크하고 만료가 되지 않았을때만 요청하고, 만약 만료가 되었다면 refreshToken을 스프링서버로 요청한다. 이때 refreshToken은 cookie로 전송한다. 안전하게!!
스프링서버는 cookie에 refreshToken이 있으면 AccessToken을 새로 만들고, DB에 저장한뒤 사용자에게 응답한다. RefreshToken은 건드리지 않는다. 해당 토큰은 메일을 보냈는데 본인이 로그인 안했다는 요청이 오면, 날려버리면 된다.
너무 길었네요 ㅎ
안녕하세요 좋은 답변 감사드립니다!
혹시 코드방식에 대해서도 알려주실 수 있나요??