끊임없이 검증하라

나에게 당연할지라도

Project

P1_클론 프로젝트(feat. 스프링부트와 AWS로 혼자 구현하는 웹 서비스)_5-1

fadet 2022. 3. 30. 16:05

* 이 포스트는 전 배달의민족, 현재 인프런에 계시고 유튜브 개발바닥의 크리에이터이신 개발자 이동욱님의 '스프링부트와 AWS로 혼자 구현하는 웹 서비스'를 기반으로 작성되었음을 알립니다. 포스트 맨 아래에 관련 링크가 있습니다. 책의 내용을 기반으로 작성되기에 실습 중이라면 책을 main 해당 포스트를 sub로 참고해주세요. 책의 설명이 부족한 부분 위주로 포스트가 구성됩니다.


5장 내용인 스프링 시큐리티 파트는 포스트가 길어 나누어 작성하겠습니다.

 

5.1 스프링 시큐리티와 Oauth2 클라이언트

 

# 시작 전

스프링 시큐리티를 사용하는 효용은 책에 충분히 설명되어 있으니 넘어가겠습니다. 스프링부트 버전이 많이 바뀐만큼 1.5를 유지할건지의 논의는 저희에겐 필요 없을 것입니다. 이번 포스트에선 다른 내용은 크게 책과 다르지 않고 이후 나올 application.yml을 잘 작성해주지 않으면 작동하지 않기때문에 해당 부분에서 다시 강조하겠습니다. 또한 책 뿐만 아니라 이번 포스트에서도 설정 하나하나의 자세한 설명은 하지 않고 중요한 부분만 체크할 것입니다.

 

5.2 구글 서비스 등록

 

# 구글 클라우드 플랫폼

해당 파트의 자세한 절차는 책 그대로 진행하시면 됩니다. 전체적인 순서만 언급하고 중요한 내용만 살펴보겠습니다.

> 구글 클라우드 플랫폼 접속

> 새 프로젝트 생성

> 사용자 인증 정보 탭 이동

> OAuth Client ID 생성 및 동의 화면 항목 작성

- 진행하다 만나는 승인된 리디렉션 URI를 입력할 때 작성되는 항목을 틀리지 않게 작성하고 이후 네이버 로그인에 사용되는 uri도 이와 똑같은 구조를 따르는걸 기억해두면 편합니다.

 

> 완료 및 Client 정보 화면 

- Client-ID와 Client-secret은 따로 메모장 등에 적어두면 편합니다.

 

# application-oauth.yml

책은 application.properties 기준입니다. 따라서 책대로 하시는 분은 그대로 따라가시고 yml파일을 이용하시는 분들은 yml로 바꿔서 작성해주시면 됩니다. 변환하는건 어렵지 않지만 계층 분리하실때 공백 두칸이 아닌 공백을 하나라도 실수하면 제대로 동작하지 않음을 주의해주세요.

 

5.3 구글 로그인 연동

 

# User 엔티티 관련 코드

소셜 로그인을 사용하더라도 당연히 유저 정보를 저장할 엔티티 클래스가 필요합니다. User, UserRepository는 앞서 소셜 로그인에서 회원가입시 받을 항목을 기반으로 작성하고 어플리케이션의 권한을 부여해줄 role 필드만 들어가면됩니다.

 

# SecurityConfig

해당 클래스는 스프링 시큐리티가 어떻게 작동할지를 설정합니다. 아래 코드는 책에 나온 내용을 조금 풀어 썼습니다.

public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
@Override
protected void configure(HttpSecurity http) throws Exception {
	// parameter로 받은 객체
	http 
            // * csrf
            .csrf().disable()
            // h2 console 사용을 위한 비활성화
            .headers().frameOptions().disable() 
            .and()
            // url별 권한 관리 부분 시작
            .authorizeRequests() 
            // 아래 요청은 전부 가능
            .antMatchers("/", "/css/**", "/images/**", "/js/**", "/h2-console/**", "/profile").permitAll()
            // api요청은 USER권한만 접근 가능
            .antMatchers("/api/v1/**").hasRole(Role.USER.name()) 
            // 이외 uri 요청은 인증사용자 설정
            .anyRequest().authenticated() 
            .and()
            .logout()
            // 로그아웃 성공시 홈으로
            .logoutSuccessUrl("/")
            .and()
            .oauth2Login()
            // 로그인 성공 후 정보 로딩시 설정
            .userInfoEndpoint() 
            // 서비스 구현체 설정
            .userService(customOAuth2UserService);

추가 설명을 하자면 csrf는 위조 요청을 통한 보안 침입인데, 쉽게 csrf 토큰을 포함한 요청일 경우 위조 요청이 아님을 서버가 인식한다고 생각하고 넘어가시면 됩니다. 그런데 보안 수준 향상에 필요한 csrf를 끌 필요가 있나?라는 의문이 들 수도 있습니다. 이는 조금 복잡한 설명이 필요하고 간단하게 우리가 이용하는 rest api는 csrf 공격으로부터 안전하므로 굳이 csrf 토큰이 필요없기 때문에 비활성화한다고 생각하시면 됩니다.

 

# CustomOAuth2UserService

앞선 클래스에 필요하고 기능 지원을 담당하는 클래스입니다. 마찬가지로 책 내용으로 다 담지 못한 부분만 체크하겠습니다.

public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
...
@Override
    // throws로 로그인이 서비스 전체에 영향을 주지 않게함
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        // User 정보를 가져오는 대리자 생성
        OAuth2UserService delegate = new DefaultOAuth2UserService();
        OAuth2User oAuth2User = delegate.loadUser(userRequest);
        
        // 로그인 서비스 구분을 위한 아이디(이후 네이버 추가시 사용됨)
        String registrationId = userRequest.getClientRegistration().getRegistrationId();
        // 로그인시 키가 되는 필드값을 받음
        String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails() 
                .getUserInfoEndpoint().getUserNameAttributeName();
                
        // 위에서 생성한 객체들과 유저 attribute를 인자로 받음
        OAuthAttributes attributes = OAuthAttributes.of(registrationId, userNameAttributeName, oAuth2User.getAttributes());
        // 아래에서 생성한 수정 멧소드
        User user = saveOrUpdate(attributes);
        // 세션에 유저 정보를 등록
        httpSession.setAttribute("user", new SessionUser(user)); // 유저정보를 세션에 담는 dto
		
        // 유저 정보를 담아 로그인된 사용자를 반환
        return new DefaultOAuth2User(
                Collections.singleton(new SimpleGrantedAuthority(user.getRoleKey())),
                attributes.getAttributes(),
                attributes.getNameAttributeKey());
    }

    // repository에 접근하여 유저 정보 존재시 업데이트 / 아니면 생성
    private User saveOrUpdate(OAuthAttributes attributes) {
        User user = userRepository.findByEmail(attributes.getEmail())
                .map(entity -> entity.update(attributes.getName(), attributes.getPictures()))
                .orElse(attributes.toEntity());
        // * 더티체킹
        return userRepository.save(user);

각 메소드가 하는 역할은 책을 보거나 메소드명을 보면 유추할 수 있을거라 생각합니다. 그래도 어려우시면 스프링부트 공식문서를 참고하시면 됩니다. 마지막에 표시한 더티체킹에 관해서는 앞선 내용에서 update 쿼리를 날리지 않았던 영속성 컨텍스트 관련해서 나왔던 내용입니다. 이에 대해선 나중에 준영속, merge 등의 관련 내용을 같이 공부하시는걸 추천합니다.

 

# OAuthAttribute

책에서는 해당 클래스를 dto로 놓고 진행됩니다. 사용자 요청을 받는 requestDto 클래스기 때문에 User 정보를 toentity로 생성합니다. 이 부분은 of메소드의 경우 이후 return이 ofGoogle과 ofNaver로 갈라진다는 것만 알고 넘어가면 헷갈릴 게 별로 없을 것 같습니다.

 

# SessionUser

해당 클래스 역시 dto클래스입니다. 앞서 posts관련 dto는 인자로 entity를 받았는데 여기선 user를 왜 받는지 의문이 드신다면 그냥 큰 이유는 없고 객체명의 중복을 피하기 위해서일거라 생각합니다.(제가 저자는 아니기에 제 추측입니다) 책에 나온 직렬화 관련 내용은 이해하고 넘어가시길 추천합니다.

 

이보다 우리가 살펴봐야 할 것은 직렬화 관련 내용입니다. 아래 코드의 import와 implements를 봅시다.

import java.io.Serializable; // 해당 인터페이스 impport

@Getter
public class SessionUser implements Serializable {

책에서 직렬화를 알거라 생각하고 넘어간 것 같은데 모르시는 분이 계실 것 같습니다. 직렬화(Serializable)는 쉽게 말해서 자바 내의 객체를 좀 더 범용적으로 사용할 수 있게 byte형태로 변환하는 것입니다. 하지만 아직 JPA를 제대로 공부하지 않으신분들은 @ManyToOne 등의 내용을 잘 모르실 수 있기에 책의 내용이 살짝 추상적일 수 있습니다. 이를 쉽게 풀어보자면 엔티티 클래스는 DB 테이블에 직접 매핑되기에 많은 연관 관계에 대한 많은 정보를 담고 있습니다. 책에서는 객체의 필드만이 아닌 이 많은 정보까지 모두 직렬화를 한다면 리소스가 투입되어야 하고 의도치 않은 로직을 수행하기에 Dto를 따로 만드는 것을 추천한 것입니다.

 

# 이외의 구현

나머지 내용은 크게 어려운 부분은 없으니 넘어가도록 하겠습니다. 그 전에 굳이 한가지만 짚고가자면 책에서는 DB에서 직접 권한을 변경했는데 이는 관리자 페이지를 만든다면 홈페이지에서 직접할 수 있습니다. 관리자 페이지에 대한 내용은 이후 포스트하도록 하겠습니다.


내용이 길어져 이후 5.4 내용부터는 이후 포스트에서 이어 작성하겠습니다.

 

refer

이동욱님 블로그의 관련 포스트 : https://jojoldu.tistory.com/539?category=717427

개발바닥 유튜브 :https://www.youtube.com/channel/UCSEOUzkGNCT_29EU_vnBYjg

 

개발바닥

본격 세계최초 DEV 엔터테인먼트 토크쇼 두 스타트업 개발자의 요절복통 이야기 구독 안하면 장애남!!

www.youtube.com

이동욱님 github의 해당 repository : https://github.com/jojoldu/freelec-springboot2-webservice