Skip to content

Commit cea9fe9

Browse files
authored
고생하셨습니다.
🎉 PR 머지 완료! 🎉
1 parent f91494b commit cea9fe9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+987
-490
lines changed

src/main/java/roomescape/auth/AuthController.java

Lines changed: 14 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,39 @@
22

33
import jakarta.servlet.http.Cookie;
44
import jakarta.servlet.http.HttpServletRequest;
5-
import jakarta.servlet.http.HttpServletResponse;
65
import jakarta.validation.Valid;
7-
import java.util.Arrays;
6+
import java.time.Duration;
87
import org.springframework.http.HttpHeaders;
98
import org.springframework.http.ResponseCookie;
109
import org.springframework.http.ResponseEntity;
1110
import org.springframework.stereotype.Controller;
1211
import org.springframework.web.bind.annotation.GetMapping;
1312
import org.springframework.web.bind.annotation.PostMapping;
1413
import org.springframework.web.bind.annotation.RequestBody;
15-
import roomescape.global.exception.RoomescapeUnauthorizedException;
16-
14+
import roomescape.auth.client.cookie.CookieResolver;
15+
import roomescape.auth.client.cookie.CookieProvider;
1716

1817
@Controller
1918
public class AuthController {
2019

21-
public static final String TOKEN = "token";
20+
private static final String EXPIRED_TOKEN = "";
21+
private static final Long DEFAULT_TIME = 60L;
2222

2323
private final AuthService authService;
24+
private final CookieProvider cookieProvider;
25+
private final CookieResolver cookieResolver;
2426

25-
public AuthController(AuthService authService) {
27+
public AuthController(AuthService authService, CookieProvider cookieProvider, CookieResolver cookieResolver) {
2628
this.authService = authService;
29+
this.cookieProvider = cookieProvider;
30+
this.cookieResolver = cookieResolver;
2731
}
2832

2933
@PostMapping("/login")
3034
public ResponseEntity<Void> login(@RequestBody @Valid LoginRequest loginRequest) {
3135
String accessToken = authService.generateAccessToken(loginRequest);
32-
ResponseCookie responseCookie = ResponseCookie.from(TOKEN, accessToken)
33-
.path("/")
34-
.httpOnly(true)
35-
.build();
36+
ResponseCookie responseCookie = cookieProvider.generateCookie(accessToken,
37+
Duration.ofMinutes(DEFAULT_TIME));
3638

3739
return ResponseEntity.noContent()
3840
.header(HttpHeaders.SET_COOKIE, responseCookie.toString())
@@ -42,7 +44,7 @@ public ResponseEntity<Void> login(@RequestBody @Valid LoginRequest loginRequest)
4244
@GetMapping("/login/check")
4345
public ResponseEntity<LoginCheckResponse> loginCheck(HttpServletRequest request) {
4446
Cookie[] cookies = request.getCookies();
45-
String accessToken = getToken(cookies);
47+
String accessToken = cookieResolver.getToken(cookies);
4648

4749
LoginCheckResponse result = authService.checkAccessToken(accessToken);
4850
return ResponseEntity.ok()
@@ -51,27 +53,10 @@ public ResponseEntity<LoginCheckResponse> loginCheck(HttpServletRequest request)
5153

5254
@PostMapping("/logout")
5355
public ResponseEntity<Void> logout() {
54-
ResponseCookie responseCookie = ResponseCookie.from(TOKEN, "")
55-
.path("/")
56-
.httpOnly(true)
57-
.maxAge(0)
58-
.build();
56+
ResponseCookie responseCookie = cookieProvider.generateCookie(EXPIRED_TOKEN, Duration.ZERO);
5957

6058
return ResponseEntity.noContent()
6159
.header(HttpHeaders.SET_COOKIE, responseCookie.toString())
6260
.build();
6361
}
64-
65-
private String getToken(Cookie[] cookies) {
66-
String accessToken = Arrays.stream(cookies)
67-
.filter(cookie -> cookie.getName().equals(TOKEN))
68-
.map(Cookie::getValue)
69-
.findFirst()
70-
.orElse(null);
71-
72-
if (accessToken == null) {
73-
throw new RoomescapeUnauthorizedException("로그인 되어있지 않습니다.");
74-
}
75-
return accessToken;
76-
}
7762
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package roomescape.auth;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Retention;
5+
import java.lang.annotation.RetentionPolicy;
6+
import java.lang.annotation.Target;
7+
8+
@Target(ElementType.PARAMETER)
9+
@Retention(RetentionPolicy.RUNTIME)
10+
public @interface AuthMember {
11+
}

src/main/java/roomescape/auth/AuthService.java

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,23 @@
22

33
import io.jsonwebtoken.Jwts;
44
import io.jsonwebtoken.security.Keys;
5-
import org.springframework.dao.EmptyResultDataAccessException;
65
import org.springframework.dao.IncorrectResultSizeDataAccessException;
76
import org.springframework.stereotype.Service;
7+
import roomescape.auth.client.jwt.JwtProperties;
8+
import roomescape.auth.client.jwt.JwtProvider;
89
import roomescape.global.exception.RoomescapeUnauthorizedException;
910
import roomescape.member.Member;
1011
import roomescape.member.MemberDao;
1112

1213
@Service
1314
public class AuthService {
1415

15-
public static final String SECRET_KEY = "Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E=";
16-
1716
private final MemberDao memberDao;
17+
private final JwtProvider jwtProvider;
1818

19-
public AuthService(MemberDao memberDao) {
19+
public AuthService(MemberDao memberDao, JwtProvider jwtProvider) {
2020
this.memberDao = memberDao;
21+
this.jwtProvider = jwtProvider;
2122
}
2223

2324
public String generateAccessToken(LoginRequest loginRequest) {
@@ -29,27 +30,21 @@ public String generateAccessToken(LoginRequest loginRequest) {
2930
throw new RoomescapeUnauthorizedException("회원 정보를 찾을 수 없습니다.");
3031
}
3132

32-
return Jwts.builder()
33-
.setSubject(findMember.getId().toString())
34-
.claim("name", findMember.getName())
35-
.claim("role", findMember.getRole())
36-
.signWith(Keys.hmacShaKeyFor(SECRET_KEY.getBytes()))
37-
.compact();
33+
return jwtProvider.generateToken(findMember.getId(), findMember.getName(), findMember.getRole());
3834
}
3935

4036
public LoginCheckResponse checkAccessToken(String accessToken) {
4137
String name = Jwts.parserBuilder()
42-
.setSigningKey(Keys.hmacShaKeyFor(SECRET_KEY.getBytes()))
38+
.setSigningKey(Keys.hmacShaKeyFor(JwtProperties.SECRET_KEY.getBytes()))
4339
.build()
4440
.parseClaimsJws(accessToken)
4541
.getBody()
4642
.get("name", String.class);
47-
4843
try {
4944
memberDao.findByName(name);
5045
return new LoginCheckResponse(name);
5146
} catch (IncorrectResultSizeDataAccessException exception) {
52-
throw new RoomescapeUnauthorizedException("회원 정보를 찾을 수 없습니다.");
47+
throw new RoomescapeUnauthorizedException("회원 정보를 찾을 수 없습니다." + name);
5348
}
5449
}
5550
}

src/main/java/roomescape/auth/LoginRequest.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ public record LoginRequest(
99
@NotNull(message = "이메일은 필수 입력값입니다.")
1010
@NotBlank(message = "이메일은 비어 있을 수 없습니다.")
1111
@Email(message = "이메일 양식에 맞지 않습니다.")
12-
@Size(max = 255, message = "이메일은 255자 이하여야 합니다.")
12+
@Size(max = MAX_EMAIL_LENGTH, message = "이메일은 20자 이하여야 합니다.")
1313
String email,
1414

1515
@NotNull(message = "비밀번호는 필수 입력값입니다.")
1616
@NotBlank(message = "비밀번호는 비어 있을 수 없습니다.")
17-
@Size(max = 20, message = "비밀번호는 20자 이하여야 합니다.")
17+
@Size(max = MAX_PASSWORD_LENGTH, message = "비밀번호는 255자 이하여야 합니다.")
1818
String password
1919
) {
20+
public static final int MAX_PASSWORD_LENGTH = 255;
21+
public static final int MAX_EMAIL_LENGTH = 20;
2022
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package roomescape.auth.client.cookie;
2+
3+
public class CookieProperties {
4+
public static final String TOKEN = "token";
5+
public static final String PATH = "/";
6+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package roomescape.auth.client.cookie;
2+
3+
import java.time.Duration;
4+
import org.springframework.http.ResponseCookie;
5+
import org.springframework.stereotype.Component;
6+
7+
@Component
8+
public class CookieProvider {
9+
10+
public ResponseCookie generateCookie(String value, Duration duration) {
11+
return ResponseCookie.from(CookieProperties.TOKEN, value)
12+
.path(CookieProperties.PATH)
13+
.httpOnly(true)
14+
.maxAge(duration)
15+
.build();
16+
}
17+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package roomescape.auth.client.cookie;
2+
3+
import jakarta.servlet.http.Cookie;
4+
import java.util.Arrays;
5+
import org.springframework.stereotype.Component;
6+
import roomescape.global.exception.RoomescapeUnauthorizedException;
7+
8+
@Component
9+
public class CookieResolver {
10+
11+
public String getToken(Cookie[] cookies) {
12+
String accessToken = Arrays.stream(cookies)
13+
.filter(cookie -> cookie.getName().equals(CookieProperties.TOKEN))
14+
.map(Cookie::getValue)
15+
.findFirst()
16+
.orElseThrow(() -> new RoomescapeUnauthorizedException("로그인 되어있지 않습니다."));
17+
18+
return accessToken;
19+
}
20+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package roomescape.auth.client.jwt;
2+
3+
public class JwtProperties {
4+
public static final String SECRET_KEY = "Yn2kjibddFAWtnPJ2AFlL8WXmohJMCvigQggaEypa5E=";
5+
public static final String NAME = "name";
6+
public static final String ROLE = "role";
7+
public static final String ADMIN = "ADMIN";
8+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package roomescape.auth.client.jwt;
2+
3+
import io.jsonwebtoken.Jwts;
4+
import io.jsonwebtoken.security.Keys;
5+
import org.springframework.stereotype.Component;
6+
7+
@Component
8+
public class JwtProvider {
9+
10+
public String generateToken(Long id, String name, String role) {
11+
return Jwts.builder()
12+
.setSubject(id.toString())
13+
.claim(JwtProperties.NAME, name)
14+
.claim(JwtProperties.ROLE, role)
15+
.signWith(Keys.hmacShaKeyFor(JwtProperties.SECRET_KEY.getBytes()))
16+
.compact();
17+
}
18+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package roomescape.auth.client.jwt;
2+
3+
import io.jsonwebtoken.ExpiredJwtException;
4+
import io.jsonwebtoken.Jwts;
5+
import io.jsonwebtoken.security.Keys;
6+
import org.springframework.stereotype.Component;
7+
import roomescape.global.exception.RoomescapeUnauthorizedException;
8+
9+
@Component
10+
public class JwtResolver {
11+
12+
public String getName(String token) {
13+
return resolveToken(token, JwtProperties.NAME);
14+
}
15+
16+
public String getRole(String token) {
17+
return resolveToken(token, JwtProperties.ROLE);
18+
}
19+
20+
private String resolveToken(String token, String target) {
21+
try {
22+
String name = Jwts.parserBuilder()
23+
.setSigningKey(Keys.hmacShaKeyFor(JwtProperties.SECRET_KEY.getBytes()))
24+
.build()
25+
.parseClaimsJws(token)
26+
.getBody()
27+
.get(target, String.class);
28+
return name;
29+
} catch (ExpiredJwtException exception) {
30+
throw new RoomescapeUnauthorizedException("만료된 토큰입니다.");
31+
} catch (Exception exception) {
32+
throw new RoomescapeUnauthorizedException("잘못된 토큰입니다. 다시 로그인 해주세요.");
33+
}
34+
}
35+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package roomescape.global.configuration;
2+
3+
import java.util.List;
4+
import org.springframework.context.annotation.Configuration;
5+
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
6+
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
7+
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
8+
import roomescape.auth.client.cookie.CookieResolver;
9+
import roomescape.auth.client.jwt.JwtResolver;
10+
import roomescape.global.interceptor.AuthInterceptor;
11+
import roomescape.global.resolver.MemberArgumentResolver;
12+
import roomescape.member.MemberService;
13+
14+
@Configuration
15+
public class WebConfiguration implements WebMvcConfigurer {
16+
17+
private final MemberService memberService;
18+
private final JwtResolver jwtResolver;
19+
private final CookieResolver cookieResolver;
20+
private final AuthInterceptor authInterceptor;
21+
22+
public WebConfiguration(MemberService memberService, JwtResolver jwtResolver, CookieResolver cookieResolver,
23+
AuthInterceptor authInterceptor) {
24+
this.memberService = memberService;
25+
this.jwtResolver = jwtResolver;
26+
this.cookieResolver = cookieResolver;
27+
this.authInterceptor = authInterceptor;
28+
}
29+
30+
@Override
31+
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> memberArgumentResolver) {
32+
memberArgumentResolver.add(new MemberArgumentResolver(memberService, jwtResolver, cookieResolver));
33+
}
34+
35+
@Override
36+
public void addInterceptors(InterceptorRegistry registry) {
37+
registry.addInterceptor(authInterceptor)
38+
.order(1)
39+
.addPathPatterns("/admin/**");
40+
}
41+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package roomescape.global.exception;
2+
3+
import org.springframework.http.HttpStatus;
4+
5+
public class RoomescapeNotFoundException extends RoomescapeException {
6+
7+
public RoomescapeNotFoundException(String message) {
8+
super(HttpStatus.NOT_FOUND, message);
9+
}
10+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package roomescape.global.interceptor;
2+
3+
import jakarta.servlet.http.HttpServletRequest;
4+
import jakarta.servlet.http.HttpServletResponse;
5+
import org.springframework.stereotype.Component;
6+
import org.springframework.web.servlet.HandlerInterceptor;
7+
import roomescape.auth.client.cookie.CookieResolver;
8+
import roomescape.auth.client.jwt.JwtProperties;
9+
import roomescape.auth.client.jwt.JwtResolver;
10+
11+
@Component
12+
public class AuthInterceptor implements HandlerInterceptor {
13+
14+
private final JwtResolver jwtResolver;
15+
private final CookieResolver cookieResolver;
16+
17+
public AuthInterceptor(JwtResolver jwtResolver, final CookieResolver cookieResolver) {
18+
this.jwtResolver = jwtResolver;
19+
this.cookieResolver = cookieResolver;
20+
}
21+
22+
@Override
23+
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
24+
throws Exception {
25+
String token = cookieResolver.getToken(request.getCookies());
26+
String name = jwtResolver.getName(token);
27+
String role = jwtResolver.getRole(token);
28+
29+
if (name == null || role == null || !role.equals(JwtProperties.ADMIN)) {
30+
response.setStatus(404);
31+
return false;
32+
}
33+
return true;
34+
}
35+
}

0 commit comments

Comments
 (0)