- member.google_id 컬럼 추가(멱등 ALTER) + 기존 GOOGLE 계정 sub 백필 - googleLogin: google_id 조회 → 없으면 검증된 동일 이메일 계정(LOCAL 우선)에 연결 → 그래도 없으면 신규 생성. provider 는 유지(아이디 로그인/프로필 편집 보존) - MemberMapper: findByGoogleId/findByEmailForLink/linkGoogle 추가, google_id 매핑 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -23,8 +23,9 @@ public class Member implements Serializable {
|
||||
private String password; // BCrypt 해시 (소셜 전용은 null)
|
||||
private String name;
|
||||
private String email;
|
||||
private String provider; // LOCAL / NAVER
|
||||
private String provider; // LOCAL / NAVER / GOOGLE
|
||||
private String providerId; // 소셜 제공자 측 고유 ID
|
||||
private String googleId; // 구글 sub (계정 연결용 — LOCAL 계정에 구글을 연결해도 provider 는 유지)
|
||||
private String role; // USER / ADMIN
|
||||
private String status; // ACTIVE / SUSPENDED / WITHDRAWN
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@@ -22,6 +22,15 @@ public interface MemberMapper {
|
||||
Member findByProvider(@Param("provider") String provider,
|
||||
@Param("providerId") String providerId);
|
||||
|
||||
/** 구글 sub 로 회원 조회 (연결된 LOCAL 계정 + 순수 구글 계정 모두). */
|
||||
Member findByGoogleId(@Param("googleId") String googleId);
|
||||
|
||||
/** 구글 로그인 시 연결 대상이 될 같은 이메일의 기존 계정(LOCAL 우선, 미연결만). */
|
||||
Member findByEmailForLink(@Param("email") String email);
|
||||
|
||||
/** 기존 계정에 구글 sub 연결 (provider 유지). */
|
||||
int linkGoogle(@Param("id") Long id, @Param("googleId") String googleId);
|
||||
|
||||
int countByLoginId(@Param("loginId") String loginId);
|
||||
|
||||
int insert(Member member);
|
||||
|
||||
@@ -183,12 +183,30 @@ public class AuthService {
|
||||
}
|
||||
String sub = String.valueOf(info.get("sub"));
|
||||
String email = info.get("email") == null ? null : String.valueOf(info.get("email"));
|
||||
boolean emailVerified = "true".equals(String.valueOf(info.get("email_verified")));
|
||||
String name = info.get("name") != null ? String.valueOf(info.get("name"))
|
||||
: (email != null ? email.split("@")[0] : "사용자");
|
||||
|
||||
Member member = memberMapper.findByProvider("GOOGLE", sub);
|
||||
// 1) 구글 sub 로 이미 연결/가입된 계정 조회
|
||||
Member member = memberMapper.findByGoogleId(sub);
|
||||
|
||||
// 2) 없으면 같은 (검증된)이메일의 기존 계정에 구글 연결 — 중복 계정/데이터 분리 방지
|
||||
if (member == null && email != null && !email.isBlank() && emailVerified) {
|
||||
Member existing = memberMapper.findByEmailForLink(email);
|
||||
if (existing != null) {
|
||||
if (!"ACTIVE".equals(existing.getStatus())) {
|
||||
throw new ApiException(HttpStatus.FORBIDDEN, "사용할 수 없는 계정입니다.");
|
||||
}
|
||||
memberMapper.linkGoogle(existing.getId(), sub);
|
||||
existing.setGoogleId(sub);
|
||||
member = existing;
|
||||
log.info("[google] linked to existing member id={} provider={} email={}",
|
||||
member.getId(), member.getProvider(), email);
|
||||
}
|
||||
}
|
||||
|
||||
// 3) 그래도 없으면 신규 구글 계정 생성(최초 로그인 = 가입). 가입 제한 시 차단.
|
||||
if (member == null) {
|
||||
// 최초 로그인 = 가입. 가입 제한 시 차단.
|
||||
if (!appSettingService.isSignupEnabled()) {
|
||||
throw new ApiException(HttpStatus.FORBIDDEN, "현재 회원가입이 제한되어 있습니다.");
|
||||
}
|
||||
@@ -197,6 +215,7 @@ public class AuthService {
|
||||
.email(email)
|
||||
.provider("GOOGLE")
|
||||
.providerId(sub)
|
||||
.googleId(sub)
|
||||
.role("USER")
|
||||
.status("ACTIVE")
|
||||
.build();
|
||||
|
||||
@@ -20,6 +20,13 @@ CREATE TABLE IF NOT EXISTS member (
|
||||
UNIQUE KEY uk_member_provider (provider, provider_id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- 구글 계정 연결: 기존 LOCAL 계정에 구글 sub 를 연결해 같은 이메일이면 한 계정으로 통합한다.
|
||||
-- (provider 는 LOCAL 로 유지 → 아이디/비밀번호 로그인·프로필 편집 그대로) MariaDB IF NOT EXISTS 로 멱등.
|
||||
ALTER TABLE member ADD COLUMN IF NOT EXISTS google_id VARCHAR(100) NULL COMMENT '구글 sub (계정 연결용)' AFTER provider_id;
|
||||
ALTER TABLE member ADD UNIQUE KEY IF NOT EXISTS uk_member_google_id (google_id);
|
||||
-- 기존 구글 계정(provider=GOOGLE)의 sub 를 google_id 로 백필(멱등 — 이미 채워졌으면 아무 것도 안 함).
|
||||
UPDATE member SET google_id = provider_id WHERE provider = 'GOOGLE' AND google_id IS NULL AND provider_id IS NOT NULL;
|
||||
|
||||
-- ============================================================
|
||||
-- 앱 전역 설정 (단일 행, id=1). 관리자 화면에서 변경.
|
||||
-- ============================================================
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<result property="email" column="email"/>
|
||||
<result property="provider" column="provider"/>
|
||||
<result property="providerId" column="provider_id"/>
|
||||
<result property="googleId" column="google_id"/>
|
||||
<result property="role" column="role"/>
|
||||
<result property="status" column="status"/>
|
||||
<result property="createdAt" column="created_at"/>
|
||||
@@ -18,7 +19,7 @@
|
||||
</resultMap>
|
||||
|
||||
<sql id="columns">
|
||||
id, login_id, password, name, email, provider, provider_id, role, status, created_at, updated_at
|
||||
id, login_id, password, name, email, provider, provider_id, google_id, role, status, created_at, updated_at
|
||||
</sql>
|
||||
|
||||
<select id="findById" parameterType="long" resultMap="memberResultMap">
|
||||
@@ -39,15 +40,34 @@
|
||||
WHERE provider = #{provider} AND provider_id = #{providerId}
|
||||
</select>
|
||||
|
||||
<!-- 구글 sub 로 회원 조회 (연결된 LOCAL 계정 + 순수 구글 계정 모두 google_id 로 찾음) -->
|
||||
<select id="findByGoogleId" parameterType="string" resultMap="memberResultMap">
|
||||
SELECT <include refid="columns"/> FROM member WHERE google_id = #{googleId}
|
||||
</select>
|
||||
|
||||
<!-- 구글 로그인 시 같은 이메일의 기존 계정 1건(연결 대상). LOCAL 계정 우선. -->
|
||||
<select id="findByEmailForLink" parameterType="string" resultMap="memberResultMap">
|
||||
SELECT <include refid="columns"/>
|
||||
FROM member
|
||||
WHERE email = #{email} AND status = 'ACTIVE' AND google_id IS NULL
|
||||
ORDER BY (provider = 'LOCAL') DESC, id ASC
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
<!-- 기존 계정에 구글 연결 (provider 는 그대로 유지) -->
|
||||
<update id="linkGoogle">
|
||||
UPDATE member SET google_id = #{googleId}, updated_at = NOW() WHERE id = #{id}
|
||||
</update>
|
||||
|
||||
<select id="countByLoginId" parameterType="string" resultType="int">
|
||||
SELECT COUNT(*) FROM member WHERE login_id = #{loginId}
|
||||
</select>
|
||||
|
||||
<insert id="insert" parameterType="com.sb.web.auth.domain.Member"
|
||||
useGeneratedKeys="true" keyProperty="id">
|
||||
INSERT INTO member (login_id, password, name, email, provider, provider_id, role, status, created_at, updated_at)
|
||||
INSERT INTO member (login_id, password, name, email, provider, provider_id, google_id, role, status, created_at, updated_at)
|
||||
VALUES (#{loginId}, #{password}, #{name}, #{email},
|
||||
#{provider}, #{providerId}, #{role}, #{status}, NOW(), NOW())
|
||||
#{provider}, #{providerId}, #{googleId}, #{role}, #{status}, NOW(), NOW())
|
||||
</insert>
|
||||
|
||||
<update id="updatePassword">
|
||||
|
||||
Reference in New Issue
Block a user