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.
196 lines
8.1 KiB
196 lines
8.1 KiB
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<String> 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<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(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!"));
|
|
}
|
|
}
|