페이지 개선2
This commit is contained in:
parent
d88cfbf5fb
commit
40bde12c6d
|
|
@ -0,0 +1,54 @@
|
|||
-- Security hardening DDL for environments that already have the base tables.
|
||||
-- Review duplicate rows before adding the unique constraint.
|
||||
|
||||
-- Find duplicate login identities before enforcing uniqueness.
|
||||
SELECT
|
||||
"provider",
|
||||
"provider_user_id",
|
||||
COUNT(*) AS duplicate_count
|
||||
FROM "user_auth_identities"
|
||||
WHERE "is_delete" IS NOT TRUE
|
||||
GROUP BY "provider", "provider_user_id"
|
||||
HAVING COUNT(*) > 1;
|
||||
|
||||
-- Enforce one active identity per provider account.
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "ux_user_auth_identities_provider_user_id_active"
|
||||
ON "user_auth_identities" ("provider", "provider_user_id")
|
||||
WHERE "is_delete" IS NOT TRUE;
|
||||
|
||||
-- Add missing recruit_posts constraints safely if the base DDL was applied before it became idempotent.
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint WHERE conname = 'recruit_posts_user_id_fkey'
|
||||
) THEN
|
||||
ALTER TABLE "recruit_posts"
|
||||
ADD CONSTRAINT "recruit_posts_user_id_fkey"
|
||||
FOREIGN KEY ("user_id") REFERENCES "users" ("id");
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint WHERE conname = 'recruit_posts_role_check'
|
||||
) THEN
|
||||
ALTER TABLE "recruit_posts"
|
||||
ADD CONSTRAINT "recruit_posts_role_check"
|
||||
CHECK ("role" IN ('기획', '아트', '프로그래머'));
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint WHERE conname = 'recruit_posts_participation_type_check'
|
||||
) THEN
|
||||
ALTER TABLE "recruit_posts"
|
||||
ADD CONSTRAINT "recruit_posts_participation_type_check"
|
||||
CHECK ("participation_type" IN ('취미', '수익쉐어', '유급', '게임잼'));
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
package com.pandoli365.bibimbap.security;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Base64;
|
||||
import java.util.Map;
|
||||
|
||||
public final class CsrfTokens {
|
||||
|
||||
public static final String SESSION_ATTRIBUTE = "csrfToken";
|
||||
public static final String HEADER_NAME = "X-CSRF-Token";
|
||||
private static final int TOKEN_BYTES = 32;
|
||||
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
|
||||
|
||||
private CsrfTokens() {
|
||||
}
|
||||
|
||||
public static String getOrCreate(HttpSession session) {
|
||||
if (session == null) {
|
||||
return "";
|
||||
}
|
||||
Object existing = session.getAttribute(SESSION_ATTRIBUTE);
|
||||
if (existing instanceof String token && !token.isBlank()) {
|
||||
return token;
|
||||
}
|
||||
byte[] bytes = new byte[TOKEN_BYTES];
|
||||
SECURE_RANDOM.nextBytes(bytes);
|
||||
String token = Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
|
||||
session.setAttribute(SESSION_ATTRIBUTE, token);
|
||||
return token;
|
||||
}
|
||||
|
||||
public static boolean isValid(HttpServletRequest request) {
|
||||
HttpSession session = request == null ? null : request.getSession(false);
|
||||
if (session == null) {
|
||||
return false;
|
||||
}
|
||||
Object expected = session.getAttribute(SESSION_ATTRIBUTE);
|
||||
if (!(expected instanceof String expectedToken) || expectedToken.isBlank()) {
|
||||
return false;
|
||||
}
|
||||
String provided = request.getHeader(HEADER_NAME);
|
||||
if (provided == null || provided.isBlank()) {
|
||||
provided = request.getParameter("_csrf");
|
||||
}
|
||||
return expectedToken.equals(provided);
|
||||
}
|
||||
|
||||
public static Map<String, Object> errorBody() {
|
||||
return Map.of(
|
||||
"status", 403,
|
||||
"message", "요청 보안 토큰이 유효하지 않습니다."
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue