-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #170 from daadaadaah/feat/add-AuthenticationService
임시로 JWT를 활용하여 인증 기능 추가
- Loading branch information
Showing
23 changed files
with
672 additions
and
291 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
15 changes: 15 additions & 0 deletions
15
src/main/java/com/hcommerce/heecommerce/auth/AuthUserInfo.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package com.hcommerce.heecommerce.auth; | ||
|
||
import lombok.Getter; | ||
import lombok.Setter; | ||
|
||
@Getter | ||
@Setter | ||
public class AuthUserInfo { | ||
|
||
private final int userId; | ||
|
||
public AuthUserInfo(int userId) { | ||
this.userId = userId; | ||
} | ||
} |
118 changes: 118 additions & 0 deletions
118
src/main/java/com/hcommerce/heecommerce/auth/AuthenticationService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
package com.hcommerce.heecommerce.auth; | ||
|
||
import com.hcommerce.heecommerce.common.utils.JwtUtils; | ||
import com.hcommerce.heecommerce.user.UserQueryRepository; | ||
import io.jsonwebtoken.Claims; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.stereotype.Service; | ||
|
||
@Service | ||
public class AuthenticationService { | ||
|
||
private final String AUTH_TYPE = "Bearer"; | ||
|
||
private final JwtUtils jwtUtils; | ||
|
||
private final UserQueryRepository userQueryRepository; | ||
|
||
@Autowired | ||
public AuthenticationService(JwtUtils jwtUtils, UserQueryRepository userQueryRepository) { | ||
this.jwtUtils = jwtUtils; | ||
this.userQueryRepository = userQueryRepository; | ||
} | ||
|
||
// TODO : 테스트용으로 일단 간단하게 구현함. | ||
public String login(int userId) { | ||
return jwtUtils.encode(userId); | ||
} | ||
|
||
/** | ||
* isAuthenticatedUser는 HTTP 요청이 인증된 사용자에 의한 것인지를 판단하는 함수이다. | ||
*/ | ||
public boolean isAuthenticatedUser(HttpServletRequest request) { | ||
String authorization = request.getHeader("Authorization"); | ||
|
||
if(authorization == null || authorization.isBlank()) { | ||
return false; | ||
} | ||
|
||
if(!isValidAuthType(authorization)) { | ||
return false; | ||
} | ||
|
||
String accessToken = extractAccessToken(authorization); | ||
|
||
if(accessToken == null || accessToken.isBlank()) { | ||
return false; | ||
} | ||
|
||
AuthUserInfo authUserInfo = parseAccessToken(accessToken); | ||
|
||
if(authUserInfo == null) { | ||
return false; | ||
} | ||
|
||
boolean hasUserId = userQueryRepository.hasUserId(authUserInfo.getUserId()); | ||
|
||
if(!hasUserId) { | ||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
/** | ||
* parseAuthorization는 HTTP Header 의 authorization 를 피상해서 accessToken에 담긴 정보를 리턴하는 함수이다. | ||
* 각 단계별로 유효성 검사를 할 수 있겠지만, 다른 기능 구현에 집중하기 위해 시간 관계상 | ||
* AuthInterceptor 에서 authorization 의 유효성이 모두 유효하게 판단된 상황을 가정해서 따로 추가하지 않았다. | ||
*/ | ||
public AuthUserInfo parseAuthorization(String authorization) { | ||
String accessToken = extractAccessToken(authorization); | ||
|
||
AuthUserInfo authUserInfo = parseAccessToken(accessToken); | ||
|
||
return authUserInfo; | ||
} | ||
|
||
/** | ||
* isValidAuthType 는 HTTP Header 의 authorization 의 인증 유형이 유효한 인증 유형인지를 판단하는 함수이다. | ||
*/ | ||
private boolean isValidAuthType(String authorization) { | ||
return authorization.startsWith(AUTH_TYPE); | ||
} | ||
|
||
/** | ||
* isValidAuthType 는 HTTP Header 의 authorization로부터 accessToken을 추춣하는 함수이다. | ||
*/ | ||
private String extractAccessToken(String authorization) { | ||
String[] authorizationUnit = authorization.split(AUTH_TYPE+" "); | ||
|
||
if(authorizationUnit.length < 2) { | ||
return null; | ||
} | ||
|
||
String accessToken = authorizationUnit[1]; | ||
|
||
if(accessToken.isBlank()) { | ||
return null; | ||
} | ||
|
||
return accessToken; | ||
} | ||
|
||
/** | ||
* parseAccessToken 는 accessToken에 파싱하여 저장된 사용자 인증 정보를 리턴하는 함수이다. | ||
*/ | ||
private AuthUserInfo parseAccessToken(String accessToken) { | ||
Claims claims = jwtUtils.decode(accessToken); | ||
|
||
if(claims == null) { | ||
return null; | ||
} | ||
|
||
int userId = claims.get("userId", Integer.class); | ||
|
||
return new AuthUserInfo(userId); | ||
} | ||
} |
7 changes: 7 additions & 0 deletions
7
src/main/java/com/hcommerce/heecommerce/auth/InvalidTokenException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package com.hcommerce.heecommerce.auth; | ||
|
||
public class InvalidTokenException extends RuntimeException { | ||
public InvalidTokenException(String token) { | ||
super(token +" : 유효하지 않은 토큰 입니다."); | ||
} | ||
} |
17 changes: 0 additions & 17 deletions
17
src/main/java/com/hcommerce/heecommerce/common/AuthHelper.java
This file was deleted.
Oops, something went wrong.
45 changes: 16 additions & 29 deletions
45
src/main/java/com/hcommerce/heecommerce/common/AuthInterceptor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,57 +1,44 @@ | ||
package com.hcommerce.heecommerce.common; | ||
|
||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import com.hcommerce.heecommerce.auth.AuthenticationService; | ||
import com.hcommerce.heecommerce.common.dto.ResponseDto; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.web.servlet.HandlerInterceptor; | ||
|
||
@Component | ||
public class AuthInterceptor implements HandlerInterceptor { | ||
|
||
private final AuthenticationService authenticationService; | ||
|
||
private final ObjectMapper objectMapper = new ObjectMapper(); | ||
|
||
@Autowired | ||
public AuthInterceptor(AuthenticationService authenticationService) { | ||
this.authenticationService = authenticationService; | ||
} | ||
|
||
@Override | ||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { | ||
|
||
// TODO : 추후 삭제 필요! 이거 주석처리 후 테스트해야 제대로 테스트 됨 | ||
// HttpSession session = request.getSession(); | ||
// session.setAttribute("isAdmin", true); | ||
|
||
if (!AuthHelper.isLogin(request)) { | ||
if(!authenticationService.isAuthenticatedUser(request)) { | ||
response.setStatus(HttpStatus.UNAUTHORIZED.value()); | ||
|
||
response.setContentType("application/json; charset=UTF-8"); | ||
|
||
response.getWriter().append( | ||
objectMapper.writeValueAsString(ResponseDto.builder() | ||
.code(HttpStatus.UNAUTHORIZED.name()) | ||
.message("로그인 후에 이용할 수 있습니다.") | ||
.build()) | ||
objectMapper.writeValueAsString(ResponseDto.builder() | ||
.code(HttpStatus.UNAUTHORIZED.name()) | ||
.message("로그인 후에 이용할 수 있습니다.") | ||
.build()) | ||
); | ||
|
||
return false; | ||
} | ||
|
||
if(!AuthHelper.isAdmin(request)) { | ||
response.setStatus(HttpStatus.FORBIDDEN.value()); | ||
|
||
response.setContentType("application/json; charset=UTF-8"); | ||
|
||
response.getWriter().append( | ||
objectMapper.writeValueAsString(ResponseDto.builder() | ||
.code(HttpStatus.FORBIDDEN.name()) | ||
.message("관리자만 이용 가능합니다.") | ||
.build()) | ||
); | ||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
// TODO : 추후 삭제 필요! | ||
private boolean isRandomAdmin() { | ||
return System.currentTimeMillis() % 2 == 0; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
12 changes: 10 additions & 2 deletions
12
src/main/java/com/hcommerce/heecommerce/common/WebConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,24 @@ | ||
package com.hcommerce.heecommerce.common; | ||
|
||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; | ||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; | ||
|
||
@Configuration | ||
public class WebConfig implements WebMvcConfigurer { | ||
|
||
private final AuthInterceptor authInterceptor; | ||
|
||
@Autowired | ||
public WebConfig(AuthInterceptor authInterceptor) { | ||
this.authInterceptor = authInterceptor; | ||
} | ||
|
||
@Override | ||
public void addInterceptors(InterceptorRegistry registry) { | ||
registry.addInterceptor(new AuthInterceptor()) | ||
registry.addInterceptor(authInterceptor) | ||
.order(1) | ||
.addPathPatterns("/admin/**"); | ||
.addPathPatterns("/orders/**"); // 주문 관련 기능은 회원만 가능하도록 | ||
} | ||
} |
54 changes: 54 additions & 0 deletions
54
src/main/java/com/hcommerce/heecommerce/common/utils/JwtUtils.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package com.hcommerce.heecommerce.common.utils; | ||
|
||
import com.hcommerce.heecommerce.auth.InvalidTokenException; | ||
import io.jsonwebtoken.Claims; | ||
import io.jsonwebtoken.Jwts; | ||
import io.jsonwebtoken.MalformedJwtException; | ||
import io.jsonwebtoken.security.Keys; | ||
import io.jsonwebtoken.security.SignatureException; | ||
import java.security.Key; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.stereotype.Component; | ||
|
||
@Component | ||
public class JwtUtils { | ||
|
||
private Key key; | ||
|
||
public JwtUtils(@Value("${jwt.secret}") String secret) { | ||
if(secret == null || secret.isBlank()) { // isEmpty가 아닌 isBlank를 사용한 이유 : " " 값을 가지는 경우도 예외로 처리해야 하므로, | ||
// TODO : 클라이언트 - 내부 서버 예러 입니다. / 서버 로그 - jwt secret 값을 확인해주세요. | ||
} | ||
|
||
this.key = Keys.hmacShaKeyFor(secret.getBytes()); | ||
} | ||
|
||
public String encode(int userId) { | ||
if(userId < 0) { | ||
// TODO : 유효하지 않은 사용자입니다. | ||
} | ||
|
||
return Jwts.builder() | ||
.claim("userId", userId) | ||
.signWith(key) | ||
.compact(); | ||
} | ||
|
||
public Claims decode(String token) { | ||
try { | ||
return Jwts.parserBuilder() | ||
.setSigningKey(key) | ||
.build() | ||
.parseClaimsJws(token) | ||
.getBody(); | ||
} catch (SignatureException e) { // token이 우리가 발행한 토큰이 아닌 경우 | ||
throw new InvalidTokenException("token(value = "+token+") is invalid"); | ||
} catch (IllegalArgumentException e) { // token이 Null 또는 "", " " 일 경우, IllegalArgumentException: JWT String argument cannot be null or empty. 발생 | ||
throw new InvalidTokenException("token(value = null or empty) is invalid"); | ||
} catch (MalformedJwtException e) { | ||
throw new InvalidTokenException("token(value = "+token+") is invalid"); | ||
} catch (Exception e) { | ||
throw new RuntimeException("JwtUtils : decode 예외"); // TODO : 임시로 만들어 놓음 | ||
} | ||
} | ||
} |
Oops, something went wrong.