728x90

🧩 전반적인 구조 한눈에 보기

 
[사용자 로그인 요청(OAuth)] → [SecurityConfig - 설정]  
    ↓
Spring이 OAuth2UserService를 호출
    ↓
[MemberService] → OAuth2 플랫폼에서 사용자 정보 받아옴  
    ↓
[MemberRepository] 통해 DB에서 사용자 찾기 or 회원가입  
    ↓
[Member] 엔티티로 DB 저장  
    ↓
[MemberDetails]로 스프링 시큐리티에 사용자 정보 전달 (인증객체로 사용)

이 흐름을 기준으로 생각하면 코드이해가 더 잘된다!!!

 

 

 


✅ 1. ApiController - API 테스트용 컨트롤러

@RestController
public class ApiController {

    @GetMapping("/user/test")
    public String userTest() {
        return "User Test!";
    }

    @GetMapping("/admin/test")
    public String adminTest() {
        return "Admin Test!";
    }
}

✔️ 역할

  • OAuth 인증이 완료된 사용자가 실제 접근하게 될 리소스를 테스트하기 위한 컨트롤러.
  • /user/test → USER 또는 ADMIN 권한이 있어야 접근 가능
  • /admin/test → ADMIN 권한만 있어야 접근 가능 (이건 SecurityConfig에서 지정)
 
 

 

 

 

✅ 2. SecurityConfig - 보안 설정 클래스

@Configuration
public class SecurityConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http
                .csrf(AbstractHttpConfigurer::disable)
                .formLogin(AbstractHttpConfigurer::disable)
                .oauth2Login(Customizer.withDefaults())
                .authorizeHttpRequests(auth -> {
                    auth.requestMatchers("/login", "/sign-up").anonymous()
                        .requestMatchers("user/**").hasAnyAuthority("USER", "ADMIN")
                        .requestMatchers("admin/**").hasAnyAuthority("ADMIN")
                        .anyRequest().authenticated();
                })
                .build();
    }
}

 

✔️ 핵심 설명

  • csrf.disable() : API 서버로 사용하거나 OAuth2에서는 비활성화하는 경우가 많음.
  • formLogin.disable() : 일반적인 로그인 폼을 사용하지 않겠다는 의미. 우리는 소셜 로그인(OAuth2)을 쓰니까.
  • oauth2Login(Customizer.withDefaults()) : 기본 설정으로 소셜 로그인 사용하겠다는 의미.
  • authorizeHttpRequests() :
    • anonymous() : 인증 안 된 사람만 접근 가능.
    • hasAnyAuthority(...) : 해당 권한을 가진 사용자만 접근 가능 (여기선 문자열 권한 명시함).
    • authenticated() : 로그인한 사용자만 접근 가능.

 

 

 

 


✅ 3. MemberService - 소셜 로그인 사용자 처리 로직(OAuth2 사용자 처리 서비스)

@Service
@Transactional
@RequiredArgsConstructor
public class MemberService extends DefaultOAuth2UserService {

    private final MemberRepository memberRepository;

    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        String provider = userRequest.getClientRegistration().getRegistrationId();
        OAuth2User oAuth2User = super.loadUser(userRequest);

        Map<String, Object> attributes = oAuth2User.getAttributes();
        String findName = attributes.get("name").toString();
        String email = attributes.get("email").toString();

        Optional<Member> memberOptional = memberRepository.findByEmail(email);

        Member member = memberOptional.orElseGet(() -> {
            Member saved = Member.builder()
                    .name(findName)
                    .email(email)
                    .provider(provider)
                    .build();
            return memberRepository.save(saved);
        });

        return MemberDetails.builder()
                .name(member.getName())
                .role(member.getRole())
                .attributes(attributes)
                .build();
    }
}

❓ 이건 뭐냐면?

→ OAuth 로그인 후, 사용자 정보를 가져오고, 그 사용자가 DB에 있는지 확인하거나 저장하고,
그걸 바탕으로 인증에 사용할 MemberDetails 객체를 만들어주는 클래스

 

 

🎯 핵심 포인트:

  • 스프링이 자동으로 이걸 호출함
  • DefaultOAuth2UserService를 상속해서 loadUser()를 오버라이딩하면 우리가 원하는 로직으로 "로그인 후 처리" 가능

 

 

🛠 역할 순서대로 설명:

public OAuth2User loadUser(OAuth2UserRequest userRequest) {

이 함수는 OAuth 로그인 완료 후 자동으로 호출됨

  1. super.loadUser(userRequest) → 구글/네이버에서 사용자 정보 받아옴
  2. 사용자 이메일을 기준으로 DB에 있는지 확인
  3. 없다면 Member 객체 만들어서 저장
  4. 인증 정보로 사용할 MemberDetails 객체 만들어서 리턴 → Spring Security가 내부에서 인증에 사용

 

 

🧩 MemberService 흐름 예시:

구글로 로그인 → 사용자 정보 받음 (name, email 등)  
→ 우리 DB에 저장된 이메일이 있는지 확인  
  → 있으면 그대로 사용  
  → 없으면 새로 회원가입 (DB 저장)  
→ Spring Security에 인증 정보(MemberDetails) 전달

 

 

✔️ 코드 분석 시작

@Service
@Transactional
@RequiredArgsConstructor
public class MemberService extends DefaultOAuth2UserService {

✔️ 기본 설명

  • DefaultOAuth2UserService를 상속 → Spring Security가 소셜 로그인 사용자 정보를 가져올 때 사용하는 기본 구현체를 오버라이딩.
  • 여기서 유저 정보 받아서 DB에 저장하거나 기존 유저 찾는 로직을 커스터마이징함.

 

 

✔️ 주요 메서드 설명

 
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
  • 이 메서드는 소셜 로그인 후 사용자 정보를 받아올 때 호출됨.
  • OAuth2UserRequest에는 어떤 제공자인지, 엑세스 토큰 등이 담겨 있음.
 
 
OAuth2User oAuth2User = super.loadUser(userRequest);
  • 소셜 제공자로부터 유저 정보를 불러옴. (name, email 등)
 
 
Map<String, Object> attributes = oAuth2User.getAttributes();
  • 사용자 정보 Map. 구글일 경우 이름: name, 이메일: email 등이 들어있음.
 
 
Optional<Member> memberOptional = memberRepository.findByEmail(email);
  • 이미 DB에 존재하는 이메일인지 확인.
 
 
Member member = memberOptional.orElseGet(() -> {
    Member saved = Member.builder()
        .name(findName)
        .email(email)
        .provider(provider)
        .build();
    return memberRepository.save(saved);
});
  • 존재하지 않으면 새로 회원 가입 → DB 저장.
 
 
return MemberDetails.builder()
    .name(member.getName())
    .role(member.getRole())
    .attributes(attributes)
    .build();
  • 인증 객체로 사용할 MemberDetails 객체 반환. (Spring Security가 내부적으로 이 객체를 이용해 인가 처리를 함)
 
 
 
 
 
 

✅ 4. MemberDetails - OAuth2 사용자 정보 구현체

@Getter
public class MemberDetails implements OAuth2User {

    private String name;
    private Map<String, Object> attributes;

    @Setter
    private String role;

    @Builder
    public MemberDetails(String name, String role, Map<String, Object> attributes) {
        this.name = name;
        this.role = role;
        this.attributes = attributes;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return List.of(new SimpleGrantedAuthority(role));
    }
}

❓ 이건 뭐냐면?

→ Spring Security에서 "로그인한 사용자"를 나타내는 인증 객체

 

 

🛠 역할:

  • 로그인한 사용자의 권한 정보 (ROLE_USER, ROLE_ADMIN 등)를 담고 있음
  • Spring Security는 이 객체로 "인가(authorization)" 처리를 함
  • Spring Security에서 인증된 사용자 객체로 사용됨
  • 권한 (role) 을 통해 인가 처리 가능

 

 

🔐 중요한 부분:

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
    return List.of(new SimpleGrantedAuthority(role));
}
  • 사용자 권한을 SimpleGrantedAuthority로 감싸서 리턴해줌 → Spring Security는 여기서 권한 읽음
  • 예: role = "ADMIN" → new SimpleGrantedAuthority("ADMIN")

 

 

📌 왜 우리가 직접 만들어야 해?

→ Spring Security의 기본 OAuth2User는 우리가 원하는 필드(name, role 등)를 다루기 어려움
→ 우리가 원하는 필드를 가진 MemberDetails 객체로 커스터마이징

 

 

✔️ 코드 분석 시작

public class MemberDetails implements OAuth2User {
  • OAuth2User 인터페이스를 구현 → Spring Security에서 OAuth 인증 정보를 이 클래스를 통해 처리함.

 

 

✔️ 핵심 필드

private String name;
private String role;
private Map<String, Object> attributes;

 

 

✔️ 주요 메서드

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
    return List.of(new SimpleGrantedAuthority(role));
}
  • 현재 로그인한 사용자의 권한을 반환함 (ROLE_USER, ROLE_ADMIN 등)
  • SimpleGrantedAuthority를 통해 권한 객체 생성.

 

 

 

 


✅ 5. Member - 회원 도메인 엔티티

@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member {

    @Id
    @Column(name = "member_id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private String nickname;

    private String role = "USER";

    private String email;

    private String provider;

    private LocalDateTime signedAt = LocalDateTime.now();

    @Builder
    public Member(String name, String nickname, String email, String provider) {
        this.name = name;
        this.nickname = nickname;
        this.email = email;
        this.provider = provider;
    }
}

❓ 이건 뭐냐면?

→ 데이터베이스의 member 테이블과 연결된 JPA 엔티티 클래스

 

 

🛠 역할:

  • 사용자의 정보를 담는 객체 (name, email, role, provider 등)
  • 이 객체가 그대로 DB의 행(Row) 하나가 됨

 

 

🧱 예시:

Member member = Member.builder()
    .name("철수")
    .email("chulsoo@gmail.com")
    .provider("google")
    .build();

이렇게 하면 DB에 이런 식으로 저장될 수 있어:

id name email provider role signed_at
1 철수 chulsoo@gmail.com google USER 2025-04-16 14:30:00

 

 

🔗 왜 만들어?

  • DB와 데이터를 주고받으려면 “엔티티”가 필요하기 때문에
  • OAuth 로그인으로 받은 정보(email 등)를 이걸로 저장하고 불러오려고 만든 거임!

 

 

✔️ 필드 설명

  • @Id, @GeneratedValue : 기본 키 자동 생성
  • email, provider : OAuth 로그인 시 사용자 식별 정보
  • role : 권한 (기본 "USER"로 초기화됨)
  • signedAt : 가입 시간
  • @Builder 생성자 : 코드에서 객체 만들 때 Member.builder()...build() 형식 사용 가능

 

 

 

 

 


6. MemberRepository - DB 접근 인터페이스

❓ 이건 뭐냐면?

→ Member 엔티티를 DB에서 조회, 저장, 삭제 등을 할 수 있도록 도와주는 DAO (Data Access Object)

 

 

🛠 역할:

  • DB와 직접 연결되는 “통로”
  • 예: 이메일로 사용자 찾기, 회원정보 저장하기
Optional<Member> optional = memberRepository.findByEmail("chulsoo@gmail.com");

 

 

💡왜 Optional로 반환해?

→ 사용자 존재할 수도 있고, 안 할 수도 있으니까.
→ null 방지하고 안전하게 ifPresent 혹은 orElseGet 등으로 처리 가능

 

 

 

✔️ 코드 분석 시작

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

✔️ 역할

  • JPA 기반의 DB CRUD 처리 자동화.
  • 회원 조회/저장 등의 DB 작업 처리
  • findByEmail : 이메일로 유저 조회 가능하게 만드는 메서드 (JPA 쿼리 메서드)
  • findByEmail() : 이메일 기준 회원 조회

 

 

 


🔁 전체 흐름 요약

  1. 사용자가 Google 로그인 버튼 클릭
  2. Spring Security가 OAuth2 인증 요청 → Google 로그인
  3. 인증 후, 사용자 정보가 loadUser()로 전달됨
  4. DB에서 이메일로 기존 유저 있는지 확인
  5. 없으면 새로 저장 (회원가입)
  6. MemberDetails 객체 생성 → Spring Security 내부 인증 완료
  7. 인증된 사용자만 user/test, admin/test 접근 가능

 

 

 


📌 참고 사항

  • 컨트롤러에서 현재 로그인한 사용자 정보를 얻고 싶다면:
@GetMapping("/profile")
public String profile(@AuthenticationPrincipal OAuth2User user) {
    return user.getAttribute("email");
}
  • 실제 서비스에선 권한을 ENUM으로 관리하는 것이 안전하고 유지보수에 좋음.

 

 

 

 


 

728x90