Skip to content

Spring Security ‐ OAuth 2.0 oauth2Login

dnwls16071 edited this page Aug 31, 2025 · 6 revisions

📚 OAuth2LoginConfigurer 초기화 이해

스크린샷 2025-08-31 오후 3 00 33 스크린샷 2025-08-31 오후 3 00 48 스크린샷 2025-08-31 오후 3 01 00
  • SecurityFilterChain 타입의 빈을 생성해서 보안 필터를 구성한다.
  • HttpSecurity에 있는 oauth2Login()과 oauth2Client() API를 정의하고 빌드한다.

📚 OAuth2 로그인 페이지

  • 기본적으로 OAuth2 Login 페이지는 DefaultLoginPageGeneratingFilter가 자동으로 생성해준다.
  • 이 디폴트 페이지는 OAuth 2.0 클라이언트명을 보여준다.
  • 링크를 누르면 인가 요청을 시작할 수 있게 된다.
  • 요청 매핑 URL : /oauth2/authorization/{registrationId}
  • 디폴트 로그인 페이지를 재정의하려면 oauth2Login().loginPage()를 재정의하면 된다.

DefaultLoginPageGeneratingFilter

public class DefaultLoginPageGeneratingFilter extends GenericFilterBean {
    public static final String DEFAULT_LOGIN_PAGE_URL = "/login";
    public static final String ERROR_PARAMETER_NAME = "error";
    private String loginPageUrl;
    private String logoutSuccessUrl;
    private String failureUrl;
    private boolean formLoginEnabled;
    private boolean oauth2LoginEnabled;
    private boolean saml2LoginEnabled;
    private boolean passkeysEnabled;
    private boolean oneTimeTokenEnabled;
    private String authenticationUrl;
    private String generateOneTimeTokenUrl;
    private String usernameParameter;
    private String passwordParameter;
    private String rememberMeParameter;
    private Map<String, String> oauth2AuthenticationUrlToClientName;
    private Map<String, String> saml2AuthenticationUrlToProviderName;
    private Function<HttpServletRequest, Map<String, String>> resolveHiddenInputs = (request) -> {
        return Collections.emptyMap();
    };
    private Function<HttpServletRequest, Map<String, String>> resolveHeaders = (request) -> {
        return Collections.emptyMap();
    };
    private static final String CSRF_HEADERS = "{\"{{headerName}}\" : \"{{headerValue}}\"}";
    private static final String PASSKEY_SCRIPT_TEMPLATE = "\t<script type=\"text/javascript\" src=\"{{contextPath}}/login/webauthn.js\"></script>\n\t<script type=\"text/javascript\">\n\t<!--\n\t\tdocument.addEventListener(\"DOMContentLoaded\",() => setupLogin({{csrfHeaders}}, \"{{contextPath}}\", document.getElementById('passkey-signin')));\n\n\t//-->\n\t</script>\n";
    private static final String PASSKEY_FORM_TEMPLATE = "<div class=\"login-form\">\n<h2>Login with Passkeys</h2>\n<button id=\"passkey-signin\" type=\"submit\" class=\"primary\">Sign in with a passkey</button>\n</div>\n";
    private static final String LOGIN_PAGE_TEMPLATE = "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n    <meta name=\"description\" content=\"\">\n    <meta name=\"author\" content=\"\">\n    <title>Please sign in</title>\n    <link href=\"{{contextPath}}/default-ui.css\" rel=\"stylesheet\" />{{javaScript}}\n  </head>\n  <body>\n    <div class=\"content\">\n{{formLogin}}\n{{oneTimeTokenLogin}}{{passkeyLogin}}\n{{oauth2Login}}\n{{saml2Login}}\n    </div>\n  </body>\n</html>";
    private static final String LOGIN_FORM_TEMPLATE = "      <form class=\"login-form\" method=\"post\" action=\"{{loginUrl}}\">\n        <h2>Please sign in</h2>\n{{errorMessage}}{{logoutMessage}}\n        <p>\n          <label for=\"username\" class=\"screenreader\">Username</label>\n          <input type=\"text\" id=\"username\" name=\"{{usernameParameter}}\" placeholder=\"Username\" required autofocus>\n        </p>\n        <p>\n          <label for=\"password\" class=\"screenreader\">Password</label>\n          <input type=\"password\" id=\"password\" name=\"{{passwordParameter}}\" placeholder=\"Password\" {{autocomplete}}required>\n        </p>\n{{rememberMeInput}}\n{{hiddenInputs}}\n        <button type=\"submit\" class=\"primary\">Sign in</button>\n      </form>";
    private static final String HIDDEN_HTML_INPUT_TEMPLATE = "<input name=\"{{name}}\" type=\"hidden\" value=\"{{value}}\" />\n";
    private static final String ALERT_TEMPLATE = "<div class=\"alert alert-danger\" role=\"alert\">{{message}}</div>";
    private static final String OAUTH2_LOGIN_TEMPLATE = "<h2>Login with OAuth 2.0</h2>\n{{errorMessage}}{{logoutMessage}}\n<table class=\"table table-striped\">\n  {{oauth2Rows}}\n</table>";
    private static final String OAUTH2_ROW_TEMPLATE = "<tr><td><a href=\"{{url}}\">{{clientName}}</a></td></tr>";
    private static final String SAML_LOGIN_TEMPLATE = "<h2>Login with SAML 2.0</h2>\n{{errorMessage}}{{logoutMessage}}\n<table class=\"table table-striped\">\n  {{samlRows}}\n</table>";
    private static final String SAML_ROW_TEMPLATE = "<tr><td><a href=\"{{url}}\">{{clientName}}</a></td></tr>";
    private static final String ONE_TIME_TEMPLATE = "      <form id=\"ott-form\" class=\"login-form\" method=\"post\" action=\"{{generateOneTimeTokenUrl}}\">\n        <h2>Request a One-Time Token</h2>\n{{errorMessage}}{{logoutMessage}}\n        <p>\n          <label for=\"ott-username\" class=\"screenreader\">Username</label>\n          <input type=\"text\" id=\"ott-username\" name=\"username\" placeholder=\"Username\" required>\n        </p>\n{{hiddenInputs}}\n        <button class=\"primary\" type=\"submit\" form=\"ott-form\">Send Token</button>\n      </form>\n";
  ...
}

📚 OAuth2 Authorization Code 요청

OAuth2AuthorizationRequestRedirectFilter

  • 클라이언트는 사용자 브라우저를 통해 인가 서버 권한 부여 엔드포인트로 리다이렉트해 권한 코드 부여 흐름을 시작한다.
스크린샷 2025-08-31 오후 3 36 07
  • 요청 매핑 URL
    • AuthorizationRequestMatcher : /oauth2/authorization/{registrationId}
    • AuthorizationEndpointConfig.authorizationRequestBaseUri를 통해 재정의가 가능하다.

DefaultOAuth2AuthorizationRequestResolver

  • 웹 요청에 대해 OAuth2AuthorizationRequest 객체를 최종 완성한다.
  • /oauth2/authorization/{registrationId}와 일치하는지 확인해서 일치하면 registrationId를 추출하고 이를 사용해서 ClientRegistration을 가져와 OAuth2AuthorizationRequest를 빌드한다.
스크린샷 2025-08-31 오후 3 39 48

OAuth2AuthorizationRequest

  • 토큰 엔드포인트 요청 파라미터를 담은 객체로서 인가 응답을 연계하고 검증할 때 사용한다.
스크린샷 2025-08-31 오후 3 40 25

OAuth2AuthorizationRequestRepository

  • 인가 요청을 시작한 시점부터 인가 요청을 받는 시점까지 OAuth2AuthorizationRequest를 유지해준다.
스크린샷 2025-08-31 오후 3 41 16

📚 OAuth2 Access Token 교환

OAuth2LoginAuthenticationFilter

  • 인가서버로부터 리다이렉트되면서 전달된 code를 인가 서버의 Access Token으로 교환하고 Access Token이 저장된 OAuth2LoginAuthenticationToken을 AuthenticationManager에 위임하여 UserInfo 정보를 요청해서 최종 사용자에 로그인한다.
  • OAuth2AuthorizedClientRepository를 사용해 OAuth2AuthorizedClient를 저장한다.
  • 인증에 성공하면 OAuth2AuthenticationToken이 생성되고 SecurityContext에 저장되면서 인증 처리를 완료한다.
스크린샷 2025-08-31 오후 3 50 08
  • 요청 매핑 URL : /login/oauth2/code/*

OAuth2LoginAuthenticationProvider

  • 인가 서버로부터 리다이렉트 된 이후 프로세스를 처리하며 Access Token으로 교환하고 이 토큰을 사용해 UserInfo 처리를 담당한다.
  • Scope에 openId가 포함되어 있으면 OidcAuthorizationCodeAuthenticationProvider를 호출하고 아니면 OAuth2AuthorizationCodeAuthenticationProvider를 호출하도록 제어한다.
스크린샷 2025-08-31 오후 3 53 16

OAuth2AuthorizationCodeAuthenticationProvider

  • 권한 코드 부여 흐름을 처리하는 AuthenticationProvider
  • 인가 서버에 Authorization Code와 Access Token 교환을 담당하는 클래스
스크린샷 2025-08-31 오후 3 54 01

OidcAuthorizationCodeAuthenticationProvider

  • OpenID Connect Core 1.0 권한 코드 부여 흐름을 처리하는 AuthenticationProvider이며 요청 Scope에 openId가 존재할 경우 실행된다.
스크린샷 2025-08-31 오후 3 55 20

DefaultAuthorizationCodeTokenResponseClient

  • 인가 서버의 token 엔드포인트로 통신을 담당하며 Access Token을 받은 후 OAuth2AccessTokenResponse에 저장하고 반환한다.
스크린샷 2025-08-31 오후 3 56 09 스크린샷 2025-08-31 오후 3 56 23 스크린샷 2025-08-31 오후 3 56 47 스크린샷 2025-08-31 오후 3 59 16

📚 OAuth2 User 모델

📚 OAuth2 UserInfo 엔드포인트

📚 OAuth2 OpenID Connect 로그아웃

📚 Authorization BaseUri & Redirect BaseUri

📚 OAuth2AuthorizationRequestResolver

📚

📖 Java

📖 Kotlin

📖 Coroutine

📖 Spring

📖 Spring Security

📖 Spring Batch

📖 Reactive Programming

📖 Database

📖 MySQL

📖 Redis

📖 JPA

📖 QueryDsl

📖 MSA

📖 Kafka

📖 Apache Flink

  • [Apache Flink - Apache Flink Architecture]
  • [Apache Flink - Stream Processing]
  • [Apache Flink - Data Stream API & Window]
  • [Apache Flink - State Management]

📖 HTTP

📖 AWS

📖 Docker

📖 Kubernetes

📖 CI/CD

📖 Nginx

📖 Monitoring

  • [Monitoring - Log Concept]
  • [Monitoring - Log Level & Filter]
  • [Monitoring - Logback]
  • [Monitoring - Log Collection with ELK Stack]
  • [Monitoring - Log Monitoring with Kibana]
  • [Monitoring - Building a Monitoring System with Spring Boot Actuator]
  • [Monitoring - Server Monitoring with Prometheus and Grafana with Discord Alerts]

📖 Test

📖 Effective Java 3/E

📖 Kotlin Academy - Effective Kotlin

📖 Kotlin Academy - 핵심편

📖 스프링으로 시작하는 리액티브 프로그래밍

📖 가상 면접 사례로 배우는 대규모 시스템 설계 기초 1

📖 가상 면접 사례로 배우는 대규모 시스템 설계 기초 2

📖 Clean Code

📖 리팩토링 2판

📖 주니어 백엔드 개발자가 반드시 알아야 할 실무 지식

📖 개발자가 반드시 정복해야 할 객체 지향과 디자인 패턴

📖 Spring AI

Clone this wiki locally