인가 엔드 포인트를 개발하기 앞서 자세한 공부가 필요했으며 Spring Authorization Server의 코드를 분석하기 위해 큰 흐름을 다시 알 필요가 있었다.
- 다음 그림은 인가 엔드포인트의 순서대로 그림을 그린 것이다.
- 이 그림을 토대로 Spring Authorization Server가 진행이 될 것이다.
- 한 순서를 하나씩 Spring Authorization Server 코드를 분석해서 순차적으로 개발을 진행하면 될 것 같다.
- 인가 엔드포인트는 기밀 클라이언트든 공개 클라이언트든 흐름은 같다. 다른 구간이 Token Endpoint에서 갈린다.

항목 분석
1. 로그인 버튼 클릭
- 리소스 소유자는 클라이언트(Spring Boot + Thymeleaf / React / JSP 뭐든 가능) 서버가 제공한 것

보통은 다음과 같은 코드가 있는 것
<a href="https://auth.example.com/oauth2/authorize
?response_type=code
&client_id=test-client
&redirect_uri=https://client.example.com/callback
&scope=read
&state=xyz">
로그인
</a>
또는 JS
window.location.href =
"<https://auth.example.com/oauth2/authorize?response_type=code&>...";
2. 클라이언트 서버가 브라우저에게 주는 응답
- 클라이언트가 브라우저에게 응답(302)을 줌 → 즉, 브라우저에게 여기로 가라 이런 말

3. 브라우저의 GET or POST
- 사용자가 보는 시점 → https://auth.example.com/oauth2/authorize
- 사용자는 인지적으로 아 로그인하려고 하니까 auth.example.com 으로 이동했구나
- 브라우저는 사용자 몰래 자동으로 HTTP 요청을 보냄
여기서 GET 방식이냐, POST 방식을 좀 더 본다면
1. GET 방식
- 이 경우:
- 주소창에 쿼리 스트링이 그대로 보임
- 대부분의 OAuth 로그인 버튼은 이 방식
GET /oauth2/authorize
?response_type=code
&client_id=test-client
&redirect_uri=https://client.example.com/callback
&scope=read
&state=xyz
Host: auth.example.com
2. POST 방식 경우 (HTML form)
<form method="post" action="https://auth.example.com/oauth2/authorize">
<input name="response_type" value="code" />
<input name="client_id" value="test-client" />
</form>
브라우저가 실제로 보내는 요청
POST /oauth2/authorize
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded
response_type=code&client_id=test-client&...
즉 이 2개로 나뉘는 거는 클라이언트가 브랑줘를 어떻게 이동시키느냐에 따라 다른 거임
GET 방식
<a href="https://auth.example.com/oauth2/authorize?response_type=code&client_id=...">
로그인
</a>
POST 방식
<form method="post" action="https://auth.example.com/oauth2/authorize">
<input type="hidden" name="response_type" value="code" />
<input type="hidden" name="client_id" value="test-client" />
<button type="submit">로그인</button>
</form>

4. 로그인 페이지(응답) - 브라우저와 인가 서버 통신
- 인가 서버는 로그인 요청이 온 사용자 로그인 세션이 없으면 브라우저에게 /login으로 302 리다이렉트를 보내줌
- 이걸 하는 이유는 인가 서버가 자신의 로그인 페이지로 리다이렉션 시키려고 하는 거임

5. 브라우저의 이동(요청) - 브라우저와 인가 서버 통신
- 브라우저는 auth.example.com 인가 서버에게 /login 페이지를 달라고 하는 거

6. 인가 서버(응답) - 브라우저와 인가 서버 통신
- 인가 서버는 브라우저가 /login 요청을 보내면 로그인 HTML 페이지를 응답으로 내려준다.

7. 로그인 페이지 보임 - 리소스 소유자 && 브라우저
- 브라우저는 사용자가 보고 있는 화면을 → https://auth.example.com/login으로 바꾸고 HTMl을 렌더링 이것이 로그인 페이지

8. 사용자가 로그인 폼에 입력 후 버튼 클릭 - 리소스 소유자 && 브라우저
- 사용자가 자신의 비번 && 아이디를 입력 후 버튼 클릭

9. 로그인 요청 - 브라우저와 인가 서버 통신
- 사용자가 입력한 비번 && 아이디를 브라우저가 인가 서버의 /login 엔드포인트로 POST 요청을 보냄

10. 로그인 성공 후 인가 서버 응답 - 브라우저와 인가 서버 통신
- 인가 서버는 HTTP 세션을 생성 및 세션에 사용자 인증 정보를 저장해놓음
- 이 세션을 식별하기 위해
Set-Cookie: JSESSIONID=3A9F1E2C8A1B9D...; HttpOnly
- 이 세션은 인가 서버(auth.example.com)의 세션
- 클라이언트 서버와는 전혀 공유되지 않음
- 위가 브라우저로 가는 거임 → 인가 서버는 “야 브라우저 앞으로 auth.example.com에 요청할 때 이 JESSIONID 쿠키를 같이 보내라” 라는 말
- 그러면 브라우저는 이걸 보고 쿠키 저장소에 저장함 또한 HttpOnly라서 브라우저는 JS로 접근이 불가능
또한, 원래 가려던 곳을 기억하려고 하는데
사용자는 원래
GET /oauth2/authorize?response_type=code&client_id=...
이걸 하려다가 로그인이 안 되어 있어서 /login으로 튕김
- 인가 서버는 이 원래 요청을 세션에 저장해둠 즉, 처음 들어왔을 때 세션은 이미 만들어진 상태임 그래서 인가 서버가 어딘가에 처음에 이걸 저장해놓은 거임
- 사용자가 처음부터 인가 서버는 세션을 저장해놓음
Location: /oauth2/authorize?response_type=code&client_id=...
그러면 인가 서버는 응답할 때 쿠키 값을 보내주어 사용자를 인식함 → 보내주면 이 세션 없어지고 쿠키 값으로 사용자 인식함
ex → (Spring Security의 RequestCache 개념)
SavedRequest:
/oauth2/authorize?response_type=code&client_id=...
이후에는 인가 서버는 이 사용자는 로그인 성공했고 원래 /oauth2/authorize를 하려던 사람이네? 라고 생각함 그래서 응답이
HTTP/1.1 302 Found
Location: /oauth2/authorize?response_type=code&client_id=...

11. 브라우저가 자동으로 이동(요청) - 브라우저와 인가 서버 통신
- 동의를 아직 안 했다고 생각하고 진행
이 시점에서 인가 서버는 이미 알고 있음
- 사용자 로그인됨 (세션 있음)
- client_id 검증 가능
- redirect_uri 검증 가능
- 사용자 동의(consent) 아직 안 함
브라우저한테 다시 요청을 받는데 로그인 전 요청과 동일한 요청이지만 차이가 딱 하나
이번엔 JSESSIONID 쿠키가 있음 → 로그인 사용자 식별 가능
인가 서버안에서
- client_id 유효한가?
- redirect_uri 등록된 값인가?
- response_type=code 맞나?
- scope 유효한가?
- 이 사용자 + 이 클라이언트 + 이 scope에 대해 동의가 이미 되어 있나? → NO
를 판단
즉, “아직 이 사용자가 test-client가 read scope로 접근하는 걸 허용했는지 모른다” → 그래서 인가 코드 발급 안 하고 동의 화면 보여주러 감

12. 동의 화면으로 리다이렉트 - 브라우저와 인가 서버 통신
인가 서버는 이제 동의 화면을 브라우저한테 전달

13. 브라우저가 동의 페이지 요청 - 브라우저와 인가 서버 통신
브라우저는
- 브라우저가 이 응답을 받음
- 302 + Location 헤더 확인

14. 인가 서버 동의 HTML (응답) - 브라우저와 인가 서버 통신
- 인가 서버는 동의 화면을 응답해줌
동의 화면은 다음과 같이
- 커스텀 HTML일 수도 있고
- Spring Authorization Server가 기본으로 생성해주는 화면일 수도 있음.

15, 16. 동의 화면 보임 및 클릭 - 브라우저 && 리소스 소유자
- 브라우저는 응답을 받아 리소스 소유자한테 동의 화면을 보여줌
- 리소스 소유자는 동의 버튼을 클릭

17. 동의 화면 보임 및 클릭 - 브라우저와 인가 서버 통신
- 브라우저는 HTML을 통해 POST 요청을 함 이때 response_type은 없음

18. 인가 코드 발급! - 브라우저와 인가 서버 통신
- 동의가 진행되었으면 인가 서버는
- 클라이언트가 요청한 scope
- 클라이언트에 등록된(scope whitelist)
- 사용자가 동의한 scope
이 3가지를 교집합을 계산해서 그 결과만 인가 코드에 묶는다.
단계별로 정확히 풀어보자
클라이언트 요청
scope=read write
클라이언트 등록 정보(DB)
allowed_scopes = [read, profile]
사용자 동의 화면에서 체크한 것
approved_scopes = [read]
등록이 안 된 거 있으면 → invalid_scope 에러
인가 서버는
인가 서버는 다음 정보를 Authorization Code와 함께 저장
- client_id
- user_id
- redirect_uri
- approved scopes
- 만료 시간
- (PKCE면 code_challenge 등)
authorization_code = SpIxIOBeZQQYbYS6WxSbIA
scopes = [read]
그 후 브라우저 응답

19. 브라우저가 클라이언트에게 인가 코드 전송 - 브라우저와 클라이언트
기밀 클라이언트의 경우
브라우저 ──GET /callback──▶ 클라이언트 서버
- 위를 서버가 직접 받아서 서부 내부에서 code 추출, state 검증(CSRF 방어)
공개 클라이언트 경우
- SPA (React, Vue)
- 모바일 앱
- 네이티브 앱
브라우저 주소창:
<https://client.example.com/callback?code=...&state=>...
위를 브라우저(JS)가 처리
JS 코드
const code = new URLSearchParams(location.search).get("code");

'RFC > OAuth 2.0' 카테고리의 다른 글
| [OAuth 2.0] Spring Security 기본 설정(FilterChain + formLogin) (0) | 2026.02.04 |
|---|---|
| [OAuth 2.0] Spring Authorization Server 코드 분석(OAuth2AuthorizationEndpointFilter 클래스) (0) | 2026.02.04 |
| [OAuth 2.0] Spring Security (0) | 2026.02.03 |
| [OAuth 2.0] 개발 프로젝트 (0) | 2026.02.03 |
| [OAuth 2.0] 10. Security Considerations(보안 고려 사항) (0) | 2026.01.20 |