[Spring Security] Google Login with Spring Security & JWT (1)

반응형

이번 글에서는 Spring Boot App에서 Spring Security와 JWT를 사용해 Goolgle Login 기능을 적용하는 방법에 대해 알아보도록 하겠습니다.

1. Intro

이번글의 모든 내용은 이 글을 참고하였습니다. 😎

저는.. 위의 글을 읽으면서 전체 프로젝트의 구조를 이해하고, 내용을 이해하는데 굉장히 오랜시간이 걸렸습니다. Spring Security 시리즈를 작성하게된 이유도, Google Login의 흐름을 이해하기 위해서 입니다. 😅

물론, 아직도 이해가 안되는 부분이 있지만.. 혹시 포트폴리오를 위해 구글/네이버 로그인 등을 적용하시려거든, 반드시 Spring Security가 어떻게 작동하는 것인지 먼저 학습하시는 것을 추천드립니다.

2. Project Structure

먼저, 이번 프로젝트에서 사용할 전체 클래스들은 다음과 같습니다.

IntellJ에서는 다음과 같이 구성합니다.

이제 하나 하나 클래스의 용도를 살펴보고 마지막으로 전체 Flow를 설명드리도록 하겠습니다.

3. Dependency

프로젝트 dependency는 Spring Intializer에서 아래와 같이 선택합니다.

• Spring Web
• Spring Security
• Spring Data JPA
• MySQL Driver

추가적으로 아래의 2개를 사용합니다.

•  compile 'org.springframework.boot:spring-boot-starter-oauth2-client'
•  compile group: 'io.jsonwebtoken', name: 'jjwt', version: '0.9.1'

App 생성 후 DB & JPA 설정은 각자 본인환경에 맞도록 application.yml에 설정합니다.

application.yml

spring:
    datasource:
        url: jdbc:mysql://localhost:3306/spring_social?useSSL=false&serverTimezone=UTC&useLegacyDatetimeCode=false
        username: [username]
        password: [password]

    jpa:
        show-sql: true
        hibernate:
            ddl-auto: update
            naming-strategy: org.hibernate.cfg.ImprovedNamingStrategy
        properties:
            hibernate:
                dialect: org.hibernate.dialect.MySQL5InnoDBDialect

4. Goolgle App Configuration

다음으로 google developer console에서 Google App을 등록합니다.

구글 App을 등록하는 과정은 생략하겠습니다.

등록이 완료되면 위와 같이 OAuth 2.0 Client 를 확인할 수 있습니다.

4-1) application.yml

위에서 확인한 Client ID와 클라이언트 Secret key를 application.yml 에 등록합니다.

application.yml

spring:
  security:
    oauth2:
      client:
        registration:
          google :
            client-id : [CLIENT_ID]
            client-secret : [CLIENT_SECRET]
            # redirectUri: "{baseUrl}/login/oauth2/code/google"
            scope : email, profile

• scope는 email, profile를 사용하겠습니다.

scope는 로그인 성공 후 제 도메인에서 구글에 요청할 사용자 정보입니다. email, profile을 사용하겠다 선언했으므로 이제 제 도메인에서 google 사용자의 email과 profile 정보를 사용할 수 있습니다.

• redirectUri는 주석처리하겠습니다.

redirectUri는 사용자가 구글에서 Authentication을 성공 후 authorization_code를 전달할 제 도메인의 endPoint 입니다. 자세한 내용은 이 글을 참고하시길 바랍니다.

Spring Security에서는 google의 default redirectUri로 "/login/oauth2/code/google"를 제공합니다. 따라서 생략하도록 하겠습니다.

google developer console에는 위와 같이 동일한 redirection uri를 등록하면 됩니다.

만약 naver와 같은 spring security에서 제공해주지 않는 oauth provider를 사용할 경우 아래와 같은 설정을 추가해야 합니다.

## if use naver
spring:
  security:
    oauth2:
      client:
        registration:
          naver :
            client-id : [CLIENT_ID]
            client-secret : [CLIENT_SECRET]
            redirectUriTemplate : "{baseUrl}/login/oauth2/code/naver"
            authorization-grant-type : authorization_code
            scope : name, email, profile_image
            client-name : Naver

        provider:
          naver:
            authorization-uri : https://nid.naver.com/oauth2.0/authorize
            token-uri : https://nid.naver.com/oauth2.0/token
            user-info-uri : https://openapi.naver.com/v1/nid/me
            user-name-attribute : response

5. JWT Configuration

다음으로 Jwt 관련 configuartion을 application.yml에 추가합니다.

application.yml

app:
  auth:
    tokenSecret: [TOKEN_SECRET]
    # ex) tokenSecret: 926D96C90030DD58429D2751AC1BDBBC
    tokenExpirationMsec: [EXPIRATION_MSEC]
    # ex) tokenExpirationMsec: 864000000
  oauth2:
    authorizedRedirectUris:
      - http://localhost:3000/oauth2/redirect

• TOKEN_SECRET : JWT Token을 hash 할 때 사용하는 secret key입니다. 저는 이 곳에서 무료로 생성해주는 보안성 높은 key를 사용했습니다.

• EXPIRATION_MSEC : JWT Token의 유효기간을 설정합니다. 유효기간이 만료된 TOKEN으로 접근시 재발급 process를 거치게됩니다.

• authorizedRedirectUris : 생성된 JWT Token을 response 할 uri를 입력합니다. 저는 localhost:3000으로 실행되는 react App으로 전달하겠습니다. 이곳에 정의된 redirectUri 외에는 JWT Token을 전달 받을 수 없습니다.

6. Binding AppProperties

이제 위의 JWT Configuartion을 binding하는 POJO 클래스를 생성합니다.

AppProperties

@ConfigurationProperties(prefix = "app")
public class AppProperties {
    private final Auth auth = new Auth();
    private final OAuth2 oauth2 = new OAuth2();

    public static class Auth {
        private String tokenSecret;
        private long tokenExpirationMsec;

        public String getTokenSecret() {
            return tokenSecret;
        }

        public void setTokenSecret(String tokenSecret) {
            this.tokenSecret = tokenSecret;
        }

        public long getTokenExpirationMsec() {
            return tokenExpirationMsec;
        }

        public void setTokenExpirationMsec(long tokenExpirationMsec) {
            this.tokenExpirationMsec = tokenExpirationMsec;
        }
    }

    public static final class OAuth2 {
        private List<String> authorizedRedirectUris = new ArrayList<>();

        public List<String> getAuthorizedRedirectUris() {
            return authorizedRedirectUris;
        }

        public OAuth2 authorizedRedirectUris(List<String> authorizedRedirectUris) {
            this.authorizedRedirectUris = authorizedRedirectUris;
            return this;
        }
    }

    public Auth getAuth() {
        return auth;
    }

    public OAuth2 getOauth2() {
        return oauth2;
    }
}

• @ConfigurationProperties(prefix = "app")을 선언함으로써, application.yml에 정의한 JWT Configuration을 POJO 클래스로 binding 할 수 있습니다.

7. Enable AppProperties

위에서 작성한 AppProperties를 project에서 사용할 수 있도록 @EnableConfigurationProperties를 main application에 선언합니다.

SpringSocialApplication

@SpringBootApplication
@EnableConfigurationProperties(AppProperties.class)
public class SpringSocialApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringSocialApplication.class, args);
    }
}

8. Enable Cors

Front-end Client에서 server의 API에 access 할 수 있도록 cors를 open 합니다.

WebMvcConfig

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    private final long MAX_AGE_SECS = 3600;

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
        .allowedOrigins("*")
        .allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")
        .allowedHeaders("*")
        .allowCredentials(true)
        .maxAge(MAX_AGE_SECS);
    }
}

실제 production에서는 각자 환경에 맞는 allowedOrigins를 정의해 사용하면 됩니다.

9. User Entity

로그인에 사용할 User Entity는 다음과 같습니다.

@Entity
@Table(name = "users", uniqueConstraints = {
        @UniqueConstraint(columnNames = "email")
})
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String name;

    @Email
    @Column(nullable = false)
    private String email;

    private String imageUrl;

    @Column(nullable = false)
    private Boolean emailVerified = false;

    @JsonIgnore
    private String password;

    @NotNull
    @Enumerated(EnumType.STRING)
    private AuthProvider provider;

    private String providerId;

    // Getters and Setters (Omitted for brevity)
}

• AuthProvider는 google/naver등의 oauth provider를 의미합니다.

AuthProvider

public enum AuthProvider {
    google
    ## ,github
    ## ,naver
}

• oauth provider 별로 로그인 후 전달해주는 data가 다르기 때문에, 로그인시 provider를 확인해서 각각의 process를 거치게 됩니다.

10. User Repository

User Repository는 아래와 같이 생성합니다.

@Repository
public interface UserRepository extends JpaRepository<User, Long> {


}

참고 자료 : https://www.callicoder.com/spring-boot-security-oauth2-social-login-part-1/


추천서적

 

스프링5 레시피:스프링 애플리케이션 개발에 유용한 161가지 문제 해결 기법

COUPANG

www.coupang.com

파트너스 활동을 통해 일정액의 수수료를 제공받을 수 있음


반응형

댓글

Designed by JB FACTORY