Spring Security과 OAuth2 - 소셜로그인(1)

my_mon·2023년 3월 21일
0
post-thumbnail

Spring Security

스프링 시큐리티는 스프링 기반의 어플리케이션에서 보안과 인증(Authentication)을 담당하는 프레임워크다. 인증, 권한부여, 보안, 세션관리 등의 기능을 제공하며 웹 애플리케이션 뿐만 아니라 서비스 계층에서의 보안기능을 구현하는 데 사용될 수 있다. 다양한 인증방식을 지원하는데 종류로는 기본적으로 폼 로그인 방식과 OAuth, OpenID Connect, LDAP, Kerberos 등의 다른 인증방식도 지원한다.

인증과 권한 부여를 위해 Authentication(인증)Authorization(권한 부여) 개념을 사용한다. Authentication은 주체의 신원을 확인하는 것이며, 이를 통해 해당 주체가 자원에 접근할 권한이 있는지 확인한다. Authorization은 인증된 주체가 특정 자원에 대해 어떤 권한을 가지고 있는지 확인하는 것이다.

스프링 시큐리티와 OAuth2를 활용하여 소셜로그인을 구현해보자.

구글 서비스 등록

  1. 구글 클라우드 플랫폼 주소(https://console.cloud.google.com)로 이동하여 새 프로젝트를 만든 후, API 및 서비스 카테고리로 이동하여 사용자 인증정보를 클릭한다.
  2. 사용자 인증 정보 만들기 - OAuth 클라이언트 ID - 동의 화면 구성
  3. OAuth 동의 화면 - 어플리케이션 이름, 지원이메일, API범위(email, profile, openid) 설정
  4. OAuth 클라이언트 ID 만들기 - 웹애플리케이션 설정 - 이름설정
  5. 승인된 리디렉션 URI설정
    스프링 부트2 버전의 시큐리티에서는 기본적으로 {도메인}/login/oauth2/code/{소셜서비스코드}로 리다이렉트 URL을 지원하고 있다.
    http://localhost:8080/login/oauth2/code/google
    현재는 개발단계이므로 localhost로 등록하지만, 배포하게 되면 추가로 주소를 추가해야한다.
  6. 모두 설정해준 뒤 돌아오면, 클라이언트ID와 보안비밀번호를 조회할 수 있다.

프로젝트 설정

application-oauth.properties 파일 구성

준비물은 위의 단계에서 조회된 클라이언트 ID와 클라이언트 보안 비밀번호다.
resources 디렉토리에 application-oauth.properties 파일을 생성해주자.

spring.security.oauth2.client.registration.google.client-id=클라이언트 ID
spring.security.oauth2.client.registration.google.client-secret=클라이언트 보안 비밀번호
spring.security.oauth2.client.registration.google.scope=profile,email

application-xxx.propertis 파일은 'xxx'라는 이름의 프로파일을 지정하여 각각 다른 설정을 사용할 수 있다.
프로파일이란 애플리케이션을 실행할 때 사용하는 설정의 집합으로, 실행 환경에 따라 설정을 달리하여 관리할 수 있다.
appication-oauth.properties 파일은 스프링부트 애플리케이션에서 OAuth 2.0 인증을 구현할 때 사용하는 프로퍼티 파일이다. 이 파일에는 OAuth 2.0 인증을 위한 클라이언트 ID, 클라이언트 시크릿, 리다이렉트 URI 등의 설정 정보가 포함된다. 이 파일을 사용하여 OAuth 2.0인증을 구현하면, 소셜 미디어 서비스에서 제공하는 OAuth 2.0 인증 API를 사용하여 사용자 인증을 처리할 수 있다.
이제 application-properties 파일에서 application-oauth.properties를 포함하도록 구성해야 한다.
spring.profiles.include=oauth 이렇게 코드를 추가로 작성해주면, 이 설정값을 사용할 수 있게된다.

.gitignore 등록
클라이언트 ID와 보안 비밀은 보안이 중요한 정보들이다. 외부에 노출될 경우 언제든 개인정보를 가져갈 수 있는 취약점이 될 수 있으므로 .gitignore에 다음과 같이 추가하여 깃허브에 커밋되지 않도록 한다.

프로젝트 구현

User Entity 작성

@Getter
@NoArgsConstructor
@Entity
public class User extends BaseTimeEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String name;

    @Column(nullable = false)
    private String email;

    @Column
    private String picture;

    @Enumerated(EnumType.STRING)
    @Column
    private Role role;

    @Builder
    public User(String name, String email, String picture, Role role) {
        this.name = name;
        this.email = email;
        this.picture = picture;
        this.role = role;
    }

    public User update(String name, String picture) {
        this.name = name;
        this.picture = picture;

        return this;
    }

    public String getRoleKey() {
        return this.role.getKey();
    }

}

위 코드에서 User 엔티티는 데이터베이스에 저장되는 User 테이블과 매핑된다.
Id와 컬럼들을 정의해주는 것을 알 수 있는데, 그 중 Role은 사용자의 역할을 나타내며 열거형 타입을 데이터베이스에서 문자열로 저장하도록 지정되어있다. 기본적으로는 int로 된 숫자가 저장되는데, 문자열로 저장하도록 별도로 설정해준 것이다.

Role 클래스 작성
각 사용자의 권한을 관리할 Enum 클래스 Role을 생성하여 작성하자.

@Getter
@RequiredArgsConstructor
public enum Role {
    
    GUEST("ROLE_GUEST", "손님"),
    USER("ROLE_USER", "일반 사용자");
    
    private final String key;
    private final String title;
}

스프링 시큐리티에서는 권한 코드에 항상 ROLE_이 앞에 있어야만 한다.
GUEST와 USER 두 가지 상수로 정의하여 각각의 값을 가지고 있다.

이 클래스에서 두개의 final 필드인 key와 title은, 각각 사용자의 역할을 나타내는 문자열("ROLE_GUEST", "ROLE_USER")과 해당 역할의 한글 제목("손님", "일반 사용자")을 나타낸다.

UserRepository 생성

public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByEamil(String email);
}

findByEamil : 소셜로그인으로 반환되는 값 중 email을 통해 이미 생성된 사용자인지 처음 가입하는 사용자인지 판단하기 위한 메소드

스프링 시큐리티 설정

의존성 추가

build.gradle에 스프링 시큐리티 관련 의존성 추가

  implementation('org.springframework.boot:spring-boot-starter-oauth2-client')

spring-boot-starter-oauth2-client 의존성을 추가하여 스프링 부트 애플리케이션에서 OAuth2 서비스 제공자와 연동 후 로그인 및 사용자 인증 과정을 처리하도록 한다. 이 의존성은 스프링 시큐리티와 연동하여 사용할 수 있으며, OAuth2 클라이언트의 기능을 쉽게 사용할 수 있도록 필요한 라이브러리들을 자동으로 추가해준다.

OAuth 라이브러리를 이용한 소셜로그인 코드 작성
소셜로그인에 관한 설정 코드를 작성하는 클래스를 생성하는 것이므로, 설정 정보를 관리하기 위한 패키지인 config 패키지를 생성한다.
생성한 config 패키지 안에, 인증관련 설정파일을 다룰 패키지 auth를 생성한 후 설정클래스를 생성하여 아래와 같이 작성해준다.

@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/**")
                    .permitAll()
                    .antMatchers("/api/v1/**").hasRole(Role.USER.name())
                    .anyRequest().authenticated()
                .and()
                    .logout().logoutSuccessUrl("/")
                .and()
                .oauth2Login()
                .userInfoEndpoint().userService(customOAuth2UserService);
    }
}

위의 코드에 관해 잠시 설명하자면

이 클래스는 WebSecurityConfigurerAdapter 클래스를 상속받아 구현현하는데, WebSecurityConfigurerAdapter 클래스는 스프링 시큐리티 구성을 위한 필수적인 메소드를 제공하는 추상 클래스다.

@EnableWebSecurity 는 스프링 시큐리티를 활성화 시키기 위한 어노테이션이다.
HttpSecurity는 스프링 시큐리티에서 HTTP 요청에 대한 보안 처리를 구성하는 클래스다. 이 클래스를 사용하면 인증과 권한부여에 대한 설정을 지정할 수 있다.
HttpSecurity 는 WebSecurityConfigurerAdapter 클래스를 상속하여 configure(HttpSecurity http)메소드를 재정의하여 사용한다.

// WebSecurityConfigurerAdapter 
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		this.disableLocalConfigureAuthenticationBldr = true;
	}

.csrf().disable().headers().frameOptions().disable()
: csrf 보호기능과 frameOptions기능을 사용하지 않도록 설정하는 코드. h2-console 화면을 사용하기 위해 해당 옵션들을 disable로 설정한다.

authorizeRequests() 특정한 경로에 특정한 권한을 가진 사용자만 접근할 수 있도록 설정한다. URL별 권한 관리를 설정하는 옵션의 시작점이다.

.antMatchers()은 권한관리를 지정하는 옵션으로 URL, HTTP 메소드별로 관리가 가능하다. .permitAll() 메소드로 끝나는 경로에 있는 url은 무조건 접근을 허용하도록 설정한 것이고, hasRole()로 끝나는 경로에 있는 url은 권한을 가진 사람만 접근을 허용하도록 설정한 것이다.

anyRequest는 natMatchers에서 설정되지 않은 나머지 URL을 의미한다. .anyRequest.authenticated()는 즉 나머지 URL은 모두 인증된 사용자들에게만 허용하도록 설정한 것이다.

.logout().logoutSuccessUrl("/") 은 로그아웃에 대한 설정인데, 로그아웃에 성공했다면 "/" URL 주소로 이동된다.

oauth2Login : OAuth 2.0 기반 로그인 기능에 대한 여러 설정한다.
userInfoEndpoint : OAuth 2.0 로그인으로 인증한 사용자 정보를 가져오는 엔드포인트를 설정들을 담당한다.
userService : 소셜로그인 성공 시 가져온 사온자 정보를 처리할 UserService 인터페이스의 구현체를 등록한다.

profile
기록하는 사람

0개의 댓글