아래 글 내용들을 정리한 것, 원문이 더 자세함
https://gyoogle.dev/blog/web-knowledge/OAuth.html
등장배경
기존에는 A 사이트(ex. 개인 프로젝트 사이트)에서 B 사이트(ex. 구글)의 정보를 조회하려고 하면, B 사이트의 ID/PW를 조회해야 했었다.
- ex. 네이버 계정의 이름, 성별, 생년월일로 계정 가입하기
이 방식은 보안에 취약할 수 밖에 없음. A 사이트에서 ID/PW를 해킹 당하면 B 사이트의 계정까지 다 뚫리는 것 ..
그래서 ID/PW를 직접 건네주지 않고도 Open API를 활용해서 B 사이트의 정보를 A 사이트에서 볼 수 있게 하는 것이 OAuth이다. (Open Authorization)
이전에는 각 사이트마다 이런 Auth API를 따로 만들었지만 그렇게 되면 사이트 마다 개별적으로 개발하고 유지보수해야하므로, 공통의 규격을 만든 것이 OAuth이다.
정의
인터넷 사용자들이 비밀번호를 제공하지 않고 다른 웹사이트 상의 자신들의 정보에 대해 웹사이트나 애플리케이션의 접근 권한을 부여할 수 있는 공통적인 수단으로서 사용되는, 접근 위임을 위한 개방형 표준
PW가 아니라 접근 권한 자체를 A 사이트에 넘겨줌으로써 A 사이트에서도 B 사이트의 리소스를 볼 수 있다.
용어
- Resource Owner: B 사이트에 리소스(캘린더, 이름 등등)를 가지고 있는 user (나)
- Authorization & Resource Server
- Authorization Server : Resource Owner 인지 인증하는 서버/ Client(A 사이트)에게 Access Token 발급
- Resource Server : 리소스를 가지고 있는 서버
- 두 서버는 합쳐질 수도 있고 분리될 수도 있음
- Client: user X, user에게 B 사이트에 관한 접근 권한을 위임 받는 A 사이트
Redirect URI
OAuth를 사용하기 전에 Client(A 사이트)를 Resource Server에 등록하면서 Redirect URI(사용자가 인증을 마치고 돌아갈 페이지)도 등록한다.
사전에 인증된 URL로만 리디렉션 시켜야 한다. 안 그러면 보안상 위험 ..
+) 기본적으로는 https만 허용하나, localhost는 예외적으로 허용
등록 후에는 Client ID, Client Secret을 얻는다. (A 사이트가 쓸 거)
OAuth 인증 과정
맨 위 참고 링크에 있는 사진이 매우 유용함
1~2 로그인 요청
애플리케이션을 resource server에 등록하고 받은 Client ID와 등등을 보내서 Authorization Server에 로그인 요청을 한다.
+) 이때 Scope는 Client(A사이트)가 user resource에 접근할 수 있는 범위를 설정함, ex. 카카오의 이메일 성명 성별 등
3~4 로그인
B 사이트의 로그인 페이지가 주어지고, 유저는 거기서 로그인을 하면 된다.
계정 연동할 때 흔히 하는 과정들을 생각해보면 될 듯
5~6 로그인 완료, Authorization Code 발급
로그인이 완료되면, 해당 user에게 Authorization Code를 발급한다.
user는 Authorization code와 함께 Client 단으로 redirect 된다.
7~8 Access Token
redirect 되면서 받은 Authorization Code, Client ID, Client Secret으로 Access Token을 요청하고, 발급 받는다.
이때 Access Token요청은 token 엔드포인트에서 이루어진다.
** 5~6에서 그냥 바로 Access Token을 발급하는 게 더 편하지 않나?
Authorization Server -> Resource Owner -> Client
굳이 Authorization Server -> Resource Onwer -> Client -> Authorization Server -> Client로 거쳐갈 필요가 있을까?
라고 생각할 수 있다.
Resource Owner -> Client로의 정보이동은 url로 밖에 할 수 없다. 그러면 보안 상 중요한 Access Token이 노출되기 쉽기 때문에,, 임시로 Authorization Code를 발급하는 것이다.
임시로 발급받는 Authorization Code + Client ID, Secret으로는 token 엔드포인트로 교환을 하기 때문에 더 안전하다.
** url 교환과 token endpoint 교환의 차이점
- url: query에 넣어 데이터 전달 ( ?type=post ... )
- API: FE <-> BE 간 통신하는 명령의 목록
- endpoint: API의 실제 주소 (ex. /user/post)
내가 실제로 짰던 코드를 가져와서 비교해보았다. (GoGreen, Flutter로 제작)
//myApp.dart
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: Firebase.initializeApp(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
...
if (snapshot.connectionState == ConnectionState.done) {
return const Home();
}
...
});
}
Firebase.initializeApp()의 정보를 가져와서,
snapshot.connectionState가 ConnectionState.done 인지 확인한다.
//myApp.dart - Home Widget
FirebaseAuth.instance.authStateChanges().listen((user) async {
if (user == null) {
FlutterNativeSplash.remove();
Get.to(() => const SignInScreen());
return;
}
Profile? profile = await findUserByToken();
if (profile == null) {
FlutterNativeSplash.remove();
Get.to(() => const SetNicknameScreen(),
arguments: SetNicknameProps(
email: FirebaseAuth.instance.currentUser!.email));
return;
}
FlutterNativeSplash.remove();
Get.off(() => const HomeScreen());
});
FirebaseAuth.instance.authStateChanges().listen(user) 로 user의 상태가 바뀔 때마다 나오는 창을 다르게 한다.
만약 user == null이면 로그인 화면으로
profile == null이면 가입은 했으나 프로필이 DB에 등록되어 있지 않은 것이므로 setNicknameScreen으로
else 고그린의 메인화면으로 이동
// sign_in_screen.dart
Padding(
padding: EdgeInsets.only(top: scaleHeight(context) * 266),
child: SignInButton(Buttons.Google, onPressed: () {
signInWithGoogle();
//Navigator.pushNamed(context, '/setnick_or_dailymeal');
})),
로그인 화면의 중앙에 있는 SignInWithGoogle 버튼을 누르면 /controller/user_repository.dart의 signInWithGoogle() 함수가 실행된다.
Future<UserCredential> signInWithGoogle() async {
/* 아마 1~4번 과정
1. user -> client로 로그인 요청
2. client -> Authorization Server로 요청 후
3. 로그인 페이지 제공
4. user -> ID/PW로 로그인
*/
final GoogleSignInAccount? googleUser = await GoogleSignIn().signIn();
// Obtain the auth details from the request
/*
5~6. user가 google에 로그인하고 받은 Authorization 코드 확인
*/
final GoogleSignInAuthentication? googleAuth =
await googleUser?.authentication;
/*
7~8. 받은 googleAuth로 GoogleAuthProvider를 통해 credential 얻어내기
이걸로 FirebaseAuth 로그인
*/
// Create a new credential
final credential = GoogleAuthProvider.credential(
accessToken: googleAuth?.accessToken,
idToken: googleAuth?.idToken,
);
return await FirebaseAuth.instance.signInWithCredential(credential);
}
많은 어려운 과정들을 FirebaseAuth로 간단하게 구현했었구나 .. 란 생각이 든다
'CS > web' 카테고리의 다른 글
CSR (Client Side Rendering) & SSR (Server Side Rendering) (0) | 2024.03.08 |
---|---|
JWT(Json Web Token) (0) | 2024.03.08 |
Web Server와 WAS (0) | 2024.03.01 |
[web] REST, REST API (0) | 2024.02.19 |
[web] 쿠키와 세션 (0) | 2024.02.19 |