JWT를 사용해 초기 관리자를 설정하고 이를 통해 인증과 권한 관리를 구현하는 법. 사용자와 관리자로 역할을 나누고, 역할에 따라 접근 가능한 API를 RESTful하게 설계 한다!!
1. RESTful API 설계
1. 관리자 전용 API
- 모든 사용자 조회
- 특정 사용자 삭제
2. 사용자 전용 API
- 사용자 프로필 조회
- 일정 생성 및 삭제
3. Public API
- 회원가입
- 로그인
관리자 전용 API
| HTTP Method | URI | 설명 | 권한 |
| GET | /api/admin/users | 모든 사용자 조회 | ADMIN |
| GET | /api/admin/users/{id} | 특정 사용자 조회 | ADMIN |
| DELETE | /api/admin/users/{id} | 사용자 삭제 | ADMIN |
사용자 전용 API
| HTTP Method | URI | 설명 | 권한 |
| GET | /api/user/profile | 사용자 프로필 조회 | USER, ADMIN |
| POST | /api/user/schedules | 새 일정 생성 | USER, ADMIN |
| DELETE | /api/user/schedules/{id} | 본인이 작성한 일정 삭제 | USER, ADMIN |
Public 전용 API
| HTTP Method | URI | 설명 | 권한 |
| POST | /api/public/register | 회원가입 | Public |
| POST | /api/public/login | 로그인 | Public |
2. 코드 구현
JwtFilter
필터는 요청의 JWT를 검증하고 역할(Role)에 따라 요청을 차단하거나 허용
@Component
@RequiredArgsConstructor
public class JwtFilter implements Filter {
private final JwtUtil jwtUtil;
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
String requestURI = httpRequest.getRequestURI();
String authorizationHeader = httpRequest.getHeader("Authorization");
// Public API는 필터를 통과
if (requestURI.startsWith("/api/public")) {
chain.doFilter(request, response);
return;
}
// JWT 검증
if (authorizationHeader == null || !authorizationHeader.startsWith("Bearer ")) {
httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "JWT 토큰이 필요합니다.");
return;
}
String jwt = authorizationHeader.substring(7);
if (!jwtUtil.validateToken(jwt)) {
httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN, "유효하지 않은 JWT 토큰입니다.");
return;
}
String role = jwtUtil.extractRoles(jwt);
// 관리자 전용 API 검증
if (requestURI.startsWith("/api/admin") && !"ADMIN".equals(role)) {
httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN, "ADMIN 권한이 필요합니다.");
return;
}
// 사용자 전용 API 검증
if (requestURI.startsWith("/api/user") && !"USER".equals(role) && !"ADMIN".equals(role)) {
httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN, "USER 권한이 필요합니다.");
return;
}
chain.doFilter(request, response);
}
}
Controller와 Service
AdminController
관리자는 사용자 데이터를 관리할 수 있는 권한이 있으며, 아래와 같은 API를 제공합니다:
1. 모든 사용자 조회
2. 특정 사용자 삭제
@RestController
@RequestMapping("/api/admin")
@RequiredArgsConstructor
public class AdminController {
private final AdminService adminService;
@GetMapping("/users")
public ResponseEntity<List<UserDto>> getAllUsers() {
return ResponseEntity.ok(adminService.getAllUsers());
}
@GetMapping("/users/{id}")
public ResponseEntity<UserDto> getUserById(@PathVariable Long id) {
return ResponseEntity.ok(adminService.getUserById(id));
}
@DeleteMapping("/users/{id}")
public ResponseEntity<String> deleteUser(@PathVariable Long id) {
adminService.deleteUser(id);
return ResponseEntity.ok("사용자가 삭제되었습니다.");
}
}
AdminService
@Service
@RequiredArgsConstructor
public class AdminService {
private final UserRepository userRepository;
public List<UserDto> getAllUsers() {
User user = userRepository.findAll();
return convertToDto(user);
}
public UserDto getUserById(Long id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다."));
return convertToDto(user);
}
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
}
UserController
사용자는 본인의 정보를 조회하고 일정을 생성 및 삭제할 수 있습니다.
@RestController
@RequestMapping("/api/user")
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
@GetMapping("/profile")
public ResponseEntity<UserDto> getUserProfile(@RequestHeader("Authorization") String token) {
return ResponseEntity.ok(userService.getUserProfile(token));
}
@PostMapping("/schedules")
public ResponseEntity<ScheduleDto> createSchedule(@RequestBody Schedule schedule) {
return ResponseEntity.ok(userService.createSchedule(schedule));
}
@DeleteMapping("/schedules/{id}")
public ResponseEntity<String> deleteSchedule(@PathVariable Long id, @RequestHeader("Authorization") String token) {
userService.deleteSchedule(id, token);
return ResponseEntity.ok("일정이 삭제되었습니다.");
}
}
UserService
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final ScheduleRepository scheduleRepository;
private final JwtUtil jwtUtil;
public UserDto getUserProfile(String token) {
Long userId = jwtUtil.extractUserId(token);
User user =userRepository.findById(userId)
.orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다."));
return UserDto.convertDto(user);
}
public ScheduleDto createSchedule(Schedule schedule) {
Schedule schedule = scheduleRepository.save(schedule);
return ScheduleDto.convertDto(schedule);
}
public void deleteSchedule(Long id, String token) {
Long userId = jwtUtil.extractUserId(token);
Schedule schedule = scheduleRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("일정을 찾을 수 없습니다."));
if (!schedule.getUser().getId().equals(userId)) {
throw new IllegalArgumentException("본인이 작성한 일정만 삭제할 수 있습니다.");
}
scheduleRepository.delete(schedule);
}
}
InItData(초기 관리자 설정)
@Component
@RequiredArgsConstructor
public class InitData {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
@PostConstruct
public void init() {
if (userRepository.findByUsername("admin").isEmpty()) {
User admin = new User(
null,
"박병천",
"admin@example.com",
passwordEncoder.encode("adminPassword"),
UserRoleEnum.ADMIN
);
userRepository.save(admin);
}
}
}
3. 순서도
JWT 인증 및 권한 검증 순서도
1. 클라이언트 요청 → JwtFilter 도착
2. JWT 토큰 유효성 검증
- 토큰 없음: 401 Unauthorized 반환
- 토큰 유효하지 않음: 403 Forbidden 반환
3. 역할(Role) 확인
- ADMIN 전용 URI → ADMIN 검증
- USER 전용 URI → USER 또는 ADMIN 검증
4. 요청 통과 또는 차단
5. 컨트롤러 실행'Back-End > Spring-MVC' 카테고리의 다른 글
| AOP를 활용한 권한 검증 (feat. 커스텀 어노테이션) (0) | 2025.01.09 |
|---|---|
| JWT 기반 인증/인가 흐름에서 AOP와 필터(URI기반) (0) | 2025.01.08 |
| JWT(Json Web Token) (0) | 2025.01.04 |
| 스프링 글로벌 API 예외 처리와 @Valid 검증 예외 처리 (1) | 2024.12.31 |
| Servlet Filter (0) | 2024.12.12 |