Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
08d31e9
[FEAT/#359] 테스트 전용 백오피스 환경변수 추가
2ghrms Jun 22, 2026
8175185
[FEAT/#359] 권한 분기 명시하기 위해서 컨트롤러마다 PreAuthorize 어노테이션 추가
2ghrms Jun 22, 2026
61a17fa
[FEAT/#359] Backoffice 역할 추가 / 엔티티 및 도메인 모델 작성
2ghrms Jun 22, 2026
bb26ccd
[FEAT/#359] 기존 로그인과 분리하여 Backoffice 전용 로그인 작성
2ghrms Jun 22, 2026
bf7105d
[FEAT/#359] 백오피스 운영 API 전용 컨트롤러
2ghrms Jun 22, 2026
491440c
[FEAT/#359] 백오피스 추가로 인한 Role 기반 권한 강화
2ghrms Jun 22, 2026
c09518d
[FEAT/#359] 백오피스 추가로 인한 에러 추가
2ghrms Jun 22, 2026
0e1ecc1
[FEAT/#359] @PreAuthorize 활성화
2ghrms Jun 22, 2026
5e7b097
[FEAT/#359] 401, 403 권한 관련 에러시 응답 규격 통일 위해 핸들러 추가
2ghrms Jun 22, 2026
3c52471
[FEAT/#359] 401, 403 권한 관련 에러시 응답 규격 통일 위해 핸들러 추가 및 URL RBAC (/backof…
2ghrms Jun 22, 2026
cc595a2
[FEAT/#359] Spring AOP 추가
2ghrms Jun 22, 2026
a7bac80
[FEAT/#359] 기존 로그인과 분리하여 Backoffice 전용 로그인 작성
2ghrms Jun 22, 2026
c57c1bc
[FEAT/#359] 백오피스 계정 초기세팅을 돕기 위해 이니셜라이저 작성
2ghrms Jun 22, 2026
b08f115
[FEAT/#359] 백오피스 계정 관리를 위한 CRUD 작성
2ghrms Jun 22, 2026
f7ea65f
[FEAT/#359] 백오피스 감사 로그 추적을 위한 AOP 작성
2ghrms Jun 22, 2026
87333ba
[TEST/#359] 백오피스 테스트
2ghrms Jun 22, 2026
c5c8e8a
[TEST/#359] 오타 수정
2ghrms Jun 22, 2026
af5d78f
[TEST/#359] 스웨거 컨벤션 반영
2ghrms Jun 22, 2026
fd3f8c5
[FIX/#359] 오타 수정
2ghrms Jun 22, 2026
2ab4b96
Merge branch 'feat/#359-admin-api-authorization' of https://github.co…
2ghrms Jun 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ dependencies {

// spring security
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-aop'
testImplementation 'org.springframework.security:spring-security-test'

// redis
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
Expand All @@ -17,6 +18,7 @@
@RestController
@RequiredArgsConstructor
@RequestMapping("/admin")
@PreAuthorize("hasRole('ADMIN')")
public class AdminController {

private final AdminService adminService;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
Expand All @@ -17,6 +18,7 @@
@RequiredArgsConstructor
@RequestMapping("/admin/dashBoard")
@Tag(name = "Admin Dashboard", description = "관리자 대시보드 및 통계 API")
@PreAuthorize("hasRole('ADMIN')")
public class StudentAdminController {

private final StudentAdminService studentAdminService;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;

@Tag(name = "App Review", description = "앱 리뷰 API")
@RestController
@RequiredArgsConstructor
@RequestMapping("/app-reviews")
@PreAuthorize("hasRole('STUDENT')")
public class AppReviewController {

private final AppReviewService appReviewService;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package com.assu.server.domain.auth.controller;

import com.assu.server.domain.auth.dto.backoffice.BackofficeLoginResponseDTO;
import com.assu.server.domain.auth.dto.login.CommonLoginRequestDTO;
import com.assu.server.domain.auth.dto.login.RefreshResponseDTO;
import com.assu.server.domain.auth.service.BackofficeAuthService;
import com.assu.server.global.apiPayload.BaseResponse;
import com.assu.server.global.apiPayload.code.status.SuccessStatus;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Tag(name = "Backoffice Auth", description = "백오피스 전용 인증 API")
@RestController
@RequiredArgsConstructor
@RequestMapping("/auth/backoffice")
public class BackofficeAuthController {

private final BackofficeAuthService backofficeAuthService;

@Operation(
summary = "백오피스 로그인 API",
description = "# [v1.0 (2026-06-23)]\n" +
"- `application/json`으로 호출합니다.\n" +
"- 바디: `CommonLoginRequestDTO(email, password)`.\n" +
"- `BACKOFFICE` 역할 계정만 로그인할 수 있습니다.\n" +
"- 처리: 자격 증명 검증 후 `aud=backoffice` Access/Refresh 토큰 발급 및 저장.\n" +
"- 성공 시 200(OK)과 토큰(accessToken/refreshToken), 기본 정보 반환.\n" +
"\n**Request Body:**\n" +
" - `CommonLoginRequestDTO` 객체 (JSON, required): 로그인 정보\n" +
" - `email` (String, required): 이메일 주소\n" +
" - `password` (String, required): 비밀번호\n" +
"\n**Response:**\n" +
" - 성공 시 200(OK)과 `BackofficeLoginResponseDTO` 객체 반환\n" +
" - `memberId` (Long): 회원 ID\n" +
" - `role` (UserRole): BACKOFFICE\n" +
" - `status` (ActivationStatus): 회원 상태\n" +
" - `tokens` (Object): JWT 토큰 정보 (accessToken, refreshToken)\n" +
" - `basicInfo` (UserBasicInfo): 운영자 이름 등 기본 정보\n" +
" - 403(FORBIDDEN): BACKOFFICE 역할이 아닌 계정\n" +
" - 401(UNAUTHORIZED): 이메일 또는 비밀번호 불일치"
)
@PostMapping(value = "/login", consumes = MediaType.APPLICATION_JSON_VALUE)
public BaseResponse<BackofficeLoginResponseDTO> login(
@io.swagger.v3.oas.annotations.parameters.RequestBody(
required = true,
description = "백오피스 로그인 요청",
content = @Content(
mediaType = MediaType.APPLICATION_JSON_VALUE,
schema = @Schema(implementation = CommonLoginRequestDTO.class)
)
)
@RequestBody
@Valid
CommonLoginRequestDTO request
) {
return BaseResponse.onSuccess(SuccessStatus._OK, backofficeAuthService.login(request));
}

@Operation(
summary = "백오피스 Access Token 갱신 API",
description = "# [v1.0 (2026-06-23)]\n" +
"- 헤더로 호출합니다.\n" +
"- 헤더: `RefreshToken: <refreshToken>`.\n" +
"- `aud=backoffice` Refresh Token만 갱신할 수 있습니다.\n" +
"- 처리: Refresh 검증/회전 후 신규 Access/Refresh 발급 및 저장.\n" +
"- 성공 시 200(OK)과 신규 토큰 반환.\n" +
"\n**Headers:**\n" +
" - `RefreshToken` (String, required): 백오피스 리프레시 토큰\n" +
"\n**Response:**\n" +
" - 성공 시 200(OK)과 `RefreshResponseDTO` 객체 반환\n" +
" - `memberId` (Long): 회원 ID\n" +
" - `newAccess` (String): 새로운 액세스 토큰\n" +
" - `newRefresh` (String): 새로운 리프레시 토큰\n" +
" - 401(UNAUTHORIZED): audience 불일치 또는 유효하지 않은 Refresh Token"
)
@PostMapping("/tokens/refresh")
public BaseResponse<RefreshResponseDTO> refresh(
@Parameter(
name = "RefreshToken",
description = "Backoffice Refresh Token",
required = true,
in = ParameterIn.HEADER,
schema = @Schema(type = "string")
)
@RequestHeader("RefreshToken")
String refreshToken
) {
return BaseResponse.onSuccess(SuccessStatus._OK, backofficeAuthService.refresh(refreshToken));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.assu.server.domain.auth.dto.backoffice;

import com.assu.server.domain.auth.dto.common.TokensDTO;
import com.assu.server.domain.auth.dto.common.UserBasicInfoDTO;
import com.assu.server.domain.common.enums.ActivationStatus;
import com.assu.server.domain.common.enums.UserRole;
import com.assu.server.domain.member.entity.Member;
import io.swagger.v3.oas.annotations.media.Schema;

@Schema(description = "백오피스 로그인 성공 응답")
public record BackofficeLoginResponseDTO(
@Schema(description = "회원 ID", example = "1")
Long memberId,

@Schema(description = "회원 역할", example = "BACKOFFICE")
UserRole role,

@Schema(description = "회원 상태", example = "ACTIVE")
ActivationStatus status,

@Schema(description = "액세스/리프레시 토큰")
TokensDTO tokens,

@Schema(description = "운영자 기본 정보")
UserBasicInfoDTO basicInfo
) {
public static BackofficeLoginResponseDTO from(Member member, TokensDTO tokens) {
return new BackofficeLoginResponseDTO(
member.getId(),
member.getRole(),
member.getIsActivated(),
tokens,
UserBasicInfoDTO.from(member)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ public static UserBasicInfoDTO from(Member member) {
name = partner.getName();
}
}
case BACKOFFICE -> {
var backofficeUser = member.getBackofficeProfile();
if (backofficeUser != null) {
name = backofficeUser.getName();
}
}
}

return new UserBasicInfoDTO(name, university, department, major);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,14 @@ public void registerCredentials(Member member, String email, String rawPassword)
throw new CustomAuthException(ErrorStatus.EXISTED_EMAIL);
}
String hash = passwordEncoder.encode(rawPassword);
commonAuthRepository.save(
CommonAuth.builder()
.member(member)
.email(email)
.hashedPassword(hash)
.lastLoginAt(LocalDateTime.now())
.build()
);
CommonAuth commonAuth = CommonAuth.builder()
.member(member)
.email(email)
.hashedPassword(hash)
.lastLoginAt(LocalDateTime.now())
.build();
commonAuthRepository.save(commonAuth);
member.setCommonAuth(commonAuth);
}

@Override
Expand Down
Loading
Loading