Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Spring MVC(인증)] 정원영 미션 제출합니다. #117

Merged
merged 33 commits into from
Jan 25, 2025
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
3c2620a
feat: 로그인 및 토큰 생성 로직 구현
gardenzeeero Jan 16, 2025
751823c
feat: 토큰 생성 로직 분리
gardenzeeero Jan 16, 2025
0587b38
feat: 로그인 로직 컨트롤러 연결
gardenzeeero Jan 16, 2025
95214f6
feat: 토큰 유효성 검증 메서드 구현
gardenzeeero Jan 16, 2025
e5fa217
feat: 토큰에서 멤버 클레임 추출 구현
gardenzeeero Jan 16, 2025
fcc4e23
feat: 토큰으로 로그인 검증
gardenzeeero Jan 16, 2025
a4c1804
feat: 로그인 검증 로직 컨트롤러 연결
gardenzeeero Jan 16, 2025
e059346
fix: 테스트 실패로 인한 토큰 이름 변경
gardenzeeero Jan 16, 2025
7863060
fix: 토큰 만료시간 설정
gardenzeeero Jan 16, 2025
7c7ee83
fix: Controller를 RestController로 변경
gardenzeeero Jan 18, 2025
b526907
refactor: 토큰키를 생성자에서 초기화하도록 수정
gardenzeeero Jan 18, 2025
95f6ebf
test: TokenService 단위테스트 구현
gardenzeeero Jan 18, 2025
6d28e8f
test: AuthService 단위테스트 구현
gardenzeeero Jan 18, 2025
34a3e9e
test: 테스트 대기시간 삭제
gardenzeeero Jan 25, 2025
c2275a7
fix: 잘못된 이메일 또는 비밀번호 입력시 예외처리
gardenzeeero Jan 25, 2025
c59ae63
test: Mock 없이 AuthServiceTest 수정
gardenzeeero Jan 25, 2025
556d351
test: Mock 없이 TokenServiceTest 수정
gardenzeeero Jan 25, 2025
3ce8e0f
feat: ConfigurationProperties를 이용해 설정 구현
gardenzeeero Jan 25, 2025
7521e7f
refactor: TokenService를 ConfigurationProperties를 이용하도록 변경
gardenzeeero Jan 25, 2025
e9cc2ec
test: JwtConfig를 이용한 테스트 재작성
gardenzeeero Jan 25, 2025
288dc07
feat: email 기반 password 검색 구현
gardenzeeero Jan 25, 2025
9e7f03b
feat: 로그인 시 email, password에 대한 자세한 예외 구현
gardenzeeero Jan 25, 2025
95cdb12
test: 기능 구현에 따른 테스트 재작성
gardenzeeero Jan 25, 2025
bf3068b
refactor: dto에서 Long 대신 long 사용
gardenzeeero Jan 25, 2025
eca6383
fix: 잘못된 쿼리 삭제
gardenzeeero Jan 25, 2025
996a524
feat: 로그인 정보를 담기 위한 DTO 생성
gardenzeeero Jan 23, 2025
2c2ca03
feat: 쿠키를 이용해 로그인 정보를 매핑하는 ArgumentResolver 구현
gardenzeeero Jan 23, 2025
8f96052
feat: 토큰이 없는 경우 예외 발생 구현
gardenzeeero Jan 23, 2025
85e3be5
feat: 구현한 ArgumentResolver 등록
gardenzeeero Jan 24, 2025
4d861b5
feat: ReservationRequest 생성자 구현
gardenzeeero Jan 24, 2025
ec3f8cf
feat: ReservationRequest 및 쿠키를 이용하도록 변경
gardenzeeero Jan 24, 2025
a47ee15
test: 통합 테스트 등록
gardenzeeero Jan 24, 2025
9dd8f56
fix: static 키워드 삭제
gardenzeeero Jan 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions src/main/java/roomescape/auth/AuthController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package roomescape.auth;

import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

@Controller
gardenzeeero marked this conversation as resolved.
Show resolved Hide resolved
public class AuthController {

private static final String ACCESS_TOKEN_NAME = "token";

private final AuthService authService;

public AuthController(AuthService authService) {
this.authService = authService;
}

@PostMapping("/login")
public ResponseEntity login(@RequestBody LoginRequest loginRequest, HttpServletResponse response) {
String token = authService.loginWithEmailAndPassword(loginRequest.email(), loginRequest.password());
gardenzeeero marked this conversation as resolved.
Show resolved Hide resolved
addCookie(response, ACCESS_TOKEN_NAME, token);

return ResponseEntity.ok().build();
}

@GetMapping("/login/check")
public ResponseEntity loginCheck(@CookieValue(ACCESS_TOKEN_NAME) String accessToken) {
return ResponseEntity.ok().body(authService.loginCheckWithToken(accessToken));
}

private void addCookie(HttpServletResponse response, String cookieName, String cookieValue) {
Cookie cookie = new Cookie(cookieName, cookieValue);
cookie.setHttpOnly(true);
cookie.setPath("/");
gardenzeeero marked this conversation as resolved.
Show resolved Hide resolved
response.addCookie(cookie);
}
}
40 changes: 40 additions & 0 deletions src/main/java/roomescape/auth/AuthService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package roomescape.auth;

import org.springframework.stereotype.Service;
import roomescape.auth.jwt.MemberTokenDto;
import roomescape.auth.jwt.TokenService;
import roomescape.member.Member;
import roomescape.member.MemberDao;

@Service
public class AuthService {

private final MemberDao memberDao;
private final TokenService tokenService;

public AuthService(MemberDao memberDao, TokenService tokenService) {
this.memberDao = memberDao;
this.tokenService = tokenService;
}

public String loginWithEmailAndPassword(String email, String password) {
Member member = memberDao.findByEmailAndPassword(email, password);
gardenzeeero marked this conversation as resolved.
Show resolved Hide resolved

if(member == null) {
gardenzeeero marked this conversation as resolved.
Show resolved Hide resolved
throw new IllegalArgumentException("잘못된 이메일 또는 비밀번호입니다.");
}

return tokenService.createToken(
new MemberTokenDto(member.getId(), member.getName(), member.getEmail(), member.getRole()));
}

public MemberDetailResponse loginCheckWithToken(String token) {
//유효기간 확인을 위해 필요
gardenzeeero marked this conversation as resolved.
Show resolved Hide resolved
if (!tokenService.checkValidToken(token)) {
throw new IllegalArgumentException("잘못된 토큰입니다.");
}

MemberTokenDto member = tokenService.getMemberClaims(token);
return new MemberDetailResponse(member.id(), member.name(), member.email(), member.role());
}
}
7 changes: 7 additions & 0 deletions src/main/java/roomescape/auth/LoginRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package roomescape.auth;

public record LoginRequest(
String email,
String password
) {
}
9 changes: 9 additions & 0 deletions src/main/java/roomescape/auth/MemberDetailResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package roomescape.auth;

public record MemberDetailResponse(
Long id,
String name,
String email,
String role
) {
}
9 changes: 9 additions & 0 deletions src/main/java/roomescape/auth/jwt/MemberTokenDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package roomescape.auth.jwt;

public record MemberTokenDto(
Long id,
gardenzeeero marked this conversation as resolved.
Show resolved Hide resolved
String name,
String email,
String role
) {
}
77 changes: 77 additions & 0 deletions src/main/java/roomescape/auth/jwt/TokenService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package roomescape.auth.jwt;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import jakarta.annotation.PostConstruct;
import java.security.Key;
import java.util.Date;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

@Service
public class TokenService {

private static final String NAME_CLAIM = "name";
private static final String EMAIL_CLAIM = "email";
private static final String ROLE_CLAIM = "role";

private final String secretKey;
private final long expiration;
private Key key;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

final 을 붙여도 괜찮을 거 같아요.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그러네요!


public TokenService(@Value("${roomescape.auth.jwt.secret}") String secretKey,
gardenzeeero marked this conversation as resolved.
Show resolved Hide resolved
@Value("${roomescape.auth.jwt.expiration}") long expiration) {
this.secretKey = secretKey;
this.expiration = expiration;
}

@PostConstruct
gardenzeeero marked this conversation as resolved.
Show resolved Hide resolved
public void init() {
key = Keys.hmacShaKeyFor(secretKey.getBytes());
}

public String createToken(MemberTokenDto memberTokenDto) {
return Jwts.builder()
.setSubject(memberTokenDto.id().toString())
.claim(NAME_CLAIM, memberTokenDto.name())
.claim(EMAIL_CLAIM, memberTokenDto.email())
.claim(ROLE_CLAIM, memberTokenDto.role())
.setExpiration(new Date(System.currentTimeMillis() + expiration))
gardenzeeero marked this conversation as resolved.
Show resolved Hide resolved
.signWith(key)
.compact();
}

public boolean checkValidToken(String token) {
try {
Jws<Claims> claims = Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token);

return claims.getBody().getExpiration().after(new Date());
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}

public MemberTokenDto getMemberClaims(String token) {
try {
Claims claims = Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();

return new MemberTokenDto(
Long.valueOf(claims.getSubject()),
claims.get(NAME_CLAIM).toString(),
claims.get(EMAIL_CLAIM).toString(),
claims.get(ROLE_CLAIM).toString());
} catch (JwtException | IllegalArgumentException e) {
throw new IllegalArgumentException("잘못된 토큰입니다.");
}
}
}