티스토리 뷰

1. Oauth 

  • 제3자 클라이언트(우리의 서비스)가 사용자의 인증과 접근 권한을 위임(Delegated Authorization)받을 수 있는 프로토콜
  • 로그인 기능을 직접 구현하는 것 대신, 구글, 네이버를 통해 로그인 가능
  • 개발자가 로그인 구현보다 서비스 구현에 집중할 수 있게 된다. 
  • 사용자 입장에서도 서비스 이용이 편하다

관련 개념

  • Resource Owner: 사용자  
  • Client: 인증을 요청하는 애플리케이션
    • 개발 서비스를 Client라 한다. Resource Server에 API를 요청하기 때문에
    • 사용자를 대신하여 Authorization Server로 인증 요청을 전송
    • Authorization Server에서 받은 Access Token을 사용하여, Resource Server에 사용자 데이터를 요청한다. 
  • Resource Server: 사용자 Resource를 관리 (Google, Kakao, Naver 등)
    • 사용자의 정보(구글 캘린더 정보, 친구 목록 등)를 갖고있는 서버
    • Access Token을 받으면 사용자 정보를 전달 
  • Authorization Server: OAuth를 제공하는 인증 서버 (Google, Kakao, Naver의 인증 서버
    • Resource Owner를 인증
    • Client에게 액세스 토큰을 발급
    • Client가 Oauth 인증에 필요한 어플리케이션 정보(Client url, 인증 후 redirect url 등 ) 를 등록
    • 등록 후 Client Id와 Client Secret를 서버에 설정하면 Access Token 획득이 가능
  • Access Token: 사용자의 권한을 위임받아 Resource Server API 호출 시 사용되는 인증 토큰

 

 

다음 글에서 참고

https://hudi.blog/oauth-2.0/

 

OAuth 2.0 개념과 동작원리

2022년 07월 13일에 작성한 글을 보충하여 새로 포스팅한 글이다. OAuth 등장 배경 우리의 서비스가 사용자를 대신하여 구글의 캘린더에 일정을 추가하거나, 페이스북, 트위터에 글을 남기는 기능을

hudi.blog

 


2. OAuth 2.0 클라이언트 추가 

implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
  • Spring Boot에서 OAuth 2.0 클라이언트 기능을 제공
  • 진행하는 프로젝트에 OAuth 기능만 우선 적용하기에 필요한 Oauth 2.0 클라이언트만 추가했다.
  • 또한 원하는 spring security 일부 기능이 포함되서 원하는 개발에는 문제가 없었다. 
    • redirect url controller 지원 
    • API 요청에 대한 권한 검사

주요 기능

  • OAuth 2.0 로그인 지원 (소셜 로그인)
    • Google, Naver, Kakao 등의 OAuth 2.0 기반 로그인 기능을 쉽게 설정할 수 있음.
    • Spring Security와 연동하여 인증된 사용자 정보를 가져올 수 있음.
  • OAuth 2.0 클라이언트 역할 수행
    • 애플리케이션이 OAuth 2.0 Provider(인증 서버)에 요청을 보내고 Access Token을 받는 기능을 제공.
    • 예를 들어, 애플리케이션이 Google API 또는 GitHub API에 접근하려면 이 라이브러리를 통해 Access Token을 받아야 함.
  • Spring Security와 자동 통합
    • OAuth 로그인 및 인증을 설정할 때 별도의 복잡한 설정 없이 간단한 코드만으로 구현 가능.
    • application.yml 또는 application.properties에서 OAuth Provider 설정을 추가하면 Spring Security에서 자동으로 OAuth 인증을 처리해 줌.

3. application-oauth.properties 등록

  • application-oauth.properties를 생성해서 인증에 필요한 client 정보를 저장
  • google, naver, kakao 등에 어플리케이션 정보 등록 후 secret id, secret secret 을 application 파일에 저장
  • client 의 secret을 보관하기 때문에 git으로 관리하면 안된다. 그렇기에 해당 파일을 배포를 할 경우 직접 넣어줬다. 
# springboot test 에서는 다음처럼 test용 id, secret 설정하여 실제 인증과정은 생략 가능
# spring.security.oauth2.client.registration.google.client-id=test
# spring.security.oauth2.client.registration.google.client-secret=test
# spring.security.oauth2.client.registration.google.scope=profile,email


# 구글 oauth2 로그인 정보 등록
spring.security.oauth2.client.registration.google.client-id=client의 id
spring.security.oauth2.client.registration.google.client-secret=client의 secret
spring.security.oauth2.client.registration.google.scope=profile,email

 

 

4.  Configuration 설정과 Spring Security 버전별 작성 비교

  • @EnableWebSecurity를 사용하면 Spring Security가 기본 보안 기능을 활성화
  • 특정 URL에 대한 접근 권한(permitAll(), hasRole() 등) 설정 가능
  • 세션 관리, 보안 정책 조정 가능

Spring Security 5.7 미만 설정 예시

  • localhost에서 h2 DB를 사용하여 웹으로 체크하기에 csrf, frameoptions 완화했다.
  • 홈 화면에 대한 접속은 로그인 없이 누구나 접속 가능하게 설정
  • 단, /api/v1/ 경로에 대해서는 USER 권한 필요

 

@RequiredArgsConstructor
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final CustomOAuth2UserService customOAuth2UserService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .headers().frameOptions().disable()
                .and()
                    .authorizeRequests()
                    .antMatchers("/", "/css/**", "/images/**", "/js/**", "/h2-console/**", "/profile").permitAll()
                    .antMatchers("/api/v1/**").hasRole(Role.USER.name())
                    .anyRequest().authenticated()
                .and()
                    .logout()
                        .logoutSuccessUrl("/")
                .and()
                    .oauth2Login()
                        .userInfoEndpoint()
                            .userService(customOAuth2UserService);
    }
}

 

Spring Security 5.7 이상으로 변경 후

Spring Security 5.4 이상

  • logout()과 logoutSuccessUrl() 등의 체인을 함수형 방식으로 구성하도록 권장
  • 함수형 방식이 파라미터로 인해 구분되어 가독성이 있고 유지보수에서 실수를 줄여줄 수 있다고 생각한다. 

Spring Security 5.7 이상

  • WebSecurityConfigurerAdapter deprecated 
  • antMatcher() Deprecated
  • SecurityFilterChain과 개별적인 AuthenticationManager 설정 사용 권장
@Configuration
@RequiredArgsConstructor
public class SecurityConfig {

    private final CustomOAuth2UserService customOAuth2UserService;

    @SuppressWarnings("removal")    // frameOptions removal 경고 제거
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

        http
                .csrf(csrf -> csrf
                        // h2 console 정상동작을 위해(POST) 경로에 CSRF 비활성화, 실무에서는 보안을 위해 차단필요
                        .ignoringRequestMatchers("/**"))
                .headers(headers -> headers
                        .frameOptions().disable())  // h2 console 사용하기 위해 차단안함. 실무에서는 보안을 위해 차단필요
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/", "/css/**", "/images/**", "/js/**", "/h2-console/**", "/profile", "/api/**")
                            .permitAll()
                        .requestMatchers("/api/v1/**").hasRole(Role.USER.name())    // USER 권한 가능
                        .anyRequest().authenticated())
                .logout(logout -> logout
                        .logoutSuccessUrl("/")          // 로그아웃 후 리다이렉션 url 설정
                        .invalidateHttpSession(true)    // 세션 무효화
                        .deleteCookies("JSESSIONID"))
                .oauth2Login(oauth -> oauth             // OAuth 로그인 기능에 대한 설정 진입
                        .userInfoEndpoint(userInfo -> userInfo  // Access Token을 이용해 사용자 정보 요청
                                .userService(customOAuth2UserService)));    // 사용자 데이터 처리

        return http.build();
    }
}

 


더 간단한 체인형/함수형 비교 예시

체인형 방식

http
    .authorizeRequests()
        .antMatchers("/public/**").permitAll()
        .anyRequest().authenticated()
        .and()
    .formLogin()
        .loginPage("/login")
        .permitAll()
        .and()
    .logout()
        .logoutUrl("/logout")
        .logoutSuccessUrl("/login?logout");

 

 

함수형 방식

http
    .authorizeHttpRequests(auth -> auth
        .requestMatchers("/public/**").permitAll()
        .anyRequest().authenticated()
    )
    .formLogin(form -> form
        .loginPage("/login")
        .permitAll()
    )
    .logout(logout -> logout
        .logoutUrl("/logout")
        .logoutSuccessUrl("/login?logout")
    );