Skip to content

Commit

Permalink
Merge pull request #229 from KUSITMS-27-chilling/feat/deniedMultipleL…
Browse files Browse the repository at this point in the history
…ogin

feat:denied multiple login
  • Loading branch information
ywj9811 committed Jun 29, 2023
2 parents 14985da + 5d29f84 commit d3fd42e
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ protected SecurityException(String message, String errorCode, HttpStatus httpSta
super(message, errorCode, httpStatus);
}

public static class RemovedAccessTokenException extends SecurityException {
public RemovedAccessTokenException(String message, String errorCode, HttpStatus httpStatus) {
super(message, errorCode, httpStatus);
}
}

public static class InvalidJwtFormatException extends SecurityException {
public InvalidJwtFormatException(String message, String errorCode, HttpStatus httpStatus) {
super(message, errorCode, httpStatus);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ public enum JwtExcpetionMessage {
JWT_NOT_SUPPORTED("[ERROR] 지원되지 않는 토큰 입니다"),
WRONG_TOKEN("[ERROR] 잘못된 토큰 입니다"),
NON_AUTHORITY("[ERROR] 권한이 없습니다"),
UNKHOWN_EXCEPTION("[ERROR] 알 수 없는 예외 발생!!");
UNKHOWN_EXCEPTION("[ERROR] 알 수 없는 예외 발생!!"),
REMOVED_ACCESS_TOKEN("[ERROR] 다른 컴퓨터에서 로그인 되었습니다.");
private final String message;
}

Expand All @@ -24,7 +25,8 @@ public enum JwtExcpetionCode {
JWT_NOT_SUPPORTED("jwt003"),
WRONG_TOKEN("jwt004"),
UNKHOWN_EXCEPTION("jwt005"),
NON_AUTHORITY("jwt006");
NON_AUTHORITY("jwt006"),
REMOVE_ACCESS_TOKEN("jwt007");
private final String code;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import chilling.encore.domain.User;
import chilling.encore.global.config.redis.RedisRepository;
import chilling.encore.global.config.security.exception.SecurityException.RemovedAccessTokenException;
import chilling.encore.repository.springDataJpa.UserRepository;
import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
Expand All @@ -10,6 +11,7 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
Expand All @@ -24,13 +26,19 @@
import java.util.NoSuchElementException;
import java.util.stream.Collectors;

import static chilling.encore.global.config.security.jwt.JwtConstants.JwtExcpetionCode.REMOVE_ACCESS_TOKEN;
import static chilling.encore.global.config.security.jwt.JwtConstants.JwtExcpetionMessage.REMOVED_ACCESS_TOKEN;

@Slf4j
@Component
@RequiredArgsConstructor
public class JwtTokenProvider implements InitializingBean {
private final RedisRepository redisRepository;
private static final String AUTHORITIES_KEY = "auth";
private static final String USER_IDX = "userIdx";
private static final String ACCESS = "Access";
private static final String REFRESH = "Refresh";
private static final String GRANT = "Bearer";

@Value("${jwt.secret}")
private String secret;
Expand Down Expand Up @@ -66,7 +74,7 @@ public TokenInfoResponse createToken(Authentication authentication) {

updateRefreshToken(userIdx, refreshToken);

return TokenInfoResponse.from("Bearer", accessToken, refreshToken, accessTokenValidityTime);
return TokenInfoResponse.from(GRANT, accessToken, refreshToken, accessTokenValidityTime);
}

public TokenInfoResponse oauth2CreateToken(Authentication authentication) {
Expand All @@ -84,14 +92,14 @@ public TokenInfoResponse oauth2CreateToken(Authentication authentication) {

updateRefreshToken(userIdx, refreshToken);

return TokenInfoResponse.from("Bearer", accessToken, refreshToken, accessTokenValidityTime);
return TokenInfoResponse.from(GRANT, accessToken, refreshToken, accessTokenValidityTime);
}

private String createRefreshToken(String authorities, Date current, Long userIdx) {
Date refreshTokenValidity = new Date(current.getTime() + this.refreshTokenValidityTime);

String refreshToken = Jwts.builder()
.setSubject("Refresh")
.setSubject(REFRESH)
.claim(AUTHORITIES_KEY, authorities)
.claim(USER_IDX, userIdx)
.setIssuedAt(current)
Expand All @@ -105,14 +113,16 @@ private String createAccessToken(String authorities, Date current, Long userIdx)
Date accessTokenValidity = new Date(current.getTime() + this.accessTokenValidityTime);

String accessToken = Jwts.builder()
.setSubject("Access")
.setSubject(ACCESS)
.claim(AUTHORITIES_KEY, authorities)
.claim(USER_IDX, userIdx)
.setIssuedAt(current)
.signWith(key, SignatureAlgorithm.HS512)
.setExpiration(accessTokenValidity)
.compact();

log.info("accessToken 저장");
redisRepository.setValues(ACCESS + userIdx.toString(), accessToken, Duration.ofSeconds(accessTokenValidityTime));
return accessToken;
}

Expand Down Expand Up @@ -157,25 +167,23 @@ private Claims parseClaims(String token) {

public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
checkMultiLogin(token);
return true;
} catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {
log.info("잘못된 JWT 서명입니다.");
throw e;
} catch (ExpiredJwtException e) {
log.info("만료된 JWT 토큰입니다.");
throw e;
} catch (UnsupportedJwtException e) {
log.info("지원되지 않는 JWT 토큰입니다.");
throw e;
} catch (IllegalArgumentException e) {
log.info("JWT 토큰이 잘못되었습니다.");
throw e;
}
}



public boolean validateRefreshToken(String token) {
if (!getSubject(token).equals("Refresh"))
if (!getSubject(token).equals(REFRESH))
throw new IllegalArgumentException("Refresh Token이 아닙니다.");
try {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
Expand Down Expand Up @@ -208,6 +216,14 @@ public boolean checkBlackList(String token) {
return true;
}

private void checkMultiLogin(String token) {
Jws<Claims> claims = Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
String userIdx = claims.getBody().get(USER_IDX).toString();
log.info("checkMultiple Login");
if (redisRepository.getValues(ACCESS + userIdx).isEmpty())
throw new RemovedAccessTokenException(REMOVED_ACCESS_TOKEN.getMessage(), REMOVE_ACCESS_TOKEN.getCode(), HttpStatus.FORBIDDEN);
}

public String getSubject(String token) {
Claims claims = parseClaims(token);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

@Component
@RequiredArgsConstructor
Expand All @@ -29,23 +30,26 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
filterChain.doFilter(request, response);
} catch (ExpiredJwtException e) {
String errorResponseJson = getErrorResponseJson(request, response, e);
response.getOutputStream().write(errorResponseJson.getBytes("UTF-8"));
response.getOutputStream().write(errorResponseJson.getBytes(StandardCharsets.UTF_8));

} catch (InvalidJwtFormatException e) {
String errorResponseJson = getErrorResponseJson(request, response, e);
response.getOutputStream().write(errorResponseJson.getBytes("UTF-8"));
response.getOutputStream().write(errorResponseJson.getBytes(StandardCharsets.UTF_8));

} catch (NonSupportedJwtException e) {
String errorResponseJson = getErrorResponseJson(request, response, e);
response.getOutputStream().write(errorResponseJson.getBytes("UTF-8"));
response.getOutputStream().write(errorResponseJson.getBytes(StandardCharsets.UTF_8));

} catch (WrongTokenException e) {
String errorResponseJson = getErrorResponseJson(request, response, e);
response.getOutputStream().write(errorResponseJson.getBytes("UTF-8"));
response.getOutputStream().write(errorResponseJson.getBytes(StandardCharsets.UTF_8));

} catch (UnKnownException e) {
String errorResponseJson = getErrorResponseJson(request, response, e);
response.getOutputStream().write(errorResponseJson.getBytes("UTF-8"));
response.getOutputStream().write(errorResponseJson.getBytes(StandardCharsets.UTF_8));
} catch (RemovedAccessTokenException e) {
String errorResponseJson = getErrorResponseJson(request, response, e);
response.getOutputStream().write(errorResponseJson.getBytes(StandardCharsets.UTF_8));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
SecurityContextHolder.getContext().setAuthentication(authentication);
log.debug("Security Context에 '{}' 인증 정보를 저장했습니다, uri: {}", authentication.getName(), requestURI);
}
} catch (SecurityException.RemovedAccessTokenException e) {
log.error("exception : {}", e.getMessage());
throw e;
} catch (SecurityException | MalformedJwtException e) {
log.error("exception : {}", e.getMessage());
throw new SecurityException.InvalidJwtFormatException(JwtExcpetionMessage.INVALID_FORMAT.getMessage(), JwtExcpetionCode.INVALID_FORMAT.getCode(), HttpStatus.FORBIDDEN);
Expand Down

0 comments on commit d3fd42e

Please sign in to comment.