๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
์นดํ…Œ๊ณ ๋ฆฌ ์—†์Œ

Chp5. ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์™€ OAuth 2.0์œผ๋กœ ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ํ•˜๊ธฐ

by coderSohyun 2023. 8. 19.

์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ(Spring Security)๋Š” ๋ง‰๊ฐ•ํ•œ ์ธ์ฆ(Authentication)๊ณผ ์ธ๊ฐ€(Authorization) (ํ˜น์€ ๊ถŒํ•œ ๋ถ€์—ฌ) ๊ธฐ๋Šฅ์„ ๊ฐ€์ง„ ํ”„๋ ˆ์ž„์›Œํฌ์ž…๋‹ˆ๋‹ค. ์‚ฌ์‹ค์ƒ ์Šคํ”„๋ง ๊ธฐ๋ฐ˜์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋Š” ๋ณด์•ˆ์„ ์œ„ํ•œ ํ‘œ์ค€์ด๋ผ๊ณ  ๋ณด๋ฉด ๋ฉ๋‹ˆ๋‹ค. 

 

์ธํ„ฐ์…‰ํ„ฐ, ํ•„ํ„ฐ ๊ธฐ๋ฐ˜์˜ ๋ณด์•ˆ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ๋ฅผ ํ†ตํ•ด ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์„ ์ ๊ทน์ ์œผ๋กœ ๊ถŒ์žฅํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. 

 

์Šคํ”„๋ง์˜ ๋Œ€๋ถ€๋ถ„ ํ”„๋กœ์ ํŠธ๋“ค(Mvc, Data, Batch ๋“ฑ๋“ฑ)์ฒ˜๋Ÿผ ํ™•์žฅ์„ฑ์„ ๊ณ ๋ คํ•œ ํ”„๋ ˆ์ž„์›Œํฌ๋‹ค ๋ณด๋‹ˆ ๋‹ค์–‘ํ•œ ์š”๊ตฌ์‚ฌํ•ญ์„ ์†์‰ฝ๊ฒŒ ์ถ”๊ฐ€ํ•˜๊ณ  ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฐ ์†์‰ฌ์šด ์„ค์ •์€ ํŠนํžˆ๋‚˜ ์Šคํ”„๋ง ๋ถ€ํŠธ 1.5์—์„œ 2.0์œผ๋กœ ๋„˜์–ด์˜ค๋ฉด์„œ ๋”์šฑ ๋” ๊ฐ•๋ ฅํ•ด์กŒ์Šต๋‹ˆ๋‹ค. 

 

์ด๋ฒˆ ์žฅ์—์„œ๋Š” ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์™€ OAuth 2.0์„ ๊ตฌํ˜„ํ•œ ๊ตฌ๊ธ€ ๋กœ๊ทธ์ธ์„ ์—ฐ๋™ํ•˜์—ฌ ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ์„ ๋งŒ๋“ค์–ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

 

5.1 ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์™€ ์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ Oauth2 ํด๋ผ์ด์–ธํŠธ 

 

5.2 ๊ตฌ๊ธ€ ์„œ๋น„์Šค ๋“ฑ๋ก 

๋จผ์ € ๊ตฌ๊ธ€ ์„œ๋น„์Šค์— ์‹ ๊ทœ ์„œ๋น„์Šค๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์„œ ๋ฐœ๊ธ‰๋œ ์ธ์ฆ ์ •๋ณด(clientId์™€ clientSecret)๋ฅผ ํ†ตํ•ด์„œ ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ๊ณผ ์†Œ์…œ ์„œ๋น„์Šค ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋‹ˆ ๋ฌด์กฐ๊ฑด ๋ฐœ๊ธ‰๋ฐ›๊ณ  ์‹œ์ž‘ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. 

๊ตฌ๊ธ€ ํด๋ผ์šฐ๋“œ ํ”Œ๋žซํผ ์ฃผ์†Œ (https://console.cloud.google.com/) ๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค

๊ตฌ๊ธ€ ํ”Œ๋žซํผ ํ”„๋กœ์ ํŠธ ์„ ํƒ
์ƒˆ ํ”„๋กœ์ ํŠธ ์„ ํƒ
์ƒˆํ”„๋กœ์ ํŠธ ์ด๋ฆ„
์„ค์ • ํŽ˜์ด์ง€ ์ด๋™
์‚ฌ์šฉ์ž ์ธ์ฆ ์ •๋ณด ์„ ํƒ ํ™”๋ฉด
OAuth ํด๋ผ์ด์–ธํŠธ ID
๋™์˜ ํ™”๋ฉด ๊ตฌ์„ฑ
๋™์˜ ํ™”๋ฉด ๊ด€๋ฆฌ

  • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ด๋ฆ„ : ๊ตฌ๊ธ€ ๋กœ๊ทธ์ธ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋…ธ์ถœ๋  ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ด๋ฆ„์„ ์ด์•ผ๊ธฐํ•ฉ๋‹ˆ๋‹ค
  • ์ง€์› ์ด๋ฉ”์ผ : ์‚ฌ์šฉ์ž ๋™์˜ ํ™”๋ฉด์—์„œ ๋…ธ์ถœ๋  ์ด๋ฉ”์ผ ์ฃผ์†Œ์ž…๋‹ˆ๋‹ค. ๋ณดํ†ต์€ ์„œ๋น„์Šค์˜ help ์ด๋ฉ”์ผ ์ฃผ์†Œ๋ฅผ ์‚ฌ์šฉํ•˜์ง€๋งŒ, ์—ฌ๊ธฐ์„œ๋Š” ๋…์ž์˜ ์ด๋ฉ”์ผ ์ฃผ์†Œ๋ฅผ ์‚ฌ์šฉํ•˜์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.
  • Google API์˜ ๋ฒ”์œ„ : ์ด๋ฒˆ์— ๋“ฑ๋กํ•  ๊ตฌ๊ธ€ ์„œ๋น„์Šค์—์„œ ์‚ฌ์šฉํ•  ๋ฒ”์œ„ ๋ชฉ๋ก์ž…๋‹ˆ๋‹ค. ๊ธฐ๋ณธ๊ฐ’์€ email/profile/openid์ด๋ฉฐ, ์—ฌ๊ธฐ์„œ๋Š” ๋”ฑ ๊ธฐ๋ณธ ๋ฒ”์œ„๋งŒ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด์™ธ ๋‹ค๋ฅธ ์ •๋ณด๋“ค๋„ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ๋ฒ”์œ„ ์ถ”๊ฐ€ ๋ฒ„ํŠผ์œผ๋กœ ์ถ”๊ฐ€ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. 

๋™์˜ ํ™”๋ฉด ๊ตฌ์„ฑ์ด ๋๋‚ฌ์œผ๋ฉด ํ™”๋ฉด ์ œ์ผ ์•„๋ž˜์— ์ €์žฅ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๊ณ  ๋‹ค์Œ ๊ทธ๋ฆผ๊ณผ ๊ฐ™์ด OAuth ํด๋ผ์ด์–ธํŠธ ID ๋งŒ๋“ค๊ธฐ ํ™”๋ฉด์œผ๋กœ ๋ฐ”๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค. ํ”„๋กœ์ ํŠธ ์ด๋ฆ„์„ ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค.

ํ™”๋ฉด ์•„๋ž˜๋กœ ๋‚ด๋ ค๊ฐ€๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด URL ์ฃผ์†Œ๋ฅผ ๋“ฑ๋กํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค

์—ฌ๊ธฐ์„œ [์Šน์ธ๋œ ๋ฆฌ๋””๋ ‰์…˜ URI] ํ•ญ๋ชฉ๋งŒ ๊ทธ๋ฆผ๊ณผ ๊ฐ™์ด ๊ฐ’์„ ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค 

์Šน์ธ๋œ ๋ฆฌ๋””๋ ‰์…˜ URI

  • ์„œ๋น„์Šค์—์„œ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ธ์ฆ ์ •๋ณด๋ฅผ ์ฃผ์—ˆ์„ ๋•Œ ์ธ์ฆ์ด ์„ฑ๊ณตํ•˜๋ฉด ๊ตฌ๊ธ€์—์„œ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธํ•  URL์ž…๋‹ˆ๋‹ค
  • ์Šคํ”„๋ง ๋ถ€ํŠธ 2 ๋ฒ„์ „์˜ ์‹œํ๋ฆฌํ‹ฐ์—์„œ๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ {๋„๋ฉ”์ธ}/login/oauth2/code/{์†Œ์…œ์„œ๋น„์Šค์ฝ”๋“œ}๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ URL์„ ์ง€์›ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค
  • ์‚ฌ์šฉ์ž๊ฐ€ ๋ณ„๋„๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ URL์„ ์ง€์›ํ•˜๋Š” Controller๋ฅผ ๋งŒ๋“ค ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ์‹œํ๋ฆฌํ‹ฐ์—์„œ ์ด๋ฏธ ๊ตฌํ˜„ํ•ด ๋†“์€ ์ƒํƒœ์ž…๋‹ˆ๋‹ค
  • ํ˜„์žฌ๋Š” ๊ฐœ๋ฐœ ๋‹จ๊ณ„์ด๋ฏ€๋กœ http://localhost:8080/login/oauth2/code/google๋กœ๋งŒ ๋“ฑ๋กํ•ฉ๋‹ˆ๋‹ค
  • AWS ์„œ๋ฒ„์— ๋ฐฐํฌํ•˜๊ฒŒ ๋˜๋ฉด localhost ์™ธ์— ์ถ”๊ฐ€๋กœ ์ฃผ์†Œ๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผํ•˜๋ฉฐ, ์ด๊ฑด ์ดํ›„ ๋‹จ๊ณ„์—์„œ ์ง„ํ–‰ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค 

์ƒ์„ฑ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด ๋‹ค์Œ ๊ทธ๋ฆผ๊ณผ ๊ฐ™์ด ์ƒ์„ฑ๋œ ํด๋ผ์ด์–ธํŠธ ์ •๋ณด๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๊ณ  ์ƒ์„ฑ๋œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ํด๋ฆญํ•˜๋ฉด ๊ทธ๋ฆผ๊ณผ ๊ฐ™์ด ์ธ์ฆ ์ •๋ณด๋ฅผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค 

์ƒ์„ฑ๋œ ํด๋ผ์ด์–ธํŠธ ๋ชฉ๋ก

ํด๋ผ์ด์–ธํŠธ ID์™€ ํด๋ผ์ด์–ธํŠธ ๋ณด์•ˆ ๋น„๋ฐ€ ์ฝ”๋“œ๋ฅผ ํ”„๋กœ์ ํŠธ์—์„œ ์„ค์ •ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค

  • application-oauth ๋“ฑ๋ก

4์žฅ์—์„œ ๋งŒ๋“ค์—ˆ๋˜ application.properties๊ฐ€ ์žˆ๋Š” sec/main/resources/๋””๋ ‰ํ† ๋ฆฌ์— application-oauth.properties ํŒŒ์ผ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค 

 

 

 

 

5.3 ๊ตฌ๊ธ€ ๋กœ๊ทธ์ธ ์—ฐ๋™ํ•˜๊ธฐ

๊ตฌ๊ธ€์˜ ๋กœ๊ทธ์ธ ์ธ์ฆ์ •๋ณด๋ฅผ ๋ฐœ๊ธ‰ ๋ฐ›์•˜์œผ๋‹ˆ ํ”„๋กœ์ ํŠธ ๊ตฌํ˜„์„ ์ง„ํ–‰ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

๋จผ์ € ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๋‹ด๋‹นํ•  ๋„์—์ธ์ธ User ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

ํŒจํ‚ค์ง€๋Š” domain ์•„๋ž˜์— user ํŒจํ‚ค์ง€๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

package study.springboot.domain.user;

import jakarta.persistence.*;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import study.springboot.domain.BaseTimeEntity;

import javax.management.relation.Role;

@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(nullable = false)
    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();
    }
}

@Getter

@NoArgsConstructor

@Entity

@Id

@GeneratedValue(strategy = GenerationType.IDENTITY)

@Column(nullable = false)

private String picture

@Enumerated(EnumType.STRING)

@Builder

 

@Enumerated(EnumType.STRING)

  • JPA๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋กœ ์ €์žฅํ•  ๋•Œ Enum ๊ฐ’์„ ์–ด๋–ค ํ˜•ํƒœ๋กœ ์ €์žฅํ• ์ง€๋ฅผ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค
  • ๊ธฐ๋ณธ์ ์œผ๋กœ๋Š” int๋กœ ๋œ ์ˆซ์ž๋กœ ์ €์žฅ๋ฉ๋‹ˆ๋‹ค
  • ์ˆซ์ž๋กœ ์ €์žฅ๋˜๋ฉด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋กœ ํ™•์ธํ•  ๋•Œ ๊ทธ ๊ฐ’์ด ๋ฌด์Šจ ์ฝ”๋“œ๋ฅผ ์˜๋ฏธํ•˜๋Š”์ง€ ์•Œ ์ˆ˜๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค
  • ๊ทธ๋ž˜์„œ ๋ฌธ์ž์—ด (EnumType.STRING)๋กœ ์ €์žฅ๋  ์ˆ˜ ์žˆ๋„๋ก ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค 

 

 

 

 

๊ฐ ์‚ฌ์šฉ์ž์˜ ๊ถŒํ•œ์„ ๊ด€๋ฆฌํ•  Enum ํด๋ž˜์Šค Role์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

package study.springboot.domain.user;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public enum Role {
    
    GUEST("ROLE_GUEST", "์†๋‹˜"),
    USER("ROLE_USER", "์ผ๋ฐ˜ ์‚ฌ์šฉ์ž");
    
    private final String key;
    private final String title;
    
}

์Šคํ”„๋ง ์‹œํ๋ฆฌํ‹ฐ์—์„œ๋Š” ๊ถŒํ•œ ์ฝ”๋“œ์— ํ•ญ์ƒ ROLE_์ด ์•ž์— ์žˆ์–ด์•ผ๋งŒ ํ•ฉ๋‹ˆ๋‹ค

๊ทธ๋ž˜์„œ ์ฝ”๋“œ๋ณ„ ํ‚ค ๊ฐ’์„ ROLE_GUEST, ROLE_USER ๋“ฑ์œผ๋กœ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค

 

๋งˆ์ง€๋ง‰์œผ๋กœ User์˜ CRUD๋ฅผ ์ฑ…์ž„์งˆ UserRepository๋„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค

package study.springboot.domain.user;

import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

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

findByEmail

  • ์†Œ์…œ ๋กœ๊ทธ์ธ์œผ๋กœ ๋ฐ˜ํ™˜๋˜๋Š” ๊ฐ’ ์ค‘ email์„ ํ†ตํ•ด ์ด๋ฏธ ์ƒ์„ฑ๋œ ์‚ฌ์šฉ์ž์ธ์ง€ ์ฒ˜์Œ ๊ฐ€์ž…ํ•˜๋Š” ์‚ฌ์šฉ์ž์ธ์ง€ ํŒ๋‹จํ•˜๊ธฐ ์œ„ํ•œ ๋ฉ”์†Œ๋“œ์ž„ 

User ์—”ํ‹ฐํ‹ฐ ๊ด€๋ จ ์ฝ”๋“œ๋ฅผ ๋ชจ๋‘ ์ž‘์„ฑํ–ˆ์œผ๋‹ˆ ๋ณธ๊ฒฉ์ ์œผ๋กœ ์‹œํ๋ฆฌํ‹ฐ ์„ค์ •์„ ์ง„ํ–‰ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค