package kr.re.etri.autoflow.controllers; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseCookie; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.web.bind.annotation.*; import kr.re.etri.autoflow.exception.TokenRefreshException; import kr.re.etri.autoflow.models.ERole; import kr.re.etri.autoflow.models.RefreshToken; import kr.re.etri.autoflow.models.Role; import kr.re.etri.autoflow.models.User; import kr.re.etri.autoflow.payload.request.LoginRequest; import kr.re.etri.autoflow.payload.request.SignupRequest; import kr.re.etri.autoflow.payload.response.UserInfoResponse; import kr.re.etri.autoflow.payload.response.MessageResponse; import kr.re.etri.autoflow.repository.RoleRepository; import kr.re.etri.autoflow.repository.UserRepository; import kr.re.etri.autoflow.security.jwt.JwtUtils; import kr.re.etri.autoflow.security.services.RefreshTokenService; import kr.re.etri.autoflow.security.services.UserDetailsImpl; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; @Tag(name = "Authentication", description = "User Authentication APIs") @CrossOrigin(origins = "*", maxAge = 3600) @RestController @RequestMapping("/api/auth") public class AuthController { @Autowired AuthenticationManager authenticationManager; @Autowired UserRepository userRepository; @Autowired RoleRepository roleRepository; @Autowired PasswordEncoder encoder; @Autowired JwtUtils jwtUtils; @Autowired RefreshTokenService refreshTokenService; @Operation(summary = "로그인", description = "사용자 인증 후 JWT 및 리프레시 토큰 쿠키를 반환합니다.") @ApiResponses({ @ApiResponse(responseCode = "200", description = "로그인 성공"), @ApiResponse(responseCode = "401", description = "잘못된 사용자명 또는 비밀번호") }) @PostMapping("/signin") public ResponseEntity authenticateUser(@Valid @RequestBody LoginRequest loginRequest) { Authentication authentication = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()) ); SecurityContextHolder.getContext().setAuthentication(authentication); UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal(); // 기존 refresh token 제거 refreshTokenService.deleteByUserId(userDetails.getId()); // 새 JWT 및 RefreshToken 생성 ResponseCookie jwtCookie = jwtUtils.generateJwtCookie(userDetails); RefreshToken refreshToken = refreshTokenService.createRefreshToken(userDetails.getId()); ResponseCookie jwtRefreshCookie = jwtUtils.generateRefreshJwtCookie(refreshToken.getToken()); List roles = userDetails.getAuthorities().stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.toList()); return ResponseEntity.ok() .header(HttpHeaders.SET_COOKIE, jwtCookie.toString()) .header(HttpHeaders.SET_COOKIE, jwtRefreshCookie.toString()) .body(new UserInfoResponse( userDetails.getId(), userDetails.getUsername(), userDetails.getEmail(), roles )); } @Operation(summary = "회원가입", description = "새로운 사용자를 등록합니다.") @ApiResponses({ @ApiResponse(responseCode = "200", description = "회원가입 성공"), @ApiResponse(responseCode = "400", description = "중복된 사용자명 또는 이메일") }) @PostMapping("/signup") public ResponseEntity registerUser(@Valid @RequestBody SignupRequest signUpRequest) { if (userRepository.existsByUsername(signUpRequest.getUsername())) { return ResponseEntity.badRequest().body(new MessageResponse(false,"오류: 이미 사용 중인 사용자 이름입니다.")); } if (userRepository.existsByEmail(signUpRequest.getEmail())) { return ResponseEntity.badRequest().body(new MessageResponse(false,"오류: 이미 사용 중인 이메일입니다.")); } User user = new User( signUpRequest.getUsername(), signUpRequest.getEmail(), encoder.encode(signUpRequest.getPassword()) ); Set strRoles = signUpRequest.getRole(); Set roles = new HashSet<>(); if (strRoles == null || strRoles.isEmpty()) { strRoles = Set.of("ROLE_USER"); } for (String roleName : strRoles) { try { ERole eRole = ERole.valueOf(roleName); Role role = roleRepository.findByName(eRole) .orElseThrow(() -> new RuntimeException("오류: '" + roleName + "' 역할이 DB에 존재하지 않습니다.")); roles.add(role); } catch (IllegalArgumentException e) { return ResponseEntity.badRequest().body(new MessageResponse(false,"오류: '" + roleName + "' 역할은 유효하지 않습니다.")); } } user.setRoles(roles); userRepository.save(user); return ResponseEntity.ok(new MessageResponse(true,"사용자 등록이 완료되었습니다.")); } @Operation(summary = "로그아웃", description = "현재 사용자를 로그아웃하고 쿠키 및 리프레시 토큰을 삭제합니다.") @ApiResponses({ @ApiResponse(responseCode = "200", description = "로그아웃 성공") }) @PostMapping("/signout") public ResponseEntity logoutUser() { Object principle = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); if (!"anonymousUser".equals(principle.toString())) { Long userId = ((UserDetailsImpl) principle).getId(); refreshTokenService.deleteByUserId(userId); } ResponseCookie jwtCookie = jwtUtils.getCleanJwtCookie(); ResponseCookie jwtRefreshCookie = jwtUtils.getCleanJwtRefreshCookie(); return ResponseEntity.ok() .header(HttpHeaders.SET_COOKIE, jwtCookie.toString()) .header(HttpHeaders.SET_COOKIE, jwtRefreshCookie.toString()) .body(new MessageResponse(true,"You've been signed out!")); } @Operation(summary = "토큰 갱신", description = "쿠키에 저장된 리프레시 토큰을 통해 새로운 엑세스 토큰을 발급합니다.") @ApiResponses({ @ApiResponse(responseCode = "200", description = "토큰 갱신 성공"), @ApiResponse(responseCode = "400", description = "리프레시 토큰이 없거나 유효하지 않음") }) @PostMapping("/refreshtoken") public ResponseEntity refreshtoken(HttpServletRequest request) { String refreshToken = jwtUtils.getJwtRefreshFromCookies(request); if (refreshToken != null && !refreshToken.isEmpty()) { return refreshTokenService.findByToken(refreshToken) .map(refreshTokenService::verifyExpiration) .map(RefreshToken::getUser) .map(user -> { ResponseCookie jwtCookie = jwtUtils.generateJwtCookie(user); return ResponseEntity.ok() .header(HttpHeaders.SET_COOKIE, jwtCookie.toString()) .body(new MessageResponse(true,"Token is refreshed successfully!")); }) .orElseThrow(() -> new TokenRefreshException(refreshToken, "Refresh token is not in database!")); } return ResponseEntity.badRequest().body(new MessageResponse(false,"Refresh Token is empty!")); } @Operation(summary = "전체 사용자 조회", description = "등록된 모든 사용자 목록을 조회합니다.") @ApiResponses({ @ApiResponse(responseCode = "200", description = "사용자 목록 조회 성공") }) @GetMapping("/users") public ResponseEntity> getAllUsers() { List users = userRepository.findAll().stream() .map(user -> new UserInfoResponse( user.getId(), user.getUsername(), user.getEmail(), user.getRoles().stream() .map(role -> role.getName().name()) .toList() )) .toList(); return ResponseEntity.ok(users); } }