From b4c471022a87825f4401b60941df4962194490cf Mon Sep 17 00:00:00 2001 From: sunwoong Date: Wed, 3 Jan 2024 15:16:30 +0900 Subject: [PATCH 01/17] =?UTF-8?q?chore:=20spring=20security=20&=20jwt=20?= =?UTF-8?q?=EC=9D=98=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80=20(#3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doorip-api/build.gradle | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/doorip-api/build.gradle b/doorip-api/build.gradle index 044e402..6c7cae5 100644 --- a/doorip-api/build.gradle +++ b/doorip-api/build.gradle @@ -1,7 +1,11 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.5' + implementation group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.5' + implementation group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.5' runtimeOnly 'com.h2database:h2' runtimeOnly 'com.mysql:mysql-connector-j' implementation project(path: ':doorip-domain') From 574a02dc0b384d2d945caf4463f67cfc5fd14c26 Mon Sep 17 00:00:00 2001 From: sunwoong Date: Wed, 3 Jan 2024 15:17:10 +0900 Subject: [PATCH 02/17] =?UTF-8?q?feat:=20=EC=9D=B8=EC=A6=9D=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EC=98=A4=EB=A5=98=20=EB=A9=94=EC=8B=9C=EC=A7=80=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/org/doorip/message/ErrorMessage.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doorip-common/src/main/java/org/doorip/message/ErrorMessage.java b/doorip-common/src/main/java/org/doorip/message/ErrorMessage.java index bf59122..76aadb7 100644 --- a/doorip-common/src/main/java/org/doorip/message/ErrorMessage.java +++ b/doorip-common/src/main/java/org/doorip/message/ErrorMessage.java @@ -17,6 +17,13 @@ public enum ErrorMessage { * 401 Unauthorized */ UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "e4010", "리소스 접근 권한이 없습니다."), + INVALID_ACCESS_TOKEN(HttpStatus.UNAUTHORIZED, "e4011", "액세스 토큰의 형식이 올바르지 않습니다. Bearer 타입을 확인해 주세요."), + INVALID_ACCESS_TOKEN_VALUE(HttpStatus.UNAUTHORIZED, "e4012", "액세스 토큰의 값이 올바르지 않습니다."), + EXPIRED_ACCESS_TOKEN(HttpStatus.UNAUTHORIZED, "e4013", "액세스 토큰이 만료되었습니다. 재발급 받아주세요."), + INVALID_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED, "e4014", "리프레시 토큰의 형식이 올바르지 않습니다."), + INVALID_REFRESH_TOKEN_VALUE(HttpStatus.UNAUTHORIZED, "e4015", "리프레시 토큰의 값이 올바르지 않습니다."), + EXPIRED_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED, "e4016", "리프레시 토큰이 만료되었습니다. 다시 로그인해 주세요."), + MISMATCH_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED, "e4017", "리프레시 토큰이 일치하지 않습니다."), /** * 403 Forbidden From b4df8e0dc32e000029e22c231d8b222bc5b8a52f Mon Sep 17 00:00:00 2001 From: sunwoong Date: Wed, 3 Jan 2024 15:17:42 +0900 Subject: [PATCH 03/17] =?UTF-8?q?feat:=20api=20=EB=AA=A8=EB=93=88=20?= =?UTF-8?q?=EC=A0=84=EC=97=AD=20=EC=83=81=EC=88=98=20=EA=B4=80=EB=A6=AC=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EA=B5=AC=ED=98=84=20(#3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doorip-api/src/main/java/org/doorip/common/Constants.java | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 doorip-api/src/main/java/org/doorip/common/Constants.java diff --git a/doorip-api/src/main/java/org/doorip/common/Constants.java b/doorip-api/src/main/java/org/doorip/common/Constants.java new file mode 100644 index 0000000..9f20a87 --- /dev/null +++ b/doorip-api/src/main/java/org/doorip/common/Constants.java @@ -0,0 +1,7 @@ +package org.doorip.common; + +public abstract class Constants { + public static final String AUTHORIZATION = "Authorization"; + public static final String BEARER = "Bearer "; + public static final String CHARACTER_TYPE = "utf-8"; +} From b85d5589fbfdafd959bf0233ad7e9ce85faf5db6 Mon Sep 17 00:00:00 2001 From: sunwoong Date: Wed, 3 Jan 2024 15:18:36 +0900 Subject: [PATCH 04/17] =?UTF-8?q?feat:=20jwt=20dto=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=20=EA=B5=AC=ED=98=84=20(#3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/doorip/auth/jwt/Token.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 doorip-api/src/main/java/org/doorip/auth/jwt/Token.java diff --git a/doorip-api/src/main/java/org/doorip/auth/jwt/Token.java b/doorip-api/src/main/java/org/doorip/auth/jwt/Token.java new file mode 100644 index 0000000..c9dc528 --- /dev/null +++ b/doorip-api/src/main/java/org/doorip/auth/jwt/Token.java @@ -0,0 +1,17 @@ +package org.doorip.auth.jwt; + +import lombok.AccessLevel; +import lombok.Builder; + +@Builder(access = AccessLevel.PRIVATE) +public record Token( + String accessToken, + String refreshToken +) { + public static Token of(String accessToken, String refreshToken) { + return Token.builder() + .accessToken(accessToken) + .refreshToken(refreshToken) + .build(); + } +} From 95ca8a09f81d230a253a1c6820e87fcf46fd7300 Mon Sep 17 00:00:00 2001 From: sunwoong Date: Wed, 3 Jan 2024 15:19:20 +0900 Subject: [PATCH 05/17] =?UTF-8?q?feat:=20jwt=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EA=B5=AC=ED=98=84=20(#3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/doorip/auth/jwt/JwtGenerator.java | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 doorip-api/src/main/java/org/doorip/auth/jwt/JwtGenerator.java diff --git a/doorip-api/src/main/java/org/doorip/auth/jwt/JwtGenerator.java b/doorip-api/src/main/java/org/doorip/auth/jwt/JwtGenerator.java new file mode 100644 index 0000000..5f1b146 --- /dev/null +++ b/doorip-api/src/main/java/org/doorip/auth/jwt/JwtGenerator.java @@ -0,0 +1,65 @@ +package org.doorip.auth.jwt; + +import io.jsonwebtoken.Header; +import io.jsonwebtoken.JwtParser; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.security.Key; +import java.util.Base64; +import java.util.Date; + +@Component +public class JwtGenerator { + @Value("${jwt.secret}") + private String secretKey; + @Value("${jwt.access-token-expire-time}") + private long ACCESS_TOKEN_EXPIRE_TIME; + @Value("${jwt.refresh-token-expire-time}") + private long REFRESH_TOKEN_EXPIRE_TIME; + + public String generateToken(Long userId, boolean isAccessToken) { + final Date now = generateNowDate(); + final Date expiration = generateExpirationDate(isAccessToken, now); + return Jwts.builder() + .setHeaderParam(Header.TYPE, Header.JWT_TYPE) + .setSubject(String.valueOf(userId)) + .setIssuedAt(now) + .setExpiration(expiration) + .signWith(getSigningKey(), SignatureAlgorithm.HS256) + .compact(); + } + + public JwtParser getJwtParser() { + return Jwts.parserBuilder() + .setSigningKey(getSigningKey()) + .build(); + } + + private Date generateNowDate() { + return new Date(); + } + + private Date generateExpirationDate(boolean isAccessToken, Date now) { + return new Date(now.getTime() + calculateExpirationTime(isAccessToken)); + } + + private Key getSigningKey() { + return Keys.hmacShaKeyFor(encodeSecretKey().getBytes()); + } + + private long calculateExpirationTime(boolean isAccessToken) { + if (isAccessToken) { + return ACCESS_TOKEN_EXPIRE_TIME; + } + return REFRESH_TOKEN_EXPIRE_TIME; + } + + private String encodeSecretKey() { + return Base64.getEncoder() + .encodeToString(secretKey.getBytes()); + } +} From c49a7027094f21922358f75435d763a416f58aa3 Mon Sep 17 00:00:00 2001 From: sunwoong Date: Wed, 3 Jan 2024 15:19:37 +0900 Subject: [PATCH 06/17] =?UTF-8?q?feat:=20jwt=20=EB=B0=9C=EA=B8=89=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EA=B5=AC=ED=98=84=20(#3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/doorip/auth/jwt/JwtProvider.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 doorip-api/src/main/java/org/doorip/auth/jwt/JwtProvider.java diff --git a/doorip-api/src/main/java/org/doorip/auth/jwt/JwtProvider.java b/doorip-api/src/main/java/org/doorip/auth/jwt/JwtProvider.java new file mode 100644 index 0000000..e7878c6 --- /dev/null +++ b/doorip-api/src/main/java/org/doorip/auth/jwt/JwtProvider.java @@ -0,0 +1,23 @@ +package org.doorip.auth.jwt; + +import io.jsonwebtoken.JwtParser; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@RequiredArgsConstructor +@Component +public class JwtProvider { + private final JwtGenerator jwtGenerator; + + public Token issueToken(Long userId) { + return Token.of(jwtGenerator.generateToken(userId, true), + jwtGenerator.generateToken(userId, false)); + } + + public Long getSubject(String token) { + JwtParser jwtParser = jwtGenerator.getJwtParser(); + return Long.valueOf(jwtParser.parseClaimsJws(token) + .getBody() + .getSubject()); + } +} From 9a9ed502f598da4818f286a757bdd0e2b90e369c Mon Sep 17 00:00:00 2001 From: sunwoong Date: Wed, 3 Jan 2024 15:19:50 +0900 Subject: [PATCH 07/17] =?UTF-8?q?feat:=20jwt=20=EA=B2=80=EC=A6=9D=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EA=B5=AC=ED=98=84=20(#3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/doorip/auth/jwt/JwtValidator.java | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 doorip-api/src/main/java/org/doorip/auth/jwt/JwtValidator.java diff --git a/doorip-api/src/main/java/org/doorip/auth/jwt/JwtValidator.java b/doorip-api/src/main/java/org/doorip/auth/jwt/JwtValidator.java new file mode 100644 index 0000000..d85b10d --- /dev/null +++ b/doorip-api/src/main/java/org/doorip/auth/jwt/JwtValidator.java @@ -0,0 +1,45 @@ +package org.doorip.auth.jwt; + +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.JwtParser; +import lombok.RequiredArgsConstructor; +import org.doorip.exception.UnauthorizedException; +import org.doorip.message.ErrorMessage; +import org.springframework.stereotype.Component; + +@RequiredArgsConstructor +@Component +public class JwtValidator { + private final JwtGenerator jwtGenerator; + + public void validateAccessToken(String accessToken) { + try { + parseToken(accessToken); + } catch (ExpiredJwtException e) { + throw new UnauthorizedException(ErrorMessage.EXPIRED_ACCESS_TOKEN); + } catch (Exception e) { + throw new UnauthorizedException(ErrorMessage.INVALID_ACCESS_TOKEN_VALUE); + } + } + + public void validateRefreshToken(String refreshToken) { + try { + parseToken(refreshToken); + } catch (ExpiredJwtException e) { + throw new UnauthorizedException(ErrorMessage.EXPIRED_REFRESH_TOKEN); + } catch (Exception e) { + throw new UnauthorizedException(ErrorMessage.INVALID_REFRESH_TOKEN_VALUE); + } + } + + public void equalsRefreshToken(String refreshToken, String storedRefreshToken) { + if (!refreshToken.equals(storedRefreshToken)) { + throw new UnauthorizedException(ErrorMessage.MISMATCH_REFRESH_TOKEN); + } + } + + private void parseToken(String token) { + JwtParser jwtParser = jwtGenerator.getJwtParser(); + jwtParser.parseClaimsJws(token); + } +} From 561c704b562e6db7892edbd8b5af6829aa872bb0 Mon Sep 17 00:00:00 2001 From: sunwoong Date: Wed, 3 Jan 2024 15:20:51 +0900 Subject: [PATCH 08/17] =?UTF-8?q?feat:=20jwt=20=EA=B2=80=EC=A6=9D=20&=20?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=20=ED=95=84=ED=84=B0=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?(#3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../doorip/auth/JwtAuthenticationFilter.java | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 doorip-api/src/main/java/org/doorip/auth/JwtAuthenticationFilter.java diff --git a/doorip-api/src/main/java/org/doorip/auth/JwtAuthenticationFilter.java b/doorip-api/src/main/java/org/doorip/auth/JwtAuthenticationFilter.java new file mode 100644 index 0000000..cf1a0e9 --- /dev/null +++ b/doorip-api/src/main/java/org/doorip/auth/JwtAuthenticationFilter.java @@ -0,0 +1,57 @@ +package org.doorip.auth; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.doorip.auth.jwt.JwtProvider; +import org.doorip.auth.jwt.JwtValidator; +import org.doorip.common.Constants; +import org.doorip.exception.UnauthorizedException; +import org.doorip.message.ErrorMessage; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.WebAuthenticationDetails; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +import static org.doorip.auth.UserAuthentication.createUserAuthentication; + +@RequiredArgsConstructor +public class JwtAuthenticationFilter extends OncePerRequestFilter { + private final JwtValidator jwtValidator; + private final JwtProvider jwtProvider; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + final String accessToken = getAccessToken(request); + jwtValidator.validateAccessToken(accessToken); + doAuthentication(request, jwtProvider.getSubject(accessToken)); + filterChain.doFilter(request, response); + } + + private String getAccessToken(HttpServletRequest request) { + String accessToken = request.getHeader(Constants.AUTHORIZATION); + if (StringUtils.hasText(accessToken) && accessToken.startsWith(Constants.BEARER)) { + return accessToken.substring(Constants.BEARER.length()); + } + throw new UnauthorizedException(ErrorMessage.INVALID_ACCESS_TOKEN); + } + + private void doAuthentication(HttpServletRequest request, Long userId) { + UserAuthentication authentication = createUserAuthentication(userId); + createAndSetWebAuthenticationDetails(request, authentication); + SecurityContext securityContext = SecurityContextHolder.getContext(); + securityContext.setAuthentication(authentication); + } + + private void createAndSetWebAuthenticationDetails(HttpServletRequest request, UserAuthentication authentication) { + WebAuthenticationDetailsSource webAuthenticationDetailsSource = new WebAuthenticationDetailsSource(); + WebAuthenticationDetails webAuthenticationDetails = webAuthenticationDetailsSource.buildDetails(request); + authentication.setDetails(webAuthenticationDetails); + } +} \ No newline at end of file From adb74b3f3d1a2a94e61e4a6ace98fa788df2f66d Mon Sep 17 00:00:00 2001 From: sunwoong Date: Wed, 3 Jan 2024 15:21:43 +0900 Subject: [PATCH 09/17] =?UTF-8?q?feat:=20=EC=9D=B8=EC=A6=9D=20=EC=8B=A4?= =?UTF-8?q?=ED=8C=A8=20=EC=98=88=EC=99=B8=20=ED=95=B8=EB=93=A4=EB=A7=81=20?= =?UTF-8?q?=ED=81=B4=EB=9E=98=EC=8A=A4=20=EA=B5=AC=ED=98=84=20(#3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/JwtAuthenticationEntryPoint.java | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 doorip-api/src/main/java/org/doorip/auth/JwtAuthenticationEntryPoint.java diff --git a/doorip-api/src/main/java/org/doorip/auth/JwtAuthenticationEntryPoint.java b/doorip-api/src/main/java/org/doorip/auth/JwtAuthenticationEntryPoint.java new file mode 100644 index 0000000..e377fff --- /dev/null +++ b/doorip-api/src/main/java/org/doorip/auth/JwtAuthenticationEntryPoint.java @@ -0,0 +1,40 @@ +package org.doorip.auth; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.doorip.common.ApiResponse; +import org.doorip.common.Constants; +import org.doorip.message.ErrorMessage; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.io.PrintWriter; + +@Slf4j +@Component +public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { + handleException(response); + } + + private void handleException(HttpServletResponse response) throws IOException { + setResponse(response, HttpStatus.UNAUTHORIZED, ErrorMessage.UNAUTHORIZED); + } + + private void setResponse(HttpServletResponse response, HttpStatus httpStatus, ErrorMessage errorMessage) throws IOException { + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setCharacterEncoding(Constants.CHARACTER_TYPE); + response.setStatus(httpStatus.value()); + PrintWriter writer = response.getWriter(); + writer.write(objectMapper.writeValueAsString(ApiResponse.of(errorMessage))); + } +} \ No newline at end of file From c44d65dd758ff4dbb3a7200945a2d92558fa638d Mon Sep 17 00:00:00 2001 From: sunwoong Date: Wed, 3 Jan 2024 15:23:33 +0900 Subject: [PATCH 10/17] =?UTF-8?q?feat:=20jwt=20=EA=B2=80=EC=A6=9D=20?= =?UTF-8?q?=EC=8B=A4=ED=8C=A8=20=EC=98=88=EC=99=B8=20=ED=95=B8=EB=93=A4?= =?UTF-8?q?=EB=A7=81=20=ED=95=84=ED=84=B0=20=EA=B5=AC=ED=98=84=20(#3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../doorip/auth/ExceptionHandlerFilter.java | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 doorip-api/src/main/java/org/doorip/auth/ExceptionHandlerFilter.java diff --git a/doorip-api/src/main/java/org/doorip/auth/ExceptionHandlerFilter.java b/doorip-api/src/main/java/org/doorip/auth/ExceptionHandlerFilter.java new file mode 100644 index 0000000..ac226bb --- /dev/null +++ b/doorip-api/src/main/java/org/doorip/auth/ExceptionHandlerFilter.java @@ -0,0 +1,50 @@ +package org.doorip.auth; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.FilterChain; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.doorip.common.ApiResponse; +import org.doorip.common.Constants; +import org.doorip.exception.UnauthorizedException; +import org.doorip.message.ErrorMessage; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; +import java.io.PrintWriter; + +public class ExceptionHandlerFilter extends OncePerRequestFilter { + private final ObjectMapper objectMapper = new ObjectMapper(); + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException { + try { + filterChain.doFilter(request, response); + } catch (UnauthorizedException e) { + handleUnauthorizedException(response, e); + } catch (Exception ee) { + handleException(response); + } + } + + private void handleUnauthorizedException(HttpServletResponse response, Exception e) throws IOException { + UnauthorizedException ue = (UnauthorizedException) e; + ErrorMessage errorMessage = ue.getErrorMessage(); + HttpStatus httpStatus = errorMessage.getHttpStatus(); + setResponse(response, httpStatus, errorMessage); + } + + private void handleException(HttpServletResponse response) throws IOException { + setResponse(response, HttpStatus.INTERNAL_SERVER_ERROR, ErrorMessage.INTERNAL_SERVER_ERROR); + } + + private void setResponse(HttpServletResponse response, HttpStatus httpStatus, ErrorMessage errorMessage) throws IOException { + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setCharacterEncoding(Constants.CHARACTER_TYPE); + response.setStatus(httpStatus.value()); + PrintWriter writer = response.getWriter(); + writer.write(objectMapper.writeValueAsString(ApiResponse.of(errorMessage))); + } +} From 3d6a4a86e9d9052adf1b1b204e2b5d085a6013a8 Mon Sep 17 00:00:00 2001 From: sunwoong Date: Wed, 3 Jan 2024 15:23:56 +0900 Subject: [PATCH 11/17] =?UTF-8?q?feat:=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20(#3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/doorip/auth/UserAuthentication.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 doorip-api/src/main/java/org/doorip/auth/UserAuthentication.java diff --git a/doorip-api/src/main/java/org/doorip/auth/UserAuthentication.java b/doorip-api/src/main/java/org/doorip/auth/UserAuthentication.java new file mode 100644 index 0000000..ff740e7 --- /dev/null +++ b/doorip-api/src/main/java/org/doorip/auth/UserAuthentication.java @@ -0,0 +1,16 @@ +package org.doorip.auth; + +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; + +import java.util.Collection; + +public class UserAuthentication extends UsernamePasswordAuthenticationToken { + private UserAuthentication(Object principal, Object credentials, Collection authorities) { + super(principal, credentials, authorities); + } + + public static UserAuthentication createUserAuthentication(Long userId) { + return new UserAuthentication(userId, null, null); + } +} From 0c02da8dcd82f04bc184d13026ed8ceb560aa79e Mon Sep 17 00:00:00 2001 From: sunwoong Date: Wed, 3 Jan 2024 15:24:13 +0900 Subject: [PATCH 12/17] =?UTF-8?q?feat:=20user=20id=20=EC=96=B4=EB=85=B8?= =?UTF-8?q?=ED=85=8C=EC=9D=B4=EC=85=98=20=EA=B5=AC=ED=98=84=20(#3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doorip-api/src/main/java/org/doorip/auth/UserId.java | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 doorip-api/src/main/java/org/doorip/auth/UserId.java diff --git a/doorip-api/src/main/java/org/doorip/auth/UserId.java b/doorip-api/src/main/java/org/doorip/auth/UserId.java new file mode 100644 index 0000000..f1ef4aa --- /dev/null +++ b/doorip-api/src/main/java/org/doorip/auth/UserId.java @@ -0,0 +1,11 @@ +package org.doorip.auth; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface UserId { +} From b1cb32fbb6f0b7511ffc2e7afd8f152a7736fec0 Mon Sep 17 00:00:00 2001 From: sunwoong Date: Wed, 3 Jan 2024 15:24:41 +0900 Subject: [PATCH 13/17] =?UTF-8?q?feat:=20user=20id=20=EC=96=B4=EB=85=B8?= =?UTF-8?q?=ED=85=8C=EC=9D=B4=EC=85=98=20resolver=20=EA=B5=AC=ED=98=84=20(?= =?UTF-8?q?#3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../doorip/auth/UserIdArgumentResolver.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 doorip-api/src/main/java/org/doorip/auth/UserIdArgumentResolver.java diff --git a/doorip-api/src/main/java/org/doorip/auth/UserIdArgumentResolver.java b/doorip-api/src/main/java/org/doorip/auth/UserIdArgumentResolver.java new file mode 100644 index 0000000..37ef464 --- /dev/null +++ b/doorip-api/src/main/java/org/doorip/auth/UserIdArgumentResolver.java @@ -0,0 +1,26 @@ +package org.doorip.auth; + +import org.springframework.core.MethodParameter; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; + +@Component +public class UserIdArgumentResolver implements HandlerMethodArgumentResolver { + @Override + public boolean supportsParameter(MethodParameter parameter) { + boolean hasUserIdAnnotation = parameter.hasParameterAnnotation(UserId.class); + boolean isLongType = Long.class.isAssignableFrom(parameter.getParameterType()); + return hasUserIdAnnotation && isLongType; + } + + @Override + public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { + return SecurityContextHolder.getContext() + .getAuthentication() + .getPrincipal(); + } +} From 63b192260e605b3083f0af6b47361ce5be96fc0d Mon Sep 17 00:00:00 2001 From: sunwoong Date: Wed, 3 Jan 2024 15:25:10 +0900 Subject: [PATCH 14/17] =?UTF-8?q?chore:=20argument=20resolver=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=20(#3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/doorip/auth/config/WebConfig.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 doorip-api/src/main/java/org/doorip/auth/config/WebConfig.java diff --git a/doorip-api/src/main/java/org/doorip/auth/config/WebConfig.java b/doorip-api/src/main/java/org/doorip/auth/config/WebConfig.java new file mode 100644 index 0000000..20c8aad --- /dev/null +++ b/doorip-api/src/main/java/org/doorip/auth/config/WebConfig.java @@ -0,0 +1,20 @@ +package org.doorip.auth.config; + +import lombok.RequiredArgsConstructor; +import org.doorip.auth.UserIdArgumentResolver; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.util.List; + +@RequiredArgsConstructor +@Configuration +public class WebConfig implements WebMvcConfigurer { + private final UserIdArgumentResolver userIdArgumentResolver; + + @Override + public void addArgumentResolvers(List resolvers) { + resolvers.add(userIdArgumentResolver); + } +} From d4229ecae18f3b30bae166055df2ddcd701aba03 Mon Sep 17 00:00:00 2001 From: sunwoong Date: Wed, 3 Jan 2024 15:25:57 +0900 Subject: [PATCH 15/17] =?UTF-8?q?chore:=20security=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?(#3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../doorip/auth/config/SecurityConfig.java | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 doorip-api/src/main/java/org/doorip/auth/config/SecurityConfig.java diff --git a/doorip-api/src/main/java/org/doorip/auth/config/SecurityConfig.java b/doorip-api/src/main/java/org/doorip/auth/config/SecurityConfig.java new file mode 100644 index 0000000..7159e83 --- /dev/null +++ b/doorip-api/src/main/java/org/doorip/auth/config/SecurityConfig.java @@ -0,0 +1,55 @@ +package org.doorip.auth.config; + +import lombok.RequiredArgsConstructor; +import org.doorip.auth.ExceptionHandlerFilter; +import org.doorip.auth.JwtAuthenticationEntryPoint; +import org.doorip.auth.JwtAuthenticationFilter; +import org.doorip.auth.jwt.JwtProvider; +import org.doorip.auth.jwt.JwtValidator; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@RequiredArgsConstructor +@EnableWebSecurity +@Configuration +public class SecurityConfig { + private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; + private final JwtValidator jwtValidator; + private final JwtProvider jwtProvider; + + private static final String[] whiteList = {"/api/users/signin", "/api/users/signup", "/api/users/reissue", "/"}; + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + return http + .csrf(AbstractHttpConfigurer::disable) + .formLogin(AbstractHttpConfigurer::disable) + .httpBasic(AbstractHttpConfigurer::disable) + .sessionManagement(sessionManagementConfigurer -> + sessionManagementConfigurer + .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .exceptionHandling(exceptionHandlingConfigurer -> + exceptionHandlingConfigurer + .authenticationEntryPoint(jwtAuthenticationEntryPoint)) + .authorizeHttpRequests(authorizationManagerRequestMatcherRegistry -> { + authorizationManagerRequestMatcherRegistry + .anyRequest() + .authenticated(); + }) + .addFilterBefore(new JwtAuthenticationFilter(jwtValidator, jwtProvider), UsernamePasswordAuthenticationFilter.class) + .addFilterBefore(new ExceptionHandlerFilter(), JwtAuthenticationFilter.class) + .build(); + } + + @Bean + public WebSecurityCustomizer webSecurityCustomizer() { + return web -> web.ignoring().requestMatchers(whiteList); + } +} From 081cd2cb5f44f0cb583cf5bc13355d2b2152e8f0 Mon Sep 17 00:00:00 2001 From: sunwoong Date: Wed, 3 Jan 2024 15:34:43 +0900 Subject: [PATCH 16/17] =?UTF-8?q?feat:=20=EC=98=88=EC=99=B8=20=ED=99=95?= =?UTF-8?q?=EC=9D=B8=20=EB=A1=9C=EA=B7=B8=20=EC=B6=94=EA=B0=80=20(#3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/doorip/auth/ExceptionHandlerFilter.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/doorip-api/src/main/java/org/doorip/auth/ExceptionHandlerFilter.java b/doorip-api/src/main/java/org/doorip/auth/ExceptionHandlerFilter.java index ac226bb..76660e4 100644 --- a/doorip-api/src/main/java/org/doorip/auth/ExceptionHandlerFilter.java +++ b/doorip-api/src/main/java/org/doorip/auth/ExceptionHandlerFilter.java @@ -4,6 +4,7 @@ import jakarta.servlet.FilterChain; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; import org.doorip.common.ApiResponse; import org.doorip.common.Constants; import org.doorip.exception.UnauthorizedException; @@ -15,6 +16,7 @@ import java.io.IOException; import java.io.PrintWriter; +@Slf4j public class ExceptionHandlerFilter extends OncePerRequestFilter { private final ObjectMapper objectMapper = new ObjectMapper(); @@ -25,7 +27,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse } catch (UnauthorizedException e) { handleUnauthorizedException(response, e); } catch (Exception ee) { - handleException(response); + handleException(response, ee); } } @@ -36,7 +38,8 @@ private void handleUnauthorizedException(HttpServletResponse response, Exception setResponse(response, httpStatus, errorMessage); } - private void handleException(HttpServletResponse response) throws IOException { + private void handleException(HttpServletResponse response, Exception e) throws IOException { + log.error(">>> Exception Handler Filter : ", e); setResponse(response, HttpStatus.INTERNAL_SERVER_ERROR, ErrorMessage.INTERNAL_SERVER_ERROR); } From 3692f7d250ea46da354aec529a113a2ee22b87e5 Mon Sep 17 00:00:00 2001 From: sunwoong Date: Wed, 3 Jan 2024 15:36:07 +0900 Subject: [PATCH 17/17] =?UTF-8?q?chore:=20=EB=9E=8C=EB=8B=A4=EC=8B=9D=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=A0=95=EB=A0=AC=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?(#3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/org/doorip/auth/config/SecurityConfig.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/doorip-api/src/main/java/org/doorip/auth/config/SecurityConfig.java b/doorip-api/src/main/java/org/doorip/auth/config/SecurityConfig.java index 7159e83..e221629 100644 --- a/doorip-api/src/main/java/org/doorip/auth/config/SecurityConfig.java +++ b/doorip-api/src/main/java/org/doorip/auth/config/SecurityConfig.java @@ -38,11 +38,10 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .exceptionHandling(exceptionHandlingConfigurer -> exceptionHandlingConfigurer .authenticationEntryPoint(jwtAuthenticationEntryPoint)) - .authorizeHttpRequests(authorizationManagerRequestMatcherRegistry -> { - authorizationManagerRequestMatcherRegistry - .anyRequest() - .authenticated(); - }) + .authorizeHttpRequests(authorizationManagerRequestMatcherRegistry -> + authorizationManagerRequestMatcherRegistry + .anyRequest() + .authenticated()) .addFilterBefore(new JwtAuthenticationFilter(jwtValidator, jwtProvider), UsernamePasswordAuthenticationFilter.class) .addFilterBefore(new ExceptionHandlerFilter(), JwtAuthenticationFilter.class) .build();