Skip to content

Commit 51c541e

Browse files
authored
Merge pull request #29 from YAPP-Github/feat/PRODUCT-104
[Feat] 서비스 인증/인가 기능 구현
2 parents 7fe57a4 + 3b5ef50 commit 51c541e

23 files changed

+615
-94
lines changed

build.gradle

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ dependencies {
6565
runtimeOnly 'com.h2database:h2'
6666
runtimeOnly 'com.mysql:mysql-connector-j'
6767

68+
// JWT
69+
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
70+
implementation 'io.jsonwebtoken:jjwt-impl:0.11.5'
71+
implementation 'io.jsonwebtoken:jjwt-gson:0.11.5'
72+
6873
// Test
6974
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
7075
testImplementation 'org.springframework.boot:spring-boot-starter-test'
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package timeeat.config;
2+
3+
import java.util.List;
4+
import lombok.RequiredArgsConstructor;
5+
import org.springframework.context.annotation.Configuration;
6+
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
7+
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
8+
import timeeat.controller.web.auth.AuthMemberArgumentResolver;
9+
import timeeat.controller.web.jwt.JwtManager;
10+
11+
@Configuration
12+
@RequiredArgsConstructor
13+
public class WebConfig implements WebMvcConfigurer {
14+
15+
private final JwtManager jwtManager;
16+
17+
@Override
18+
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
19+
argumentResolvers.add(new AuthMemberArgumentResolver(jwtManager));
20+
}
21+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package timeeat.controller.auth;
2+
3+
import java.net.URI;
4+
import lombok.RequiredArgsConstructor;
5+
import org.springframework.http.HttpStatus;
6+
import org.springframework.http.ResponseEntity;
7+
import org.springframework.web.bind.annotation.GetMapping;
8+
import org.springframework.web.bind.annotation.PostMapping;
9+
import org.springframework.web.bind.annotation.RequestBody;
10+
import org.springframework.web.bind.annotation.RestController;
11+
import timeeat.controller.web.jwt.JwtManager;
12+
import timeeat.service.auth.AuthService;
13+
14+
@RestController
15+
@RequiredArgsConstructor
16+
public class AuthController {
17+
18+
private final JwtManager jwtManager;
19+
private final AuthService authService;
20+
21+
@GetMapping("/api/auth/login/oauth")
22+
public ResponseEntity<Void> redirectOauthLoginPage() {
23+
URI oauthLoginUrl = authService.getOauthLoginUrl();
24+
25+
return ResponseEntity
26+
.status(HttpStatus.FOUND)
27+
.location(oauthLoginUrl)
28+
.build();
29+
}
30+
31+
@PostMapping("/api/auth/login")
32+
public ResponseEntity<TokenResponse> login(@RequestBody MemberLoginRequest request) {
33+
authService.login(request);
34+
35+
TokenResponse response = new TokenResponse(
36+
jwtManager.issueAccessToken(1L),
37+
jwtManager.issueRefreshToken(1L));
38+
return ResponseEntity
39+
.status(HttpStatus.CREATED)
40+
.body(response);
41+
}
42+
43+
@PostMapping("/api/auth/reissue")
44+
public ResponseEntity<TokenResponse> reissueToken(@RequestBody ReissueRequest request) {
45+
long id = jwtManager.resolveRefreshToken(request.refreshToken());
46+
47+
TokenResponse response = new TokenResponse(
48+
jwtManager.issueAccessToken(id),
49+
jwtManager.issueRefreshToken(id));
50+
return ResponseEntity
51+
.status(HttpStatus.CREATED)
52+
.body(response);
53+
}
54+
}

src/main/java/timeeat/controller/member/MemberLoginRequest.java renamed to src/main/java/timeeat/controller/auth/MemberLoginRequest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package timeeat.controller.member;
1+
package timeeat.controller.auth;
22

33
public record MemberLoginRequest(String code) {
44
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package timeeat.controller.auth;
2+
3+
public record ReissueRequest(String refreshToken) {
4+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package timeeat.controller.auth;
2+
3+
public record TokenResponse(String accessToken, String refreshToken) {
4+
}

src/main/java/timeeat/controller/member/MemberController.java

Lines changed: 0 additions & 34 deletions
This file was deleted.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package timeeat.controller.web.auth;
2+
3+
import lombok.AllArgsConstructor;
4+
import org.springframework.core.MethodParameter;
5+
import org.springframework.http.HttpHeaders;
6+
import org.springframework.web.bind.support.WebDataBinderFactory;
7+
import org.springframework.web.context.request.NativeWebRequest;
8+
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
9+
import org.springframework.web.method.support.ModelAndViewContainer;
10+
import timeeat.controller.web.jwt.JwtManager;
11+
import timeeat.exception.BusinessErrorCode;
12+
import timeeat.exception.BusinessException;
13+
14+
@AllArgsConstructor
15+
public class AuthMemberArgumentResolver implements HandlerMethodArgumentResolver {
16+
17+
private final JwtManager jwtManager;
18+
19+
@Override
20+
public boolean supportsParameter(MethodParameter parameter) {
21+
return parameter.getParameterType().equals(LoginMember.class);
22+
}
23+
24+
@Override
25+
public Object resolveArgument(MethodParameter parameter,
26+
ModelAndViewContainer mavContainer,
27+
NativeWebRequest webRequest,
28+
WebDataBinderFactory binderFactory) throws Exception {
29+
String accessToken = webRequest.getHeader(HttpHeaders.AUTHORIZATION);
30+
if (accessToken == null) {
31+
throw new BusinessException(BusinessErrorCode.UNAUTHORIZED_MEMBER);
32+
}
33+
long memberId = jwtManager.resolveAccessToken(accessToken);
34+
return new LoginMember(memberId);
35+
}
36+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package timeeat.controller.web.auth;
2+
3+
public record LoginMember(long id) {
4+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package timeeat.controller.web.jwt;
2+
3+
import io.jsonwebtoken.Claims;
4+
import io.jsonwebtoken.ExpiredJwtException;
5+
import io.jsonwebtoken.Jwts;
6+
import java.time.Duration;
7+
import java.util.Date;
8+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
9+
import org.springframework.stereotype.Component;
10+
import timeeat.exception.BusinessErrorCode;
11+
import timeeat.exception.BusinessException;
12+
13+
@Component
14+
@EnableConfigurationProperties(JwtProperties.class)
15+
public class JwtManager {
16+
17+
private final JwtProperties jwtProperties;
18+
19+
public JwtManager(JwtProperties jwtProperties) {
20+
this.jwtProperties = jwtProperties;
21+
}
22+
23+
public String issueAccessToken(long id) {
24+
return createToken(id, jwtProperties.getAccessTokenExpiration(), TokenType.ACCESS_TOKEN);
25+
}
26+
27+
public String issueRefreshToken(long id) {
28+
return createToken(id, jwtProperties.getRefreshTokenExpiration(), TokenType.REFRESH_TOKEN);
29+
}
30+
31+
private String createToken(long identifier, Duration expiration, TokenType tokenType) {
32+
Date now = new Date();
33+
Date expired = new Date(now.getTime() + expiration.toMillis());
34+
return Jwts.builder()
35+
.setSubject(Long.toString(identifier))
36+
.setIssuedAt(now)
37+
.setExpiration(expired)
38+
.claim("type", tokenType.name())
39+
.signWith(jwtProperties.getSecretKey())
40+
.compact();
41+
}
42+
43+
public long resolveAccessToken(String accessToken) {
44+
return resolveToken(accessToken, TokenType.ACCESS_TOKEN);
45+
}
46+
47+
public long resolveRefreshToken(String refreshToken) {
48+
return resolveToken(refreshToken, TokenType.REFRESH_TOKEN);
49+
}
50+
51+
private long resolveToken(String token, TokenType tokenType) {
52+
try {
53+
Claims claims = Jwts.parserBuilder()
54+
.setSigningKey(jwtProperties.getSecretKey())
55+
.build()
56+
.parseClaimsJws(token)
57+
.getBody();
58+
validateTokenType(claims, tokenType);
59+
return Long.parseLong(claims.getSubject());
60+
} catch (ExpiredJwtException exception) {
61+
throw new BusinessException(BusinessErrorCode.EXPIRED_TOKEN);
62+
} catch (Exception e) {
63+
throw new BusinessException(BusinessErrorCode.UNAUTHORIZED_MEMBER);
64+
}
65+
}
66+
67+
private void validateTokenType(Claims claims, TokenType tokenType) {
68+
String extractTokenType = claims.get("type", String.class);
69+
if (!extractTokenType.equals(tokenType.name())) {
70+
throw new BusinessException(BusinessErrorCode.UNAUTHORIZED_MEMBER);
71+
}
72+
}
73+
}

0 commit comments

Comments
 (0)