feat(auth): 애플 로그인 API — Sign in with Apple identity token 검증(JWKS)
Deploy / deploy (push) Failing after 12m48s
Deploy / deploy (push) Failing after 12m48s
- POST /api/auth/apple — nimbus-jose-jwt 로 애플 공개키(JWKS) 서명 검증 + iss/aud(번들ID)/exp 확인 - member.apple_id 컬럼(멱등 ALTER) + findByAppleId/linkApple, 구글과 동일한 이메일 자동연결 로직 - app.apple-client-id 설정(기본 kr.sblog.slimbudget) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -79,6 +79,8 @@ app:
|
||||
public-url: ${PUBLIC_URL:https://app.sblog.kr}
|
||||
# 구글 OAuth 웹 클라이언트 ID (Google Cloud Console 발급). 미설정 시 구글 로그인 비활성.
|
||||
google-client-id: ${GOOGLE_CLIENT_ID:}
|
||||
# 애플 로그인 aud(=iOS 앱 번들 ID). 네이티브 Sign in with Apple 의 identity token 대상값.
|
||||
apple-client-id: ${APPLE_CLIENT_ID:kr.sblog.slimbudget}
|
||||
|
||||
# ===== Logging =====
|
||||
logging:
|
||||
|
||||
@@ -27,6 +27,10 @@ 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;
|
||||
|
||||
-- 애플 로그인: 애플 sub 를 apple_id 로 저장(구글과 동일 — LOCAL 계정에 연결해도 provider 유지). 멱등.
|
||||
ALTER TABLE member ADD COLUMN IF NOT EXISTS apple_id VARCHAR(100) NULL COMMENT '애플 sub (계정 연결용)' AFTER google_id;
|
||||
ALTER TABLE member ADD UNIQUE KEY IF NOT EXISTS uk_member_apple_id (apple_id);
|
||||
|
||||
-- 멤버십 플랜 (무료/유료). 기존 회원은 FREE 로 시작 (멱등).
|
||||
ALTER TABLE member ADD COLUMN IF NOT EXISTS plan VARCHAR(20) NOT NULL DEFAULT 'FREE' COMMENT 'FREE / PREMIUM' AFTER role;
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<result property="provider" column="provider"/>
|
||||
<result property="providerId" column="provider_id"/>
|
||||
<result property="googleId" column="google_id"/>
|
||||
<result property="appleId" column="apple_id"/>
|
||||
<result property="googlePicture" column="google_picture"/>
|
||||
<result property="profileImage" column="profile_image"/>
|
||||
<result property="role" column="role"/>
|
||||
@@ -26,7 +27,7 @@
|
||||
</resultMap>
|
||||
|
||||
<sql id="columns">
|
||||
id, login_id, password, name, email, provider, provider_id, google_id, google_picture, profile_image, role, plan, plan_expires_at, plan_product, plan_auto_renew, points, status, created_at, updated_at
|
||||
id, login_id, password, name, email, provider, provider_id, google_id, apple_id, google_picture, profile_image, role, plan, plan_expires_at, plan_product, plan_auto_renew, points, status, created_at, updated_at
|
||||
</sql>
|
||||
|
||||
<select id="findById" parameterType="long" resultMap="memberResultMap">
|
||||
@@ -68,15 +69,34 @@
|
||||
UPDATE member SET google_id = #{googleId}, updated_at = NOW() WHERE id = #{id}
|
||||
</update>
|
||||
|
||||
<!-- 애플 sub 로 회원 조회 (연결된 LOCAL 계정 + 순수 애플 계정 모두 apple_id 로 찾음) -->
|
||||
<select id="findByAppleId" parameterType="string" resultMap="memberResultMap">
|
||||
SELECT <include refid="columns"/> FROM member WHERE apple_id = #{appleId}
|
||||
</select>
|
||||
|
||||
<!-- 애플 로그인 시 같은 이메일의 기존 계정 1건(연결 대상). LOCAL 계정 우선, 애플 미연결만. -->
|
||||
<select id="findByEmailForAppleLink" parameterType="string" resultMap="memberResultMap">
|
||||
SELECT <include refid="columns"/>
|
||||
FROM member
|
||||
WHERE email = #{email} AND status = 'ACTIVE' AND apple_id IS NULL
|
||||
ORDER BY (provider = 'LOCAL') DESC, id ASC
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
<!-- 기존 계정에 애플 연결 (provider 는 그대로 유지) -->
|
||||
<update id="linkApple">
|
||||
UPDATE member SET apple_id = #{appleId}, 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, google_id, google_picture, role, status, created_at, updated_at)
|
||||
INSERT INTO member (login_id, password, name, email, provider, provider_id, google_id, apple_id, google_picture, role, status, created_at, updated_at)
|
||||
VALUES (#{loginId}, #{password}, #{name}, #{email},
|
||||
#{provider}, #{providerId}, #{googleId}, #{googlePicture}, #{role}, #{status}, NOW(), NOW())
|
||||
#{provider}, #{providerId}, #{googleId}, #{appleId}, #{googlePicture}, #{role}, #{status}, NOW(), NOW())
|
||||
</insert>
|
||||
|
||||
<update id="updatePassword">
|
||||
|
||||
Reference in New Issue
Block a user