Skip to content
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

Dev auth refactoring #29

Merged
merged 4 commits into from
Dec 19, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.bit.lot.flower.auth.common.dto;

import com.bit.lot.flower.auth.common.valueobject.BaseId;
import com.bit.lot.flower.auth.common.valueobject.Role;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
public class AccessTokenExpiredResponse<T extends BaseId> {
private T id;
private Role role;
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
@NoArgsConstructor
@Getter
public class RenewAccessTokenDto<T extends BaseId> {
private T authId;
private T id;
private Role role;
private String expiredAccessToken;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ public class AuthPolicyController {

private final RenewRefreshTokenStrategy<AuthId> renewRefreshTokenStrategy;

@PostMapping("/api/auth/refresh-token")
@PostMapping("/refresh-token")
public ResponseEntity<String> renewRefreshToken(
@RequestBody RenewAccessTokenDto<AuthId> renewAccessTokenDto, HttpServletRequest request,
HttpServletResponse response) {
String newAccessToken = renewRefreshTokenStrategy.renew(renewAccessTokenDto.getAuthId(),
String newAccessToken = renewRefreshTokenStrategy.renew(renewAccessTokenDto.getId(),
renewAccessTokenDto.getRole(),renewAccessTokenDto.getExpiredAccessToken(),
request, response);
response.addHeader(SecurityPolicyStaticValue.TOKEN_AUTHORIZATION_HEADER_NAME,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.bit.lot.flower.auth.common.http.interceptor.filter;

import com.bit.lot.flower.auth.common.dto.AccessTokenExpiredResponse;
import com.bit.lot.flower.auth.common.dto.RenewAccessTokenDto;
import com.bit.lot.flower.auth.common.exception.ErrorDTO;
import com.bit.lot.flower.auth.common.util.JsonBinderUtil;
Expand Down Expand Up @@ -33,9 +34,9 @@ public void doFilterInternal(HttpServletRequest request, HttpServletResponse res
}
}

private RenewAccessTokenDto<AuthId> createDtoByToken (ExpiredJwtException e){
return RenewAccessTokenDto.<AuthId>builder()
.authId(new AuthId(Long.valueOf(e.getClaims().getSubject())))
private AccessTokenExpiredResponse<AuthId> createDtoByToken (ExpiredJwtException e){
return AccessTokenExpiredResponse.<AuthId>builder()
.id(new AuthId(Long.valueOf(e.getClaims().getSubject())))
.role(Role.valueOf(e.getClaims().get(
SecurityPolicyStaticValue.CLAIMS_ROLE_KEY_NAME, String.class))).build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,10 @@ public void doFilterInternal(HttpServletRequest request, HttpServletResponse res
throw new AuthenticationException("해당 토큰은 이미 로그아웃 처리된 토큰이라 사용할 수 없는 토큰입니다.");
}
try {
JwtUtil.isTokenValid(token);
JwtUtil.isAccessTokenValid(token);
} catch (ExpiredJwtException e) {
throw new ExpiredJwtException(e.getHeader(), e.getClaims(), "만료된 토큰입니다. Refresh토큰을 확인하세요");
response.setStatus(401);
throw new ExpiredJwtException(e.getHeader(), e.getClaims(), "Expired");
}
filterChain.doFilter(request, response);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ public void createRefreshToken(String userId, HttpServletResponse response) {
String refreshToken = JwtUtil.generateRefreshToken(String.valueOf(userId));
redisRefreshTokenUtil.saveRefreshToken(userId, refreshToken,
Long.parseLong(SecurityPolicyStaticValue.REFRESH_EXPIRATION_TIME));
response.setHeader(refreshCookieName,CookieUtil.createRefreshNoCORSCookie(refreshCookieName, refreshToken,
response.setHeader(refreshCookieName,
CookieUtil.createRefreshNoCORSCookie(refreshCookieName, refreshToken,
Duration.ofDays(1), domain).toString());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import com.bit.lot.flower.auth.common.valueobject.BaseId;
import com.bit.lot.flower.auth.common.valueobject.Role;
import com.bit.lot.flower.auth.common.valueobject.SecurityPolicyStaticValue;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtException;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
Expand All @@ -32,11 +34,16 @@ public String renew(ID id, Role role, String expiredAccessToken, HttpServletRequ

checkTokenIsBlacklist(expiredAccessToken);

String refreshCookieValue = CookieUtil.getCookieValue(request, refreshCookieName);
if (!redisRefreshTokenUtil.getRefreshToken(id.getValue().toString()).equals(refreshCookieValue)) {
throw new IllegalArgumentException("유효한 접근이 아닙니다. Refresh토큰을 확인해주세요");

try {
JwtUtil.isRefreshTokenValid(id.toString());
} catch (ExpiredJwtException e) {
throw new JwtException("refresh 토큰이 만료되었습니다. 다시 로그인 해주세요.");
}
return tokenHandler.createToken(id.getValue().toString(), createClaimsRoleMap(role), response);

checkRefreshTokenIsExistedBothRedisAndCookie(id,request);

return createNewTokenWithInvalidatingTheOldToken(id, role, expiredAccessToken, response);
}

private void checkTokenIsBlacklist(String accessToken) {
Expand All @@ -45,6 +52,23 @@ private void checkTokenIsBlacklist(String accessToken) {
}
}

private void checkRefreshTokenIsExistedBothRedisAndCookie(ID id, HttpServletRequest request) {
String refreshCookieValue = CookieUtil.getCookieValue(request, refreshCookieName);

if (!redisRefreshTokenUtil.getRefreshToken(id.getValue().toString())
.equals(refreshCookieValue)) {
throw new IllegalArgumentException("유효한 접근이 아닙니다. Refresh토큰을 확인해주세요");
}
}

private String createNewTokenWithInvalidatingTheOldToken(ID id, Role role,
String expiredAccessToken,
HttpServletResponse response) {
tokenHandler.invalidateToken(id.getValue().toString(), expiredAccessToken, response);
return tokenHandler.createToken(id.getValue().toString(), createClaimsRoleMap(role), response);
}


private Map<String, Object> createClaimsRoleMap(Role role) {
return JwtUtil.addClaims(
SecurityPolicyStaticValue.CLAIMS_ROLE_KEY_NAME, role);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ public static String extractToken(HttpServletRequest request) {

public static String extractUserId(HttpServletRequest request){
String token = extractToken(request);
return JwtUtil.extractSubject(token);
return JwtUtil.extractAccessTokenSubject(token);
}

public static String extractRole(HttpServletRequest request) {
String token = ExtractAuthorizationTokenUtil.extractToken(request);
Claims claims = JwtUtil.extractClaims(token);
Claims claims = JwtUtil.extractAccessTokenClaims(token);
return (String) claims.get(SecurityPolicyStaticValue.CLAIMS_ROLE_KEY_NAME);
}

Expand Down
98 changes: 54 additions & 44 deletions src/main/java/com/bit/lot/flower/auth/common/util/JwtUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,114 +6,124 @@
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.UnsupportedJwtException;
import java.security.NoSuchAlgorithmException;
import io.jsonwebtoken.security.Keys;
import java.time.Instant;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class JwtUtil {


private JwtUtil(){

}
private static SecretKey accessSecret;
private static SecretKey refreshSecret;
public static String accessKey;
public static String refreshKey;

private JwtUtil() {

}

public static String generateAccessTokenWithClaims(String subject,
Map<String, Object> claimsList) {
Date now = new Date();

initAccessKey();
return Jwts.builder()
.setSubject(subject)
.setIssuedAt(now)
.setExpiration(Date.from(Instant.now().plusSeconds(
Long.parseLong(SecurityPolicyStaticValue.ACCESS_EXPIRATION_TIME))))
.signWith(accessSecret)
.signWith(Keys.hmacShaKeyFor(accessKey.getBytes()), SignatureAlgorithm.HS256)
.addClaims(claimsList)
.compact();
}

public static Map<String, Object> addClaims(String id, Object value) {
Map<String, Object> claims = new HashMap<>();
claims.put(id, value);
return claims;
}

public static String
generateAccessToken(String subject) {
Date now = new Date();

initAccessKey();
return Jwts.builder()
.setSubject(subject)
.setIssuedAt(now)
.setExpiration(Date.from(Instant.now().plusSeconds(
Long.parseLong(SecurityPolicyStaticValue.ACCESS_EXPIRATION_TIME))))
.signWith(accessSecret)
.signWith(Keys.hmacShaKeyFor(accessKey.getBytes()), SignatureAlgorithm.HS256)
.compact();
}

public static String generateRefreshToken(String subject) {
Date now = new Date();
initRefreshKey();

return Jwts.builder()
.setSubject(subject)
.setIssuedAt(now)
.setExpiration(Date.from(Instant.now().plusSeconds(
Long.parseLong(SecurityPolicyStaticValue.REFRESH_EXPIRATION_TIME))))
.signWith(refreshSecret)
.signWith(Keys.hmacShaKeyFor(refreshKey.getBytes()), SignatureAlgorithm.HS256)
.compact();
}

public static String extractSubject(String token) {
return extractClaims(token).getSubject();
public static Map<String, Object> addClaims(String id, Object value) {
Map<String, Object> claims = new HashMap<>();
claims.put(id, value);
return claims;
}

public static String extractAccessTokenSubject(String token) {
return extractAccessTokenClaims(token).getSubject();
}

public static Claims extractClaims(String token) {
return Jwts.parserBuilder().setSigningKey(accessSecret).build().parseClaimsJws(token)
public static Claims extractAccessTokenClaims(String accessToken) {
return Jwts.parserBuilder()
.setSigningKey(accessKey).
build()
.parseClaimsJws(accessToken)
.getBody();
}

public static boolean isTokenValid(String token) {
public static Claims extractRefreshToken(String refreshToken) {
return Jwts.parserBuilder()
.setSigningKey(refreshKey).
build()
.parseClaimsJws(refreshToken)
.getBody();
}

public static boolean isAccessTokenValid(String accessToken) {
try {
extractClaims(token);
extractAccessTokenClaims(accessToken);
return true;
} catch (ExpiredJwtException e) {
throw new ExpiredJwtException(e.getHeader(),e.getClaims(),e.getMessage()) {
throw new ExpiredJwtException(e.getHeader(), e.getClaims(), e.getMessage()) {
};
} catch (MalformedJwtException | UnsupportedJwtException | IllegalArgumentException e) {
throw new IllegalArgumentException("올바르지 않은 접근입니다.");
}
}

private static void initRefreshKey() {
try {
refreshSecret = KeyGenerator.getInstance("HmacSHA256").generateKey();

} catch (NoSuchAlgorithmException e) {
throw new IllegalArgumentException("plz init the secret key");
}
public static boolean isRefreshTokenValid(String refreshToken) {
try {
extractRefreshToken(refreshToken);
return true;
} catch (ExpiredJwtException e) {
throw new ExpiredJwtException(e.getHeader(), e.getClaims(), "refresh-token이 만료되었습니다.") {
};
} catch (MalformedJwtException | UnsupportedJwtException | IllegalArgumentException e) {
throw new IllegalArgumentException("올바르지 않은 접근입니다.");
}
}

@Value("${encrypt.key.access}")
private void setAccessKey(String accessKey) {
JwtUtil.accessKey = accessKey;
}

@Value("${encrypt.key.refresh}")
private void setRefreshKey(String refreshKey) {
JwtUtil.refreshKey = refreshKey;
}

private static void initAccessKey() {
try {
accessSecret = KeyGenerator.getInstance("HmacSHA256").generateKey();

} catch (NoSuchAlgorithmException e) {
throw new IllegalArgumentException("plz init the secret key");
}
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ AuthenticationManager systemAuthenticationManager() {
public UsernamePasswordAuthenticationFilter systemAdminAuthenticationFilter() {
SystemAdminAuthenticationFilter systemAdminAuthenticationFilter = new SystemAdminAuthenticationFilter(authenticationSuccessHandler,
systemAuthenticationManager());
systemAdminAuthenticationFilter.setFilterProcessesUrl("/**/admin/login");
systemAdminAuthenticationFilter.setFilterProcessesUrl("/**/system/admin/login");
return systemAdminAuthenticationFilter;
}

Expand Down
4 changes: 4 additions & 0 deletions src/main/resources/application-local.yml
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,7 @@ user:
secret: user-secret-user-secret-user-secret-user-secret-user-secret


encrypt:
key:
access: access-keyaccess-keyaccess-keyaccess-keyaccess-keyaccess-keyaccess-key
refresh: refresh-keyrefresh-keyrefresh-keyrefresh-keyrefresh-keyrefresh-keyfresh-key
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ void init() {
void expirationTest_WhenJwtTokenIsExpired_ThrowExpiredJwtException() {

when(redisBlackListTokenUtil.isTokenBlacklisted(anyString())).thenReturn(false);
when(JwtUtil.isTokenValid(anyString())).thenThrow(ExpiredJwtException.class);
when(JwtUtil.isAccessTokenValid(anyString())).thenThrow(ExpiredJwtException.class);

assertThrows(ExpiredJwtException.class,
() -> jwtAuthenticationFilter.doFilterInternal(request, response, filterChain));
Expand Down
Loading