You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
autoflow-server-mgmt/src/main/java/kr/re/etri/autoflow/controllers/AuthController.java

196 lines
8.0 KiB

package kr.re.etri.autoflow.controllers;
11 months ago
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;
11 months ago
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;
11 months ago
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 및 리프레시 토큰 쿠키를 반환합니다.")
11 months ago
@ApiResponses({
@ApiResponse(responseCode = "200", description = "로그인 성공"),
@ApiResponse(responseCode = "401", description = "잘못된 사용자명 또는 비밀번호")
11 months ago
})
@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<String> roles = userDetails.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
11 months ago
.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 = "중복된 사용자명 또는 이메일")
})
11 months ago
@PostMapping("/signup")
public ResponseEntity<?> registerUser(@Valid @RequestBody SignupRequest signUpRequest) {
if (userRepository.existsByUsername(signUpRequest.getUsername())) {
return ResponseEntity.badRequest().body(new MessageResponse("오류: 이미 사용 중인 사용자 이름입니다."));
11 months ago
}
if (userRepository.existsByEmail(signUpRequest.getEmail())) {
return ResponseEntity.badRequest().body(new MessageResponse("오류: 이미 사용 중인 이메일입니다."));
11 months ago
}
User user = new User(
signUpRequest.getUsername(),
signUpRequest.getEmail(),
encoder.encode(signUpRequest.getPassword())
);
11 months ago
Set<String> strRoles = signUpRequest.getRole();
Set<Role> 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("오류: '" + roleName + "' 역할은 유효하지 않습니다."));
}
11 months ago
}
user.setRoles(roles);
userRepository.save(user);
return ResponseEntity.ok(new MessageResponse("사용자 등록이 완료되었습니다."));
11 months ago
}
@Operation(summary = "로그아웃", description = "현재 사용자를 로그아웃하고 쿠키 및 리프레시 토큰을 삭제합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "로그아웃 성공")
})
11 months ago
@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("You've been signed out!"));
}
@Operation(summary = "토큰 갱신", description = "쿠키에 저장된 리프레시 토큰을 통해 새로운 엑세스 토큰을 발급합니다.")
11 months ago
@ApiResponses({
@ApiResponse(responseCode = "200", description = "토큰 갱신 성공"),
@ApiResponse(responseCode = "400", description = "리프레시 토큰이 없거나 유효하지 않음")
11 months ago
})
@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("Token is refreshed successfully!"));
})
.orElseThrow(() -> new TokenRefreshException(refreshToken, "Refresh token is not in database!"));
}
return ResponseEntity.badRequest().body(new MessageResponse("Refresh Token is empty!"));
}
}