목차

개발환경

MySQL 회원 테이블 생성

프로젝트 생성

New Spring Starter Project

New Spring Starter Dependencies

기본 설정 및 OAuth2 변수 설정

application.yml 설정

Spring Security 설정

SecurityConfig 구현

프론트 페이지 생성

메인 페이지

Entity 추가

프로젝트 구조

개발환경

개발 환경 버전
Java 17
Spring Boot 3.2.7
Spring Security 6.2.5
Spring Security OAuth2 Client 6.2.5
Spring Data JPA 3.2.7
Lombok 1.18.30
빌드 관리 도구 Gradle 8.8
템플릿 엔진 Mustache
DB MySQL 8.0.36
개발 도구 Spring Tool Suite 4.22.1

MySQL 회원 테이블 생성

CREATE TABLE `members` (
   `ID` int NOT NULL AUTO_INCREMENT COMMENT '고유번호',
   `USERID` varchar(65) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '아이디',
   `USERNAME` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '이름',
   `EMAIL` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '이메일 주소',
   `ROLE` varchar(9) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '권한',
   `PROVIDER` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '공급자',
   `PROVIDER_ID` varchar(60) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '공급자 고유 ID',
   PRIMARY KEY (`ID`)
 ) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

프로젝트 생성

STS 상단 메뉴 > File > New > Spring Starter Project 선택합니다.

참고사진_1

New Spring Starter Project

Name, Java Version, Group, Artifact, Description, Package 항목을 입력한 후 아래 "Next" 버튼을 누릅니다.

참고사진_2

New Spring Starter Dependencies

Spring Boot Version 3.2.7 선택합니다.

검색란에 의존성 키워드를 입력하여 의존성을 추가합니다. (녹색 영역 참조)

필수 의존성 [Lombok, Spring Data JPA, MySQL Driver, Spring Security, OAuth2 Client, Mustache, Spring Web] 추가 후 아래 "Finish" 버튼을 누릅니다.

참고사진_3

기본 설정 및 OAuth2 변수 설정

application.yml 설정

application.properties파일의 확장자를 application.yml 확장자로 변경합니다.

application.yml 전체 소스

spring:
  application:
    name: oauth2

server:
  port: 8080
  servlet:
    session:
      timeout: 3600 #세션 유지시간 설정
    encoding:
      force: true #mustache UTF-8
 
---
#mustache 설정
spring:
  mustache:
    cache: false
    servlet:
      expose-request-attributes: true #mustache에서 csrf 토큰 변수 오류 발생 때문에 추가
      expose-session-attributes: true #세션
 
---
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/work?useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Seoul
    username: user03
    password: localdbpass!
  jpa:
    properties:
      hibernate:
        format_sql: true #JPA 쿼리문 정렬된 형태로 출력
 
# JPA 쿼리문, binding parameter 출력
logging:
  level:
    org:
      hibernate:
        SQL: TRACE
        orm:
          jdbc:
            bind: TRACE
            
---
#OAuth2 변수 설정
spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: 858221476598-nrp6n72ecrmkhfj2r7qoqbd85t0up6c8.apps.googleusercontent.com
            client-secret: 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
            redirect-uri: http://localhost:8080/login/oauth2/code/google
            authorization-grant-type: authorization_code
            scope:
              - profile
              - email

          naver:
            client-name: naver
            client-id: 89eb6tH0NecXpm5n4Cna
            client-secret: G7X7ltb1UI
            redirect-uri: http://localhost:8080/login/oauth2/code/naver
            authorization-grant-type: authorization_code
            scope:
              - name
              - email

        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

OAuth2 변수 설정 부분에 구글, 네이버에서 발급 받은 Client ID, Client Secret와 CallbackURL을 넣어줍니다.

참고사진_4

Spring Security 설정

SecurityConfig 구현

config 패키지를 생성 후 SecurityConfig 클래스 파일을 생성합니다.

패키지 위치 : com.example.oauth2.config


SecurityConfig.java

package com.example.oauth2.config;

import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;

import lombok.RequiredArgsConstructor;
 
@RequiredArgsConstructor
@Configuration
@EnableWebSecurity
public class SecurityConfig {
    
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        
        // csrf token
        http
            .csrf((csrf) -> csrf.disable());  // 비활성화
        
        // 인가
        http
            .authorizeHttpRequests((authorize) -> authorize
                    .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()   // resources의 static(정적파일) 모두 허용
                    .requestMatchers("/", "/login", "/login/oauth2/code/*").permitAll()  // 보안 검사 없이 접근을 허용
                    //.requestMatchers("/member/**").hasAnyRole("ADMIN", "USER")  // role - ADMIN, USER만 접근 허용
                    //.requestMatchers("/admin").hasRole("ADMIN")  // role - ADMIN만 접근 허용
                    .anyRequest().authenticated()
            );
 
        return http.build();
    }
  
}

프론트 페이지 생성

메인 페이지 구현

controller 패키지를 생성 후 MainController 클래스 파일을 생성합니다.

패키지 위치 : com.example.oauth2.controller


MainController.java

package com.example.oauth2.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class MainController {

    /**
     * 메인 페이지
     * @return
     */
    @GetMapping("/")
    public String index() {
        return "index";
    }

}

templates에 index.mustache 파일을 생성합니다.


index.mustache

<!DOCTYPE html>
<html lang="ko"> 
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>메인 페이지</title>
        <style>
            .container {
                width: 800px !important ;
            }
        </style>
    </head>
    <body>
        <div class="container">
            <div class="row">
                <div class="col-md-12 pt-5">
                    <div class="card mb-4">
                        <h5 class="card-header text-center">메인 페이지</h5>
                        <div class="card-body">
                            {{#isLogin}}
                            <h5 class="card-title">{{username}}님 환영합니다.</h5>
                            <p class="card-text">접속하신 아이디는 {{userid}} 입니다.</p>
                            {{/isLogin}}
                            {{^isLogin}}
                            <div class="col-md-12 text-center">
                                <a href="/login" class="btn btn-outline-success">로그인</a>
                            </div>
                            {{/isLogin}}
                        </div>
                    </div>
                </div>
            </div>
            {{#isLogin}}
            <div class="row">
                <div class="col-md-12 text-center">
                    <a href="/logout" class="btn btn-outline-danger">로그아웃</a>
                </div>
            </div>
            {{/isLogin}}
        </div>
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
    </body>
</html>

Entity 추가

entity 패키지를 생성 후 Members 클래스 파일을 생성합니다.

패키지 위치 : com.example.oauth2.entity

Members.java

package com.example.oauth2.entity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
 
@NoArgsConstructor
@Getter
@Entity
public class Members {
 
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    // 아이디
    @Column(nullable = false)
    private String userid;  // Spring Security username

    // 이메일 주소
    @Column(nullable = false)
    private String email;
    
    // 이름
    @Column(nullable = false)
    private String username;    // 만일 null값이 들어올 경우 예외처리 필요

    // 권한
    @Column(nullable = false)
    private String role;
    
    // 공급자
    @Column(nullable = false)
    private String provider;
    
    // 공급자 고유 ID
    @Column(nullable = false)
    private String providerId;
    
    
    @Builder
    public Members(String userid, String email, String username, String role, String provider, String providerId) {
        this.userid     = userid;
        this.email      = email;
        this.username   = username;
        this.role       = role;
        this.provider   = provider;
        this.providerId = providerId;
    }
 
}

프로젝트 구조

참고사진_5

실행화면

참고사진_6

©2024, DevDream