Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 |
Tags
- EC2
- 스프링예외처리
- 연동
- o'auth2
- 도커
- 백준
- JWT
- 스파르타
- 스파르타코딩클럽
- 프로메테우스
- 테스트코드
- 소셜로그인
- 깃허브
- 그라파나
- Intellij
- 내일배움캠프
- 스프링
- 알고리즘
- Infra
- tomcat
- 키오스크
- 자바
- 스프링시큐리티
- css
- java
- MySQL
- 인텔리제이
- mysqlworkbench
- AWS
- 오버라이딩
Archives
- Today
- Total
개발스토리지😃
[스프링] 스프링시큐리티 + JWT 연동 (2) 로그인 구현 본문
시스템 환경
IDE | IntelliJ IDEA |
Language | Java 17 |
Framework | Spring Boot 3.4.5 |
Build Tool | Gradle |
ORM | JPA (Hibernate) |
Database | MySQL |
Security | Spring Security + JWT (jjwt 0.11.5) |
API 테스트 도구 | Postman |
환경 변수 관리 | .env 파일 (spring-dotenv:4.0.0) |
의존성 관리 | io.spring.dependency-management 1.1.7 |
로깅/편의성 | Lombok |
https://github.com/pleasebelieveme/security-jwt-template
GitHub - pleasebelieveme/security-jwt-template
Contribute to pleasebelieveme/security-jwt-template development by creating an account on GitHub.
github.com
0. 패키지 구조
1. User생성
- SQL문으로 User를 넣어줘도 되고 컨트롤러를 만들고 실행시켜도 된다.
- 필자는 email, password, name, nickname, userRole("USER", "ADMIN") 필드를 선언하였다.
public record UserCreateRequest( @NotBlank(message = "이메일은 필수 입력값입니다.") @Email(message = "올바른 이메일 형식이 아닙니다.") String email, @NotBlank(message = "비밀번호는 필수 입력값입니다.") @Pattern( regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[!@#$%^&*()\\-_=+{};:,<.>]).{8,}$", message = "비밀번호는 최소 8자 이상이며, 대문자, 소문자, 숫자, 특수문자를 모두 포함해야 합니다" ) String password, @NotBlank(message = "이름은 필수 입력값입니다.") @Size(min = 2, max = 20, message = "이름 최대 20글자가 넘지 않도록 해주십시오.") String name, @NotBlank(message = "닉네임은 필수 입력값입니다.") @Size(min = 2, max = 30, message = "이름 최대 30글자가 넘지 않도록 해주십시오.") String nickname, @NotNull UserRole userRole ) { }
2. LoginRequest
- email과 password를 입력받아 스프링으로 전달하는 DTO를 아래와 같이 선언하였다.
public record UserCreateRequest(
@NotBlank(message = "이메일은 필수 입력값입니다.")
@Email(message = "올바른 이메일 형식이 아닙니다.")
String email,
@NotBlank(message = "비밀번호는 필수 입력값입니다.")
@Pattern(
regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[!@#$%^&*()\\-_=+{};:,<.>]).{8,}$",
message = "비밀번호는 최소 8자 이상이며, 대문자, 소문자, 숫자, 특수문자를 모두 포함해야 합니다"
)
String password,
@NotBlank(message = "이름은 필수 입력값입니다.")
@Size(min = 2, max = 20, message = "이름 최대 20글자가 넘지 않도록 해주십시오.")
String name,
@NotBlank(message = "닉네임은 필수 입력값입니다.")
@Size(min = 2, max = 30, message = "이름 최대 30글자가 넘지 않도록 해주십시오.")
String nickname,
@NotNull
UserRole userRole
) {
}
3. TokenResponse
- 로그인 성공시 accessToken과 refreshToken을 전달할 DTO를 아래와 같이 선언하였다.
@Getter
public class TokenResponse {
private final String accessToken;
private final String refreshToken;
public TokenResponse(String accessToken, String refreshToken) {
this.accessToken = accessToken;
this.refreshToken = refreshToken;
}
}
4. AuthController
- domain/auth/controller/AuthController에 위치하였으며 로그인, 로그아웃, 리프레쉬토큰 재발급 메서드를 작성하였다.
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/auth")
public class AuthController {
private final AuthService authService;
@PostMapping("/login")
public ResponseEntity<TokenResponse> login(@Valid @RequestBody LoginRequest request) {
TokenResponse tokenResponse = authService.login(request);
return ResponseEntity.status(HttpStatus.OK).body(tokenResponse);
}
@PostMapping("/logout")
public ResponseEntity<Void> logout(HttpServletRequest request) {
authService.logout(request);
return ResponseEntity.status(HttpStatus.OK).build();
}
@PostMapping("/reissue")
public ResponseEntity<TokenResponse> reissue(@RequestHeader("Authorization") String refreshToken) {
TokenResponse tokenResponse = authService.reissue(refreshToken);
return ResponseEntity.ok(tokenResponse);
}
}
5. AuthService
- 컨트롤러와 맵핑되는 서비스로직을 작성하였고 토큰생성부분은 TokenService로 분리하여 작성하였다.
@Service
@Transactional // AuthService의 모든 public 메서드에만 트랜잭션 적용
@RequiredArgsConstructor
public class AuthService {
private final UserRepository userRepository;
private final RedisRepository redisRepository;
private final PasswordEncoder passwordEncoder;
private final TokenService tokenService;
private final JwtUtil jwtUtil;
public TokenResponse login(LoginRequest request) {
User user = userRepository.findByEmailOrElseThrow(request.email());
if (!passwordEncoder.matches(request.password(), user.getPassword())) {
throw new BizException(UserErrorCode.INVALID_PASSWORD);
}
return tokenService.createTokens(user.getId(), user.getUserRole());
}
public void logout(HttpServletRequest request) {
String token = jwtUtil.extractToken(request);
// 토큰이 유효한 경우만 블랙리스트에 등록
if (token != null && jwtUtil.validateToken(token)) {
long expiration = jwtUtil.getExpiration(token);
redisRepository.saveBlackListToken(token, expiration);
}
}
public TokenResponse reissue(String bearerToken) {
// 1. Bearer 제거
if (bearerToken == null || !bearerToken.startsWith("Bearer ")) {
throw new BizException(AuthErrorCode.MISMATCHED_REFRESH_TOKEN);
}
String refreshToken = bearerToken.substring(7);
// 2. 토큰 유효성 검증
if (!jwtUtil.validateToken(refreshToken)) {
throw new BizException(AuthErrorCode.MISMATCHED_REFRESH_TOKEN);
}
// 3. 유저 정보 추출
UserAuth userAuth = jwtUtil.extractUserAuth(refreshToken);
// 4. Redis에 저장된 Refresh Token과 일치하는지 확인
if (!redisRepository.validateRefreshToken(userAuth.getId(), refreshToken)) {
throw new BizException(AuthErrorCode.REUSED_REFRESH_TOKEN);
}
redisRepository.deleteRefreshToken(userAuth.getId());
return tokenService.createTokens(userAuth.getId(), userAuth.getUserRole());
}
}
6. TokenService
@Service
@RequiredArgsConstructor
public class TokenService {
private final JwtUtil jwtUtil;
private final RedisRepository redisRepository;
public TokenResponse createTokens(Long userId, UserRole userRole) {
String accessToken = jwtUtil.createToken(userId, userRole);
String refreshToken = jwtUtil.createRefreshToken(userId, userRole);
long refreshExpiration = jwtUtil.getRefreshExpiration(refreshToken);
redisRepository.saveRefreshToken(userId, refreshToken, refreshExpiration);
return new TokenResponse(accessToken, refreshToken);
}
}
'프레임워크 > 스프링' 카테고리의 다른 글
[스프링] 스프링시큐리티 + JWT 연동 (4) O'Auth2 소셜 로그인 네이버 (2) | 2025.06.14 |
---|---|
[스프링] 스프링시큐리티 + JWT 연동 (3) O'Auth2 소셜 로그인 구글 (0) | 2025.06.13 |
[스프링] 스프링시큐리티 + JWT 연동 (1) 기본셋팅 (1) | 2025.06.05 |
[스프링] 스파르타 심화 플러스 리팩토링 (0) | 2025.05.15 |
[스프링] 테스트 유틸리티 클래스 ReflectionTestUtils (0) | 2025.04.22 |