-
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.
Merge remote-tracking branch 'origin/main'
- Loading branch information
Showing
31 changed files
with
592 additions
and
610 deletions.
There are no files selected for viewing
21 changes: 21 additions & 0 deletions
21
src/main/java/com/example/reddiserver/auth/filter/CustomAuthenticationDetails.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,21 @@ | ||
package com.example.reddiserver.auth.filter; | ||
|
||
import jakarta.servlet.http.HttpServletRequest; | ||
import lombok.Getter; | ||
import org.springframework.security.core.Authentication; | ||
import org.springframework.security.core.context.SecurityContextHolder; | ||
import org.springframework.security.web.authentication.WebAuthenticationDetails; | ||
|
||
@Getter | ||
public class CustomAuthenticationDetails extends WebAuthenticationDetails { | ||
|
||
private final Long userId; | ||
|
||
public CustomAuthenticationDetails(Long userId, HttpServletRequest request) { | ||
super(request); | ||
this.userId = userId; | ||
} | ||
|
||
|
||
|
||
} |
116 changes: 116 additions & 0 deletions
116
src/main/java/com/example/reddiserver/auth/filter/JwtRequestFilter.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,116 @@ | ||
package com.example.reddiserver.auth.filter; | ||
|
||
import com.example.reddiserver.auth.service.JwtTokenProvider; | ||
import com.example.reddiserver.entity.enums.Authority; | ||
import io.jsonwebtoken.ExpiredJwtException; | ||
import io.jsonwebtoken.JwtException; | ||
import io.jsonwebtoken.MalformedJwtException; | ||
import jakarta.servlet.FilterChain; | ||
import jakarta.servlet.ServletException; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.h2.engine.Role; | ||
import org.springframework.http.MediaType; | ||
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; | ||
|
||
import java.io.IOException; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.UUID; | ||
|
||
@Component | ||
@Slf4j | ||
@RequiredArgsConstructor | ||
public class JwtRequestFilter extends OncePerRequestFilter { | ||
|
||
private final JwtTokenProvider jwtTokenProvider; | ||
|
||
@Override | ||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { | ||
String accessTokenHeader = request.getHeader("Authorization"); | ||
String refreshTokenHeader = request.getHeader("RefreshToken"); | ||
|
||
// 헤더에 토큰 안 담기는 경우는 인가 안하고 그대로 필터 종료 | ||
if (accessTokenHeader == null) { | ||
filterChain.doFilter(request, response); | ||
return; | ||
} | ||
|
||
// 헤더에 토큰 담기는 경우 | ||
if (accessTokenHeader.startsWith("Bearer ")) { | ||
String token = accessTokenHeader.substring(7); | ||
|
||
try { | ||
// 토큰이 유효한 경우에 수행할 작업 | ||
jwtTokenProvider.validateToken(token); | ||
|
||
Map<String, String> payload = jwtTokenProvider.getPayload(token); | ||
|
||
Long userId = Long.valueOf(payload.get("userId")); | ||
|
||
System.out.println("필터 userId = " + userId); | ||
|
||
|
||
// 권한 부여 | ||
log.info("=====권한 부여====="); | ||
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userId, null, List.of(new SimpleGrantedAuthority(Authority.ROLE_USER.name()))); | ||
|
||
|
||
// Detail을 넣어줌 | ||
// userId와 uuid를 추가로 넣어줌 | ||
authenticationToken.setDetails(new CustomAuthenticationDetails(userId, request)); | ||
|
||
SecurityContextHolder.getContext().setAuthentication(authenticationToken); | ||
|
||
log.info("[+] Token in SecurityContextHolder"); | ||
filterChain.doFilter(request, response); | ||
|
||
} catch (ExpiredJwtException e) { | ||
// 토큰이 만료된 경우에 수행할 작업 | ||
// refresh 토큰이 있으면 검증하고 access 토큰 재발급 | ||
log.error("=====만료된 토큰입니다=====", e); | ||
throw e; | ||
|
||
// errorResponse(response, "jwt필터 - 만료된 토큰입니다", HttpServletResponse.SC_UNAUTHORIZED); | ||
|
||
} catch (MalformedJwtException e) { | ||
// 토큰 형식이 잘못된 경우에 수행할 작업 | ||
log.error("=====잘못된 형식의 토큰=====", e); | ||
throw e; | ||
// errorResponse(response, "jwt필터 - 잘못된 형식의 토큰입니다", HttpServletResponse.SC_BAD_REQUEST); | ||
|
||
} catch (JwtException e) { | ||
// 기타 예외 처리 | ||
log.error("=====유효하지 않은 토큰입니다=====", e); | ||
throw e; | ||
// errorResponse(response, "jwt필터 - 유효하지 않은 토큰입니다", HttpServletResponse.SC_FORBIDDEN); | ||
} | ||
|
||
|
||
} | ||
|
||
} | ||
|
||
|
||
// private boolean userHasPermission(String userId) { | ||
// // 여기에서 해당 사용자의 권한을 확인하는 로직 구현 | ||
// // userId를 기반으로 데이터베이스 등에서 해당 사용자의 권한 정보를 가져와서 확인 | ||
// // 권한이 있다면 true를 반환, 권한이 없다면 false를 반환 | ||
// return true; // 또는 false | ||
// } | ||
//} | ||
|
||
|
||
private void errorResponse(HttpServletResponse response, String errMsg, int httpStatus) throws IOException { | ||
response.setStatus(httpStatus); | ||
response.setContentType(MediaType.APPLICATION_JSON_VALUE); | ||
response.setCharacterEncoding("UTF-8"); | ||
response.getWriter().write("{\"error\": \"" + errMsg + "\"}"); | ||
} | ||
} |
68 changes: 68 additions & 0 deletions
68
src/main/java/com/example/reddiserver/auth/oauth/GoogleOAuth.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,68 @@ | ||
package com.example.reddiserver.auth.oauth; | ||
|
||
import com.example.reddiserver.dto.auth.response.GoogleTokenResponseDto; | ||
import com.example.reddiserver.dto.auth.response.GoogleUserResponseDto; | ||
import lombok.Getter; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.http.HttpHeaders; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.web.reactive.function.client.WebClient; | ||
|
||
import java.util.Map; | ||
|
||
@Component | ||
@RequiredArgsConstructor | ||
@Getter | ||
public class GoogleOAuth { | ||
|
||
private final String GOOGLE_LOGIN_URL = "https://accounts.google.com/o/oauth2/v2/auth"; | ||
private final String GOOGLE_TOKEN_REQUEST_URL = "https://oauth2.googleapis.com/token"; | ||
private final String GOOGLE_USERINFO_REQUEST_URL = "https://www.googleapis.com/oauth2/v3/userinfo"; | ||
|
||
@Value("${spring.security.oauth2.client.registration.google.redirectUri}") | ||
private String REDIRECT_URI; | ||
|
||
@Value("${spring.security.oauth2.client.registration.google.client-id}") | ||
private String CLIENT_ID; | ||
|
||
@Value("${spring.security.oauth2.client.registration.google.client-secret}") | ||
private String CLIENT_SECRET; | ||
|
||
private final WebClient webClient; | ||
|
||
public GoogleTokenResponseDto requestToken(String code) { | ||
Map<String, Object> params = Map.of( | ||
"code", code, | ||
"client_id", CLIENT_ID, | ||
"client_secret", CLIENT_SECRET, | ||
"redirect_uri", REDIRECT_URI, | ||
"grant_type", "authorization_code" | ||
); | ||
|
||
return webClient.post() | ||
.uri(GOOGLE_TOKEN_REQUEST_URL) | ||
.bodyValue(params) | ||
.retrieve() | ||
.bodyToMono(GoogleTokenResponseDto.class) | ||
.block(); | ||
} | ||
|
||
|
||
public GoogleUserResponseDto requestUserInfo(GoogleTokenResponseDto googleToken) { | ||
|
||
HttpHeaders headers = new HttpHeaders(); | ||
headers.add("Authorization", "Bearer " + googleToken.getAccess_token()); | ||
|
||
GoogleUserResponseDto googleUserResponse = webClient.get() | ||
.uri(GOOGLE_USERINFO_REQUEST_URL) | ||
.headers(httpHeaders -> httpHeaders.setBearerAuth(googleToken.getAccess_token())) | ||
.retrieve() | ||
.bodyToMono(GoogleUserResponseDto.class) | ||
.block(); | ||
|
||
System.out.println("requestUserInfo response = " + googleUserResponse); | ||
|
||
return googleUserResponse; | ||
} | ||
} |
2 changes: 1 addition & 1 deletion
2
...rity/oauth/provider/GoogleOAuth2User.java → ...auth/oauth/provider/GoogleOAuth2User.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
2 changes: 1 addition & 1 deletion
2
...curity/oauth/provider/OAuth2UserInfo.java → ...r/auth/oauth/provider/OAuth2UserInfo.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
115 changes: 115 additions & 0 deletions
115
src/main/java/com/example/reddiserver/auth/service/JwtTokenProvider.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,115 @@ | ||
package com.example.reddiserver.auth.service; | ||
|
||
import io.jsonwebtoken.*; | ||
import io.jsonwebtoken.security.Keys; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.beans.factory.InitializingBean; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.stereotype.Component; | ||
|
||
import java.security.Key; | ||
import java.util.Base64; | ||
import java.util.Date; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
@Component | ||
@Slf4j | ||
@RequiredArgsConstructor | ||
public class JwtTokenProvider implements InitializingBean { | ||
|
||
@Value("${spring.jwt.secret}") | ||
private String JWT_SECRET; | ||
|
||
private Key key; | ||
|
||
private final Long accessTokenExpiredTime = 1000 * 60L * 60L * 24L; // 유효시간 24시간 (임시 변경) | ||
private final Long refreshTokenExpiredTime = 1000 * 60L * 60L * 24L * 14L; // 유효시간 14일 | ||
|
||
private final Long shortAccessTokenExpiredTime = 1000 * 60L * 5; // 유효시간 5분 | ||
|
||
@Override | ||
public void afterPropertiesSet() throws Exception { | ||
Base64.Decoder decoders = Base64.getDecoder(); | ||
byte[] keyBytes = decoders.decode(JWT_SECRET); | ||
this.key = Keys.hmacShaKeyFor(keyBytes); | ||
} | ||
|
||
public String createAccessToken(Map<String, String> payload) { | ||
return createJwtToken(payload, accessTokenExpiredTime); | ||
} | ||
|
||
public String createRefreshToken(Map<String, String> payload) { | ||
return createJwtToken(payload, refreshTokenExpiredTime); | ||
} | ||
|
||
public String createJwtToken(Map<String, String> payload, long expireLength) { | ||
Date now = new Date(); | ||
Date validity = new Date(now.getTime() + expireLength); | ||
|
||
try { | ||
return Jwts.builder() | ||
.setClaims(payload) | ||
.setIssuedAt(now) | ||
.setExpiration(validity) | ||
.signWith(key, SignatureAlgorithm.HS512) | ||
.compact(); | ||
} catch (Exception e) { | ||
throw new RuntimeException(e); | ||
} | ||
|
||
} | ||
|
||
public Map<String, String> getPayload(String token) { | ||
try { | ||
Claims claims = Jwts.parserBuilder() | ||
.setSigningKey(key) | ||
.build() | ||
.parseClaimsJws(token) | ||
.getBody(); | ||
|
||
// 원하는 클레임 값 추출하여 Map으로 저장 | ||
Map<String, String> payload = new HashMap<>(); | ||
payload.put("userId", claims.get("userId", String.class)); | ||
|
||
return payload; | ||
|
||
} catch (JwtException e) { | ||
System.err.println("Error Type: " + e.getClass().getName()); | ||
System.err.println("Error Message: " + e.getMessage()); | ||
throw new JwtException("유효하지 않은 토큰 입니다"); | ||
} | ||
} | ||
|
||
public void validateToken(String token) throws JwtException { | ||
try { | ||
Jws<Claims> claimsJws = Jwts.parserBuilder() | ||
.setSigningKey(key) | ||
.build() | ||
.parseClaimsJws(token); | ||
|
||
Date expiration = claimsJws.getBody().getExpiration(); | ||
Date now = new Date(); | ||
|
||
if (expiration.before(now)) { | ||
throw new ExpiredJwtException(null, null, "Token expired"); | ||
} | ||
} catch (ExpiredJwtException e) { | ||
// 토큰이 만료된 경우 처리 | ||
log.error("===Token expired: {}===", e.getMessage()); | ||
throw e; | ||
} catch (MalformedJwtException e) { | ||
// 토큰 형식이 잘못된 경우 처리 | ||
log.error("====Malformed token: {}===", e.getMessage()); | ||
throw e; | ||
} catch (JwtException | IllegalArgumentException e) { | ||
// 기타 예외 처리 | ||
log.error("===Invalid token: {}===", e.getMessage()); | ||
throw e; | ||
} | ||
} | ||
|
||
|
||
|
||
} |
Oops, something went wrong.