-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
8 changed files
with
261 additions
and
2 deletions.
There are no files selected for viewing
2 changes: 2 additions & 0 deletions
2
app/api/src/main/java/org/example/security/dto/AuthenticatedUser.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
77 changes: 77 additions & 0 deletions
77
app/api/src/main/java/org/example/security/token/JWTFilter.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,77 @@ | ||
package org.example.security.token; | ||
|
||
import com.auth0.jwt.interfaces.DecodedJWT; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import exception.BusinessException; | ||
import jakarta.servlet.FilterChain; | ||
import jakarta.servlet.ServletException; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import java.io.IOException; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.UUID; | ||
import lombok.RequiredArgsConstructor; | ||
import org.example.security.dto.AuthenticatedUser; | ||
import org.example.security.dto.TokenParam; | ||
import org.example.security.vo.TokenError; | ||
import org.example.vo.UserRoleApiType; | ||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | ||
import org.springframework.security.core.authority.SimpleGrantedAuthority; | ||
import org.springframework.security.core.context.SecurityContextHolder; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.web.filter.OncePerRequestFilter; | ||
|
||
@RequiredArgsConstructor | ||
@Component | ||
public class JWTFilter extends OncePerRequestFilter { | ||
|
||
private final JWTProcessor jwtProcessor; | ||
private final RefreshTokenProcessor refreshTokenProcessor; | ||
|
||
@Override | ||
protected void doFilterInternal( | ||
HttpServletRequest request, | ||
HttpServletResponse response, | ||
FilterChain filterChain | ||
) throws ServletException, IOException { | ||
if (request.getHeader("Refresh") != null) { | ||
TokenParam token = refreshTokenProcessor.process(request, response); | ||
response.getWriter().write(new ObjectMapper().writeValueAsString(token)); | ||
return; | ||
} | ||
|
||
if (request.getHeader("Authorization") != null) { | ||
handleAccessToken(request); | ||
} | ||
|
||
filterChain.doFilter(request, response); | ||
} | ||
|
||
private void handleAccessToken(HttpServletRequest request) { | ||
String accessToken = jwtProcessor.extractAccessToken(request); | ||
DecodedJWT decodedJWT = jwtProcessor.decodeToken(accessToken); | ||
saveOnSecurityContextHolder(decodedJWT); | ||
} | ||
|
||
private void saveOnSecurityContextHolder(DecodedJWT decodedJWT) { | ||
Map<String, Object> claims = decodedJWT.getClaim("claim").asMap(); | ||
|
||
if (!claims.containsKey("userId") || !claims.containsKey("role")) { | ||
throw new BusinessException(TokenError.INVALID_CLAIM); | ||
} | ||
|
||
AuthenticatedUser authenticatedUser = AuthenticatedUser.builder() | ||
.userId(UUID.fromString(claims.get("userId").toString())) | ||
.role(UserRoleApiType.valueOf(claims.get("role").toString())) | ||
.build(); | ||
|
||
SecurityContextHolder.getContext().setAuthentication( | ||
new UsernamePasswordAuthenticationToken( | ||
authenticatedUser, | ||
null, | ||
List.of(new SimpleGrantedAuthority(authenticatedUser.role().getAuthority())) | ||
) | ||
); | ||
} | ||
} |
2 changes: 1 addition & 1 deletion
2
...va/org/example/security/JWTGenerator.java → .../example/security/token/JWTGenerator.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
51 changes: 51 additions & 0 deletions
51
app/api/src/main/java/org/example/security/token/JWTProcessor.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,51 @@ | ||
package org.example.security.token; | ||
|
||
import com.auth0.jwt.JWT; | ||
import com.auth0.jwt.algorithms.Algorithm; | ||
import com.auth0.jwt.exceptions.TokenExpiredException; | ||
import com.auth0.jwt.interfaces.DecodedJWT; | ||
import exception.BusinessException; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import lombok.RequiredArgsConstructor; | ||
import org.example.property.TokenProperty; | ||
import org.example.security.vo.TokenError; | ||
import org.springframework.stereotype.Component; | ||
|
||
@Component | ||
@RequiredArgsConstructor | ||
public class JWTProcessor { | ||
|
||
private final TokenProperty tokenProperty; | ||
|
||
public String extractAccessToken(HttpServletRequest request) { | ||
String authorization = request.getHeader("Authorization"); | ||
|
||
if (!authorization.startsWith("Bearer ")) { | ||
throw new BusinessException(TokenError.WRONG_HEADER); | ||
} | ||
|
||
return authorization.replace("Bearer ", ""); | ||
} | ||
|
||
public String extractRefreshToken(HttpServletRequest request) { | ||
String refresh = request.getHeader("Refresh"); | ||
|
||
if (refresh == null) { | ||
throw new BusinessException(TokenError.WRONG_HEADER); | ||
} | ||
|
||
return refresh; | ||
} | ||
|
||
public DecodedJWT decodeToken(String token) { | ||
try { | ||
return JWT.require(Algorithm.HMAC512(tokenProperty.secretKey())) | ||
.build() | ||
.verify(token); | ||
} catch (TokenExpiredException e) { | ||
throw new BusinessException(TokenError.EXPIRED_TOKEN); | ||
} catch (Exception e) { | ||
throw new BusinessException(TokenError.INVALID_TOKEN); | ||
} | ||
} | ||
} |
23 changes: 23 additions & 0 deletions
23
app/api/src/main/java/org/example/security/token/RefreshTokenProcessor.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,23 @@ | ||
package org.example.security.token; | ||
|
||
import com.auth0.jwt.interfaces.DecodedJWT; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import lombok.RequiredArgsConstructor; | ||
import org.example.security.dto.TokenParam; | ||
import org.springframework.stereotype.Component; | ||
|
||
@Component | ||
@RequiredArgsConstructor | ||
public class RefreshTokenProcessor { | ||
|
||
private final JWTProcessor jwtProcessor; | ||
private final JWTGenerator jwtGenerator; | ||
|
||
public TokenParam process(HttpServletRequest request, HttpServletResponse response) { | ||
String accessToken = jwtProcessor.extractAccessToken(request); | ||
String refreshToken = jwtProcessor.extractRefreshToken(request); | ||
|
||
return new TokenParam(accessToken, refreshToken); | ||
} | ||
} |
94 changes: 94 additions & 0 deletions
94
app/api/src/main/java/org/example/security/vo/TokenError.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,94 @@ | ||
package org.example.security.vo; | ||
|
||
import exception.BusinessError; | ||
|
||
public enum TokenError implements BusinessError { | ||
|
||
WRONG_HEADER { | ||
@Override | ||
public int getHttpStatus() { | ||
return 401; | ||
} | ||
|
||
@Override | ||
public String getErrorCode() { | ||
return "TKN-001"; | ||
} | ||
|
||
@Override | ||
public String getClientMessage() { | ||
return "유효하지 않은 토큰입니다."; | ||
} | ||
|
||
@Override | ||
public String getLogMessage() { | ||
return "요청 헤더가 잘못 처리되었습니다."; | ||
} | ||
}, | ||
|
||
EXPIRED_TOKEN { | ||
@Override | ||
public int getHttpStatus() { | ||
return 401; | ||
} | ||
|
||
@Override | ||
public String getErrorCode() { | ||
return "TKN-002"; | ||
} | ||
|
||
@Override | ||
public String getClientMessage() { | ||
return "토큰이 만료되었습니다."; | ||
} | ||
|
||
@Override | ||
public String getLogMessage() { | ||
return "토큰이 만료되었습니다."; | ||
} | ||
}, | ||
|
||
INVALID_TOKEN { | ||
@Override | ||
public int getHttpStatus() { | ||
return 401; | ||
} | ||
|
||
@Override | ||
public String getErrorCode() { | ||
return "TKN-003"; | ||
} | ||
|
||
@Override | ||
public String getClientMessage() { | ||
return "유효하지 않은 토큰입니다."; | ||
} | ||
|
||
@Override | ||
public String getLogMessage() { | ||
return "만료 이외의 토큰 오류가 발생했습니다."; | ||
} | ||
}, | ||
|
||
INVALID_CLAIM { | ||
@Override | ||
public int getHttpStatus() { | ||
return 401; | ||
} | ||
|
||
@Override | ||
public String getErrorCode() { | ||
return "TKN-003"; | ||
} | ||
|
||
@Override | ||
public String getClientMessage() { | ||
return "유효하지 않은 토큰입니다."; | ||
} | ||
|
||
@Override | ||
public String getLogMessage() { | ||
return "토큰 claim 구성에 오류가 있습니다."; | ||
} | ||
} | ||
} |
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,5 +1,16 @@ | ||
package org.example.vo; | ||
|
||
import lombok.Getter; | ||
|
||
@Getter | ||
public enum UserRoleApiType { | ||
GUEST, USER, ADMIN | ||
GUEST("ROLE_GUEST"), | ||
USER("ROLE_USER"), | ||
ADMIN("ROLE_ADMIN"); | ||
|
||
final String authority; | ||
|
||
UserRoleApiType(String authority) { | ||
this.authority = authority; | ||
} | ||
} |
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