-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
9조 BE 코드리뷰 2회차 #36
9조 BE 코드리뷰 2회차 #36
Changes from all commits
35bcf42
233084a
adcd15e
60d69c0
75bbfea
817e4ef
203326b
d12f481
69122c9
4ec6419
7b2c575
96fbb00
5cc28b6
699df05
aa2dd0b
4db141d
e598d57
b9fa473
3359119
6f7a3ab
c1c0608
d374059
3b4ada5
3b8dcac
b1f875d
a1b891b
cc9709d
2cd742a
974c807
efdac3a
f5b0587
a479f51
c4129bd
77c60cd
c49e395
bf79893
5595140
5f0c1d1
9c785a8
c5054d8
3357746
e3d7758
2183a14
b1f5377
da981c8
c402de3
856f034
a2725c7
e4ee47b
21cf8a0
0df7e23
f788326
a6c27e1
0eace57
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package com.helpmeCookies.global.config; | ||
|
||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.data.jpa.repository.config.EnableJpaAuditing; | ||
|
||
@Configuration | ||
@EnableJpaAuditing | ||
public class JpaConfig { | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
package com.helpmeCookies.global.jwt; | ||
|
||
import java.security.Key; | ||
import java.util.Date; | ||
|
||
import javax.crypto.spec.SecretKeySpec; | ||
|
||
import org.springframework.beans.factory.InitializingBean; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.stereotype.Component; | ||
|
||
import io.jsonwebtoken.Claims; | ||
import io.jsonwebtoken.Jwts; | ||
import io.jsonwebtoken.SignatureAlgorithm; | ||
|
||
@Component | ||
public class JwtProvider implements InitializingBean { | ||
@Value("${jwt.secret}") | ||
private String secret; | ||
@Value("${jwt.access-token-expire-time}") | ||
private long accessTokenExpireTime; | ||
@Value("${jwt.refresh-token-expire-time}") | ||
private long refreshTokenExpireTime; | ||
private Key secretKey; | ||
private static final String ROLE = "role"; | ||
private static final String IS_ACCESS_TOKEN = "isAccessToken"; | ||
private static final String HEADER_PREFIX = "Bearer "; | ||
|
||
public String parseHeader(String header) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. parseHeader는 JwtProvider와는 다른 ex |
||
if (header == null || header.isEmpty()) { | ||
throw new IllegalArgumentException("Authorization 헤더가 없습니다."); | ||
} else if (!header.startsWith(HEADER_PREFIX)) { | ||
throw new IllegalArgumentException("Authorization 올바르지 않습니다."); | ||
} else if (header.split(" ").length != 2) { | ||
throw new IllegalArgumentException("Authorization 올바르지 않습니다."); | ||
} | ||
|
||
return header.split(" ")[1]; | ||
} | ||
|
||
public JwtToken createToken(JwtUser jwtUser) { | ||
String accessToken = generateToken(jwtUser, true); | ||
String refreshToken = generateToken(jwtUser, false); | ||
return JwtToken.builder() | ||
.accessToken(accessToken) | ||
.refreshToken(refreshToken) | ||
.build(); | ||
} | ||
|
||
// 유요한 토큰인지 확인 | ||
public boolean validateToken(String rawToken, boolean isAccessToken) { | ||
try { | ||
// 엑세스 토큰인지 확인 | ||
Claims claims = extractClaims(rawToken); | ||
if (claims.get(IS_ACCESS_TOKEN, Boolean.class) != isAccessToken) { | ||
return false; | ||
} | ||
// 만료시간 확인 | ||
return !claims.getExpiration().before(new Date()); | ||
} catch (Exception e) { | ||
return false; | ||
} | ||
} | ||
|
||
/** | ||
* refreshToken을 통해, accessToken을 재발급하는 메서드. | ||
* refreshToken의 유효성을 검사하고, isAccessToken이 true일때만 accessToken을 재발급한다. | ||
* TODO: refreshToken을 저장하고, 저장된 refreshToken과 비교하는 로직 필요 | ||
*/ | ||
public String reissueAccessToken(String refreshToken) { | ||
Claims claims = extractClaims(refreshToken); | ||
if (claims.get(IS_ACCESS_TOKEN, Boolean.class)) { | ||
throw new IllegalArgumentException("리프레시 토큰이 아닙니다."); | ||
} | ||
JwtUser jwtUser = claimsToJwtUser(claims); | ||
return generateToken(jwtUser, true); | ||
} | ||
|
||
/** | ||
* [validateToken] 이후 호출하는 메서드. | ||
* rawToken을 통해 JwtUser를 추출한다. | ||
* [jwtUser]는 userId와 role을 가지고 있다. 즉 JWT에 저장된 정보를 추출한다. | ||
*/ | ||
public JwtUser getJwtUser(String rawToken) { | ||
Claims claims = extractClaims(rawToken); | ||
return claimsToJwtUser(claims); | ||
} | ||
|
||
private JwtUser claimsToJwtUser(Claims claims) { | ||
String userId = claims.getSubject(); | ||
return JwtUser.of(Long.parseLong(userId)); | ||
} | ||
|
||
/** | ||
* Jwt 토큰생성 | ||
* accessToken과 refreshToken의 다른점은 만료시간과, isAccessToken이다. | ||
*/ | ||
private String generateToken(JwtUser jwtUser, boolean isAccessToken) { | ||
long expireTime = isAccessToken ? accessTokenExpireTime : refreshTokenExpireTime; | ||
Date expireDate = new Date(System.currentTimeMillis() + expireTime); | ||
return Jwts.builder() | ||
.signWith(secretKey) | ||
.claim(IS_ACCESS_TOKEN, isAccessToken) | ||
.setSubject(jwtUser.getId().toString()) | ||
.setExpiration(expireDate) | ||
.compact(); | ||
} | ||
|
||
|
||
private Claims extractClaims(String rawToken) { | ||
return Jwts.parserBuilder() | ||
.setSigningKey(secretKey) | ||
.build() | ||
.parseClaimsJws(rawToken) | ||
.getBody(); | ||
} | ||
|
||
/** | ||
* HS256방식의 키를 생성한다. | ||
*/ | ||
@Override | ||
public void afterPropertiesSet() { | ||
secretKey = new SecretKeySpec(secret.getBytes(), SignatureAlgorithm.HS256.getJcaName()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package com.helpmeCookies.global.jwt; | ||
|
||
import lombok.Builder; | ||
import lombok.Getter; | ||
|
||
@Getter | ||
@Builder | ||
public class JwtToken { | ||
private String accessToken; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. final 키워드를 써도 괜찮지 않을까요? |
||
private String refreshToken; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package com.helpmeCookies.global.jwt; | ||
|
||
import java.util.Collection; | ||
import java.util.List; | ||
|
||
import org.springframework.security.core.GrantedAuthority; | ||
import org.springframework.security.core.authority.SimpleGrantedAuthority; | ||
import org.springframework.security.core.userdetails.UserDetails; | ||
|
||
import lombok.Builder; | ||
import lombok.Getter; | ||
|
||
@Builder | ||
@Getter | ||
public class JwtUser implements UserDetails { | ||
private Long id; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 해당 클래스의 필드들도 final 키워드를 붙일수 있을까요? |
||
private String username; | ||
private Collection<? extends GrantedAuthority> authorities; | ||
|
||
public static JwtUser of(Long id) { | ||
return JwtUser.builder() | ||
.id(id) | ||
.build(); | ||
} | ||
|
||
@Override | ||
// 임시 기본 권한을 USER로 설정 | ||
public Collection<? extends GrantedAuthority> getAuthorities() { | ||
return List.of(new SimpleGrantedAuthority("ROLE_" + "USER")); | ||
} | ||
|
||
@Override | ||
public String getPassword() { | ||
return null; | ||
} | ||
|
||
@Override | ||
public String getUsername() { | ||
return this.username; | ||
} | ||
|
||
@Override | ||
public boolean isAccountNonExpired() { | ||
return true; | ||
} | ||
|
||
@Override | ||
public boolean isAccountNonLocked() { | ||
return true; | ||
} | ||
|
||
@Override | ||
public boolean isCredentialsNonExpired() { | ||
return true; | ||
} | ||
|
||
@Override | ||
public boolean isEnabled() { | ||
return true; | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package com.helpmeCookies.global.security; | ||
|
||
import java.io.IOException; | ||
import java.io.PrintWriter; | ||
|
||
import org.springframework.http.MediaType; | ||
import org.springframework.security.access.AccessDeniedException; | ||
import org.springframework.security.web.access.AccessDeniedHandler; | ||
import org.springframework.stereotype.Component; | ||
|
||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
|
||
import jakarta.servlet.ServletException; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
|
||
@Slf4j | ||
@Component | ||
@RequiredArgsConstructor | ||
public class JwtAccessDeniedHandler implements AccessDeniedHandler { | ||
private final ObjectMapper objectMapper; | ||
|
||
@Override | ||
public void handle(HttpServletRequest request, HttpServletResponse response, | ||
AccessDeniedException accessDeniedException) { | ||
log.error("Token : {}", request.getHeader("Authorization")); | ||
// TODO: 에러코드 추가 | ||
response.setStatus(403); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package com.helpmeCookies.global.security; | ||
|
||
import java.io.IOException; | ||
|
||
import org.springframework.security.core.AuthenticationException; | ||
import org.springframework.security.web.AuthenticationEntryPoint; | ||
import org.springframework.stereotype.Component; | ||
|
||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
|
||
import jakarta.servlet.ServletException; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
|
||
@Slf4j | ||
@Component | ||
@RequiredArgsConstructor | ||
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { | ||
private final ObjectMapper objectMapper; | ||
|
||
@Override | ||
public void commence(HttpServletRequest request, HttpServletResponse response, | ||
AuthenticationException authException) throws IOException, ServletException { | ||
log.debug("Token : {}", request.getHeader("Authorization")); | ||
response.setStatus(401); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
요렇게 Value로 바인딩 받는것들은 프로퍼티로 묶어서 바인딩 받으면 조금더 코드가 깔끔해질것 같아요