본문으로 건너뛰기
ElastiCORE 3.0 은 릴리스 후보(RC)입니다: Maven Central 의 3.0.261022-rc1. 2.2 라인 프로젝트는 2.2 문서를 보세요.
버전: 3.0

권한과 로그인

누가 무엇을 할 수 있는지는 엔티티의 meta: 줄에 적고, 다시 생성되는 코어 서비스에서 검사합니다 - 개발자 소유인 컨트롤러가 아니라서, DSL 을 고치면 다음 빌드에 모든 진입점에 반영됩니다. 3.0 규칙 엔진과 jpa-service-access 가 든 번들이 필요합니다: 모든 v3 번들(DDD, 헥사고날, -Pv3, Kotlin). 2.x 생성기는 이 어노테이션들을 처리하지 않습니다.

@access 와 @owner​

PurchaseOrder:
meta: entity @root @expose @owner(customerId)
@access(read=owner, create=user, cancel=owner) # ship·delete 는 적지 않음 -> admin
수준누가
public누구나 (로그인 불필요)
user로그인한 사용자
owner그 행의 주인(@owner 필드 = 로그인 ID)과 관리자
admin관리자 - 역할 ADMIN, env.yml 의 config: adminRole: 로 변경
none아무도 (애플리케이션 자신은 가능)

연산 이름: read(조회·검색), create, update, delete, 그리고 명령 이름(ship, cancel ...). 적지 않은 연산은 admin 입니다(*=user 로 기본값 지정). @access 가 없으면 아무것도 검사하지 않습니다.

상황결과
로그인 안 함 + user/owner/admin401 - 제목 Sign in first, 문장 Sign in to <연산> <엔티티> (예: Sign in to create PurchaseOrder)
로그인했지만 권한 없음403
owner: 남의 행 조회·수정·삭제·명령404 - 존재 자체를 알리지 않음
owner: 검색소유자 필드의 동등 조건을 덧붙임 - 내 것만 나옴
create소유자 필드를 로그인 ID 로 - 요청이 무엇을 보냈든 (관리자의 값은 그대로)
update소유자 필드와 @access(admin) 필드는 행의 현재 값으로
owner: findAll, 조건 삭제·조건 수정관리자만
로그인 안 한 호출 (요청 밖, @Async ...)방문자 - fail-closed
CurrentUser.runAsSystem(() -> ...) 안애플리케이션 자신 - 모두 허용

다른 엔티티의 행 전체(중첩 DTO)는 그 엔티티의 읽기 수준이 public 일 때만 DTO 에 실립니다. 아니면 빌드가 그 필드를 빼고 경고합니다(식별자는 남음).

관리자만 쓰는 필드​

Customer:
meta: entity @root @editable @expose @owner(customerId) @access(read=owner, create=public, update=owner)
fields:
role: UserRole @default(UserRole.USER) @access(admin) # 가입자가 스스로 관리자가 될 수 없음

다른 사람이 보낸 값은 생성 시 엔티티 기본값으로, 수정 시 행의 현재 값으로 바뀝니다.

@password 와 @login​

login:    string(10)! @login @unique
password: string(255)! @password # 해시 길이를 담을 만큼 (PBKDF2 약 100자)
  • 요청으로 받은 비밀번호는 모든 쓰기 경로에서 해시로만 저장합니다(기본 PBKDF2-SHA256, PasswordHasher 빈으로 교체). 응답·검색·DTO 복사에 나오지 않고, TypeScript 에서는 optional 입니다.
  • @login 과 @password 가 함께 있으면 코어 서비스와 유스케이스에 authenticate(login, password) 가 생깁니다: 그 행, 또는 로그인이 없거나 두 행 이상이거나 비밀번호가 틀리면 비어 있음. 없는 로그인도 같은 해시 비용을 치러 응답 시간으로 계정 존재를 알 수 없습니다.
  • 로그인은 유일해야 합니다: @signin 없이 쓰는 엔티티의 로그인이 @unique(또는 키, 그 컬럼 하나만의 unique @Index)가 아니면 DSL 경고입니다.

@signin - 로그인 API 생성​

Customer:
meta: entity @root @editable @expose(20) @signin @owner(customerId) @access(read=owner, create=public, update=owner)
fields:
customerId: long @id @sequence
login: string(10)! @search(eq) @login
password: string(255)! @password
role: UserRole @default(UserRole.USER) @access(admin)
요청답
POST /api/shop/customer/signin {"login": "...", "password": "..."} (키는 두 필드의 이름)200 고객 (비밀번호 없음), 세션 시작. 틀리면 401 Wrong login or password
POST /api/shop/customer/signout204, 세션 끝
GET /api/shop/customer/me200 로그인한 고객. 방문자는 401

코어 서비스·유스케이스에 me() 가, TypeScript 클라이언트에 signIn(login, password), signOut(), me()(방문자면 null)가 생깁니다. 쿠키 세션이라 화면과 API 가 같은 출처일 때 동작합니다. @login 컬럼에는 unique = true 가 붙어, 이미 있는 로그인은 409 입니다.

세션에는 행의 키와 역할이 들어갑니다. 역할은 @role 을 붙인 필드, 없으면 role 이라는 필드이고, 관리자만 쓰는 필드(@access(admin))인 enum 이나 문자열일 때만 씁니다. enum 이면 상수 이름(ADMIN)을 adminRole 과 비교합니다. 그렇지 않으면 가입자가 "role":"ADMIN" 을 보내고 관리자로 로그인할 수 있으므로, 빌드가 경고하고 역할 없이 로그인합니다.

@signin 은 안전할 때만 생성됩니다 - 아니면 DSL 경고가 이유와 고칠 문장을 알려 줍니다:

  • 그 행은 본인과 관리자만 바꿀 수 있어야 합니다: update=owner 와 키를 가리키는 @owner, 또는 update=admin·none. (@access 가 없으면 수정 수준이 public 이라 아무나 관리자의 비밀번호를 바꿀 수 있습니다.)
  • 키를 요청이 정하면(@genid·@sequence 가 아님) 삭제도 같게 제한됩니다 - 아니면 누가 alice 를 지우고 alice 로 다시 가입할 수 있습니다.
  • @expose, 키 하나, @login 과 @password.
  • 애플리케이션에 @signin 엔티티는 하나 - 세션이 맨 키만 들고 있어, 둘이면 서로의 행으로 읽힙니다. 멀티 모듈은 컨트롤러가 생기는 모듈마다 셉니다.

계정 잠금·시도 횟수 같은 로그인 규칙은 개발자 소유 서비스에서 authenticate(login, password) 를 override 해 씁니다 - 생성된 signin 이 그것을 부릅니다. 컨트롤러는 개발자 소유라 손댄 컨트롤러에는 엔드포인트가 들어가지 않고, 빌드가 빠진 것을 알려 줍니다.

손으로 세션 다루기​

가입하고 바로 로그인 같은 나만의 흐름:

SessionCurrentUser.signIn(request, customer.getCustomerId(), customer.getRole().name());
SessionCurrentUser.signOut(request);

Spring Security 를 쓰면 io.elasticore.springboot3.security.CurrentUser 빈을 등록하고 @signin 은 쓰지 않습니다 (authenticate 는 남음).

런타임 클래스​

CurrentUser - id(), signedIn(), hasRole("ADMIN")생성된 서비스가 보는 "지금 묻는 사람" (기본 SessionCurrentUser)
CurrentUser.runAsSystem(() -> ...)애플리케이션 자신: 시작 데이터, 야간 작업
ACCESS.require(currentUser, "report")직접 만든 연산에 엔티티의 @access 적용 (생성된 서비스의 필드)
AccessPolicy, PasswordHasher바꿀 수 있는 빈
NotSignedInException (401), ForbiddenException (403)io.elasticore.springboot3.security

생성되는 서비스 테스트(testCode: true)는 접근 규칙이 있는 엔티티를 애플리케이션 자신으로 실행합니다 (RunAsSystemExtension). 권한 자체를 시험하는 테스트는 사용자로 로그인해서 씁니다.