Spring 로그인 / Session : JWT 차이점과 장단점
프로젝트를 진행 하던 도중 기존에 Session 방식의 로그인으로 구현해두고 나중에 JWT로 변경하고자 생각중이였으나 큰 오산이였다. JWT는 헤더에 토큰을 담아서 보낼 필요가 있었고, 현재의 프로젝트에서 모든 헤더값에 토큰을 담아 보내는 것은 작업량이 너무 방대하여 포기하기로 하였다. 하지만 JWT와 Session의 차이점과 장단점은 정리해두기로 하였다
[ 참고 블로그 ]
https://hudi.blog/session-based-auth-vs-token-based-auth/
세션 기반 인증과 토큰 기반 인증 (feat. 인증과 인가)
인증과 인가 세션기반 인가와 토큰기반 인가에 대해 알아보기 이전에 먼저, 인증과 인가가 무엇인지 부터 알아야할 필요가 있다. 인증과 인가를 같거나 비슷한 개념이라고 생각하는 사람들이
hudi.blog
https://velog.io/@chuu1019/%EC%95%8C%EA%B3%A0-%EC%93%B0%EC%9E%90-JWTJson-Web-Token
😎 알고 쓰자, JWT(Json Web Token).
jwt 토큰이 어떻게 구성되어있는지, 왜 jwt 토큰을 사용해야하는지 아시나요?
velog.io
https://dev-coco.tistory.com/174
Spring Security의 구조(Architecture) 및 처리 과정 알아보기
시작하기 앞서 스프링 시큐리티에서 어플리케이션 보안을 구성하는 두 가지 영역에 대해 간단히 알아보자. 인증(Authentication)과 인가(Authorization) 대부분의 시스템에서는 회원을 관리하고 있고,
dev-coco.tistory.com
https://cordcat.tistory.com/108
세션과 토큰 인증 방식 중 각각의 장단점을 말씀해 주세요
Session 인증 방식의 장단점 장점 : - 서버 측에서 유저의 인증 정보를 관리하기 때문에, 보안성이 높습니다. - 유저의 인증 정보를 서버 측에서 유지하기 때문에, 클라이언트 측에서 인증 정보를
cordcat.tistory.com
https://colabear754.tistory.com/179
[JWT] Access Token의 한계와 Refresh Token의 필요성
[수정사항] 2023-08-20 : 자바 코드의 TokenProvider 클래스에서 리프레시 토큰이 일치하는지 검사하는 메소드가 누락된 부분 수정 목차 들어가기 전에 이전에 스프링 시큐리티와 JWT를 이용한 사용자
colabear754.tistory.com
https://inpa.tistory.com/entry/WEB-%F0%9F%93%9A-Stateful-Stateless-%EC%A0%95%EB%A6%AC
🌐 아주 쉽게 이해하는 Stateful / Stateless 차이
Stateful 과 Stateless 차이점 웹 공부를 하다보면 클라이언트(Client)와 서버(Server)간의 통신을 상태유지(Stateful) 하느냐, 상태유지하지않음(Stateless) 으로 하느냐 라는 말귀를 한번쯤은 들어본 적이 있
inpa.tistory.com
1. 인증 인가란?
로그인을 말하려면 인증, 인가가 무엇인지를 알아야한다.
(1) 인증이란 ?
간단하게 말하자면 로그인이다. 해당 사용자가 정말로 사용자가 맞는지 확인하는것 그것을 인증이라한다.
예 ) 유저 id : userTest1 라고 가정할 때 로그인 시 id가 users 인지, password가 일치하는지 확인하는 것을 인증이라
(2) 인가란 ?
인가란, 인증된 사용자에 대한 자원에 대한 접근 확인 절차를 의미한다
위 단계인 인증에서 userTest1 라는 유저가 인증에 성공하였다.
userTest1인 유저는 일반유저이기에 게시글을 작성, 수정, 삭제 같은 기능은 사용할 수 있다.
하지만 userTest2가 작성한 게시글은 볼 수만 있지 수정, 삭제는 불가능하다
또한 admin 계정에서는 관리자 페이지에 접속이 가능하다.
하지만 userTest1,2는 일반 유저이기에 관리자 페이지에 접속이 불가능하다
길어졌지만 간단히 이야기하자면 인가란, 해당 유저가 사용가능 여부를 판단해주는 것으로 생각하면 편하다.
2. HTTP의 비상태성(Stateless)
HTTP는 비상태성 이라는 특성을 가지게된다. 서버는 클라이언트의 상태를 따로 저장하지 않기에, 이전 요청과 다음 요청의 맥락이 이어지지 않는다. 즉, HTTP 단독으로 요청하게 되면, 클라이언트가 이전에 어떠한 인증을 거쳤는지 확인할 방법이 없어진다.
그렇다면 어떠한 api를 요청할 때마다 로그인 정보를 받아서 처리할 수는 없다.
하지만 현재의 사이트에서는 어떠한 곳에 로그인 후에는 데이터가 저장되어 있는 것을 추측해볼 수 있다.
그것이 바로 세션, 토큰(JWT) 방식의 로그인 방식을 채택하고 있기 때문이다.
위에서 말한 인증, 인가 중 한번의 인증을 하고 난 뒤, 계속해서 인증을 거치지 않고 인가를 받을 수 있게끔 한다.
[ Stateful 과 Stateless의 차이 ]
https://inpa.tistory.com/entry/WEB-%F0%9F%93%9A-Stateful-Stateless-%EC%A0%95%EB%A6%AC
🌐 아주 쉽게 이해하는 Stateful / Stateless 차이
Stateful 과 Stateless 차이점 웹 공부를 하다보면 클라이언트(Client)와 서버(Server)간의 통신을 상태유지(Stateful) 하느냐, 상태유지하지않음(Stateless) 으로 하느냐 라는 말귀를 한번쯤은 들어본 적이 있
inpa.tistory.com
Stateful 과 Stateless의 차이는 위의 블로그에 굉장히 자세히 설명되어있다
[ 요약 ]
Stateful(상태유지) : 상태 유지라 서버가 클라이언트의 상태를 보존하는것을 의미한다.
Stateless(무상태) : 무상태는 반대로 클라이언트와 서버 관계에서 서버가 클라이언트의 상태를 보존하지 않음을 의미한다.
3. Session(세션) 기반 인증
세션 기반 인증을 먼저 살펴보자
세션 기반 인증의 처리 과정이다.
username(id와 동일)과 password로 DB에서 유저 데이터를 조회한다.
성공하면 cookie에 session-id(user 데이터)를 저장한다.
그리고 어떠한 요청을 할 때 마다 로그인으로 인증을 거치는 것이 아닌
cookie에 저장 된 session-id를 가져와서 인가를 진행하는 것을 세션 기반 인증이라한다.
조금 어렵다고 생각된다면, 간단하게 설명해본다
1. 로그인
2. 유저 데이터가 일치하여 인증에 성공
3. cookie에 session-id를 저장
4. 로그인 성공
5. 클라이언트에서 어떠한 요청 전송
6. 또 다시 인증(로그인) 과정을 거치는 것이 아닌 cookie의 session-id를 확인하여 인증없이 바로 인가 확인
[ 구현 코드 ]
(1) securityConfig
http.csrf().disable()
.authorizeRequests()
.antMatchers("/tour/*").authenticated()
... -----> 권한 체크할 목록
.and()
.formLogin()
.usernameParameter("userId")
.passwordParameter("userPw")
.loginPage("/user/login")
.loginProcessingUrl("/login")
.failureHandler(new CustomAuthFailureHandler())
.defaultSuccessUrl("/")
... ------> 로그인에 대한 정보
.and()
.logout()
.logoutUrl("/user/logout");
... ------> 로그아웃 (세션을 날리는 처리)
(2) UserDetailsService
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<Qaru.Prj.domain.entity.User> userEntity = userRepository.findByUserId(username);
if(!userEntity.isPresent()){
throw new UsernameNotFoundException(username);
}
return new PrincipalDetails(userEntity.get());
}
간단하게 설명을 해보자
1. 로그인 시 userId, userPw (security 파라미터로 지정) 를 받아옴
2. loadUserByUsername에 파라미터 username(userId)로 해당 아이디를 찾음
3. UsernamePasswordAuthenticationFilter가 진행
4. UsernamePasswordAuthenticationToken 생성(password 인증 진행)
5. 세션을 저장
위의 과정에서 인증에서 성공시 아래처럼 쿠리에서 JESSIONID 값이 저장 된 것을 확인 할 수 있다
4. 토큰 기반 인증
다음으로 토큰 기반 인증을 알아보자
토큰 기반 인증 과정이다
토큰 기반 인증의 경우
먼저 로그인을 진행한다. 그러면 세션기반과 동일하게 user 정보를 체크한다
그 이후의 과정에서 세션의 경우 쿠키에 session-id를 저장시키는 방식을 사용하지만
토큰의 경의 headers에 Authorization : Bearer ${JWT_TOKEN} 의 형식으로 담아서 전송한다
그 이후 클라이언트에서 요청 시 헤당 헤더 토큰에서 데이터를 조회하여 인가를 진행한다
5. JWT란 무엇인가 ?
그렇다면 JWT란 무엇인지를 알아보자
[ JWT 이란? ]
Json Web Token이라는 의미로 Json 객체에 필요한 정보들을 담은 후 비밀키로 서명한 토큰으로, 인터넷 표준 인증 방식을 뜻한다.
[ JWT의 구조 ]
JWT의 구조는 Header, Payload, Signature 3개로 구성되어 있다
[ Header ]
alg : Signature에서 사용하는 알고리즘
typ : 토큰 타입
Signature에서 사용하는 알고리즘은 대표적으로 RS256(공개키/개인키)와 HS256(비밀키(대칭키))가 있다.
이 부분은 auth0 공식 문서에서 자세히 설명해주고 있다.
[ Payload ]
사용자 정보의 한 조각인 클레임(claim)이 들어있다.
sub : 토큰 제목(subject)
aud : 토큰 대상자(audience)
iat : 토큰이 발급된 시각 (issued at)
exp : 토큰의 만료 시각 (expired)
[ Signature ]
Signature는 헤더와 페이로드의 문자열을 합친 후에, 헤더에서 선언한 알고리즘과 key를 이용해 암호한 값이다.
Header와 Payload는 단순히 Base64url로 인코딩되어 있어 누구나 쉽게 복호화할 수 있지만, Signature는 key가 없으면 복호화할 수 없다. 이를 통해 보안상 안전하다는 특성을 가질 수 있게 되었다.
앞서 언급한 것처럼 header에서 선언한 알고리즘에 따라 key는 개인키가 될 수도 있고 비밀키가 될 수도 있다. 개인키로 서명했다면 공개키로 유효성 검사를 할 수 있고, 비밀키로 서명했다면 비밀키를 가지고 있는 사람만이 암호화 복호화, 유효성 검사를 할 수 있다.
전부 중요하지만 데이터가 들어가는 곳은 Payload이다
임시로 만든 JWT 토큰 정보를 확인해보자
[ 생성한 JWT ]
eyJhbGciOiJIUzUxMiJ9.eyJzdWaIiOiIzMjA2IiwiYXV0aCI6IlJPTEVfVVNFUiIsImV4cCI6MTcwMjk3OTUwM30.S-3u0nheLR6jwqRoJncrgRFSboXoPjQxBfDjpVapdnFwRp9-1aHeSqLdi7uT3FpGlleUSuf8IGJdRXhqWUGgMA
보다시피 무슨 데이터가 들어있는지 절때 확인할 수 없다
이 토큰을 테스트 해볼 수 있는 사이트
JWT.IO
JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.
jwt.io
에서 토큰을 입력해보자
결과값을 주목해보자
header : alg(알고리즘) - HS512를 사용하였다.
payload : sub - userid / auth - 권한(ROLE_USER) / exp - 만료시간
signature : 일련의 문자열로, 시그니처를 통해 토큰이 변조되었는지 여부를 확인할 수 있다.
중요하게 지켜 볼 것은 sub에 들어있는 데이터이다. 이곳에 해킹을 대비하여 최소한의 정보만을 저장해두고 이 데이터를 이용하여 유저 데이터를 조회한다.
6. JWT 생성 과정과 로그인 과정 실제 로직
기본적으로 security에 인가를 거치게 해둔 api는 해당 권한이 없는 유저가 접근 시 403 에러를 반환하게 된다.
세션의 경우 쿠키의 session 데이터를 기반으로 인가를 처리하지만 토큰 방식의 경우에는 어떤식으로 처리해야할까?
(1) securityConfig
http
.csrf().disable()
.httpBasic().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/tour/*").hasRole("USER")
.anyRequest().permitAll()
.and()
.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class);
주목할 점은 addFilterBefore이다
addFilterBefore에 적용된 필터를 무언가 실행되기 이전에 실행하게된다
(2) JwtAuthenticationFilter
private final JwtTokenProvider jwtTokenProvider;
// Request Header에서 토큰 정보 추출
private String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer")) {
return bearerToken.substring(7);
}
return "noToken";
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
String token = resolveToken((HttpServletRequest) request);
if (!token.equals("noToken") && jwtTokenProvider.isExpired(token)) {
Authentication authentication = jwtTokenProvider.getAuthentication(token, (HttpServletRequest) request);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
해당 필터에서 doFilter, 즉 어떠한 요청이 왔을 때 해당 필터를 먼저 진행한다.
해당 필터에서 header에 담긴 토큰을 확인하고 해당 토큰 데이터가 인증 인가를 통과한다면
해당 데이터를 저장하는 방식이다.
(3) JwtTokenProvider
public String generate(Qaru.Prj.domain.entity.User user, Date expiredAt) {
String authorities = user.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(","));
return Jwts.builder()
.setSubject(String.valueOf(user.getId()))
.claim("auth", authorities)
.setExpiration(expiredAt)
.signWith(key, SignatureAlgorithm.HS512)
.compact();
}
JWT 토큰의 생성을 보자
setSubject에 userId를 담아두었고, claim에 해당 유저의 권한 정보를 담아두었다,
알고리즘 정보와 인증 유효시간에 대한 데이터도 같이 담아둔 것을 확인해볼 수 있다.
(4) JWT 파싱 테스트
public Claims parseClaims(String accessToken) {
try {
return Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(accessToken)
.getBody();
} catch (ExpiredJwtException e) {
return e.getClaims();
}
}
파싱하는 메소드이다
accessToken을 넣으면 해당 토큰을 파싱하여 subject에 담긴 userId를 뱉어낸다
String accessToken = "eyJhbGciOiJIUzUxMiJ9." +
"eyJzdWIiOiIzMjA2IiwiYXV0aCI6IlJPTEVfVVNFUiIsImV4cCI6MTcwMjk3OTUwM30." +
"S-3u0nheLR6jwqRoJncrgRFSboXoPjQxBfDjpVapdnFwRp9-1aHeSqLdi7uT3FpGlleUSuf8IGJdRXhqWUGgMA";
Claims claims = jwtTokenProvider.parseClaims(accessToken);
System.out.println("=========== > " + claims.getSubject());
=========== 결과값
=========== > 3206
(5) getAuthentication
public Authentication getAuthentication(String accessToken, HttpServletRequest request) {
// Jwt 토큰 복호화
Claims claims = parseClaims(accessToken);
// 클레임에서 권한 정보 가져오기
Collection<? extends GrantedAuthority> authorities = Arrays.stream(claims.get("auth").toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(userRepository.findById(Long.valueOf(claims.getSubject())).get(), "", authorities);
token.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
return token;
}
2번의 dofilter를 확인해보면 결과적으로 getAuthentication으로 Authentication 객체를 뱉어내고
해당 데이터를 SecurityContextHolder에 저장한다
4번의 테스트 코드의 parseClaims(토큰 파싱) 으로 해당 토큰에서 auth(권한) 데이터와 유저 id를 통해 유저데이터를 가져와서 UsernamePasswordAuthenticationToken을 생성한다(해당 토큰으로 인증을 진행함) 이후 인가를 진행한다
7. 세션 기반 인증의 장/단점
[ 장점 ]
(1) 서버에서 유저의 인증정보를 관리하기에, 보안성이 높다
(2) 서버에서 인증정보가 관리되기에 클라이언트에서 인증정보를 저장하는 토큰 방식에 비해 탈취의 가능성이 낮다
(3) 새로고침이나 브라우저 종료 등으로 인한 세션 만료 기간 설정이 가능합니다
[ 단점 ]
(1) 클라이언트가 요청할 때 마다 서버에서 세션정보를 확인해야 하기 때문에, 서버의 부하가 증가할 수 있다
(2) 서버의 메모리나 데이터베이스에 세션 정보를 저장하기 때문에, 확장성이 떨어 질 수 있다
8. 토큰 기반 인증의 장/단점
[ 장점 ]
(1) 클라이언트에서 인증 정보를 저장 관리하기에, 세션 방식에 비해 서버 부하가 적고, 서버와의 통신이 감소된다
(2) JWT를 사용하면, 토큰 내에 필요한 정보를 담을 수 있다
(3) 확장성이 좋다
[ 단점 ]
(1) 클라이언트에 모든 인증 정보가 저장되기에 보안에 취약하다
(2) 토큰을 인증하기위해 매번 DB 조회가 필요하다
9. 토큰 vs 세션 무엇을 더 많이 사용할까?
사실 정답은 없다
나의 개인적인 생각으로써는 토큰 방식은 보안도 위험하고, 로그인 때마다 DB를 조회해야하기에 번거롭다고 생각한다.
하지만 실제적으로 최근 많은 회사들이 토큰 방식을 사용하고 있는데
그 이유는 바로 장점 3번에 해당하는 확장성 때문이다
어떠한 경우에도 헤더의 토큰만 유효하다면 사용할 수 있기에 확장에 용의하다
예 ) 일반 로그인 방식을 채택 중, 카카오, 구글 로그인 등을 채택 할 경우 토큰인증방식을 접목하는 것만으로 확장이 가능해짐
그러면 이렇게 생각할 수도 있다
토큰은 인증시간이 있고 탈취에 보안이 취약해지는 위험성이 있는데, 왜 토큰 방식을 채택하는지 이유를 알 수 없다
그럴때 사용하기 위해서 만든 것이 바로 refresh 토큰이다
10. access / refresh 토큰이란?
jwt 토큰 인증 방식을 사용할 때 많은 서비스들에서는 하나의 토큰만을 발행하는 것이 아닌 2가지의 토큰을 발행한다
(1) Access Token
Access Token은 일반적으로 사용되는 토큰이다
헤더에 담기는 토큰으로써 탈취가 될 수 있다는 위험성이 있다
그렇기에 우리는 해당 토큰의 유효시간을 굉장히 짧게 설정한다
10분으로 유효시간을 설정한다면, 해킹을 당했어도 10분의 유효시간만 해킹범이 사용할 수 있게 되기에
보안을 유지할 수 있다
그러면 10분마다 매번 토큰을 발행해야 하는가?
또한 10분마다 매번 인증을 다시받기위해 로그인을 해야만 하는가?
이러한 문제를 해결하기 위해 나온 방법이 바로 Refresh Token을 같이 생성하는 방법이다
(2) Refresh Token
Refresh Token은 Access Token을 생성할 때 같이 생성하는 토큰이다
차이점이라면 Access Token은 탈취를 우려하여 유효시간을 짧게 생성하고,
Refresh Token은 유효시간을 길게 생성한다
이후 Access Token의 유효시간이 만료되면 Refresh Token을 체크하여 해당 토큰이 유효할 때
Access Token을 재발급하여 교체해주는 방식이다
순서는 이렇다
1. AccessToken, RefreshToken을 같이 클라이언트에 전송
2. 평소에는 AccessToken으로 인증인가 진행
3. AccessToken의 유효시간 만료 시 클라이언트에 RefreshToken 요청
4. 해당 Refresh Token을 가져와서 유효시간 체크
5. 유효시간 만료가 되지 않았을 시, DB에서 Refresh Token이 존재하는 지 조회
6. 존재할 시 해당 AccessToken을 다시 생성(유효시간이 리셋됨)
의 방식으로 토큰 교체가 진행된다.
간단하게 테스트 코드로 흐름 정도만 구현해보기로 하였다
[ 구현 코드 ]
(1) parseClaims
public Claims parseClaims(String accessToken) {
try {
return Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(accessToken)
.getBody();
} catch (ExpiredJwtException e) {
System.out.println("========= > : 유효시간 초과");
return e.getClaims();
}
}
(2) 테스트 코드
@Test
@Transactional
void jwt토큰파싱확인() throws NoSuchAlgorithmException {
// 유저 데이터 저장
User user = userRepository.save(createClass.createUser());
// 시간 설정 -- access : 1분 / refresh : 10분
final long ACCESS_TOKEN_EXPIRE_TIME = 1000 * 60 * 1; // 1분
final long REFRESH_TOKEN_EXPIRE_TIME = 1000 * 60 * 60; // 60분
long now = (new Date()).getTime();
Date accessTokenExpiredAt = new Date(now + ACCESS_TOKEN_EXPIRE_TIME);
Date refreshTokenExpiredAt = new Date(now + REFRESH_TOKEN_EXPIRE_TIME);
// accessToken 생성
String accessToken = jwtTokenProvider.generate(user, accessTokenExpiredAt);
// refresh 토큰 용 random 함수
SecureRandom random = SecureRandom.getInstanceStrong();
String refreshToken = String.valueOf(random.nextDouble());
// refresh 토큰 저장
RefreshToken refreshTokenEntity = RefreshToken.builder()
.refreshToken(String.valueOf(random.nextDouble()))
.user(user)
.expDate(refreshTokenExpiredAt).build();
RefreshToken refresh = refreshTokenRepository.save(refreshTokenEntity);
// accessToken 확인
Claims claims = jwtTokenProvider.parseClaims(accessToken);
// accessToken 유효함
System.out.println("=========== > userId : " + claims.getSubject());
for(int i = 0; i < 65; i++){
try{
Thread.sleep(1000);
}catch (Exception e){
e.printStackTrace();
}
}
System.out.println("========== > : 100초 경과");
// accessToken 유효시간 만료
Claims claims2 = jwtTokenProvider.parseClaims(accessToken);
// =========> 코드에는 없지만 여기서 클라이언트에 refreshToken 요청
String userId = refreshTokenRepository.findByRefreshToken(refreshToken).getRefreshToken();
// 토큰 값이 일치할 때
String newAccessToken = jwtTokenProvider.generate(userRepository.findById(Long.valueOf(userId)).get(), accessTokenExpiredAt);
System.out.println("========== > new token : " + newAccessToken);
}
(3) console
=========== > userId : 3563
========== > : 100초 경과
========= > : 유효시간 초과
========== > new token : eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIzNTYzIiwiYXV0aCI6IlJPTEVfVVNFUiIsImV4cCI6MTcwNDk3ODM5N30.lQpEFpa8lM_tUDBIkaVx-b5vuq4JkYKOyaTATNBNDZUopASKFQH2y0H_cZ9fvTKsvRuHSjaMixfHSP6hWKBqSg
위의 코드를 살펴보자
1. 로그인할 유저데이터를 먼저 생성한다
2. accessToken, refreshToken의 유효시간을 설정한다
(테스트를 위해서 시간은 짧게 진행하였다. accessToken - 1분 / refreshToken - 60분)
3. accessToken, refreshToken 생성
* 주목할 점은 refreshToken이다. refresh 토큰의 경우 굳이 JWT 토큰으로 만들필요는 없다는 조언을 받게되었다
그렇기에 SecureRandom을 사용하여 토큰처럼 활용하기로 하였다
SecureRandom의 경우 일반 Random에 비해서 더욱 중복이 나오기 힘들기에 사용하였다
4. refreshToken DB에 저장한다
* DB에 저장되는 내역은 token, 유효시간, 유저id가 저장된다
5. 유효성 체크 (유효시간 만료되지 않음 -> 유효시간 만료됨)
* 처음에 유효시간이 1분이기에 정상적으로 accessToken을 사용할 수 있다.
그 이후 for문 sleep으로 강제로 65초가 흐르고 난 뒤에는 accessToken의 유효시간이 만료되어 사용할 수 없게 된다
6. 토큰이 만료시 콘솔에서 만료를 확인할 수 있다 그 이유는 parseClaims 메소드에서 확인할 수 있다. 유효 시간이 만료된 경우 ExpiredJwtException이 발생하게 되고 해당 catch문안의 로직이 실행되도록 되어있다.
7. 유효 시간 만료를 확인했을 경우에는 다시 클라이언트에 refreshToken을 요청한다
8. 해당토큰으로 DB에서 토큰을 조회한다.
userId를 가져와 해당 user 데이터로 accessToken을 새로 생성한 accessToken을 클라이언트에 전송하면된다.
처음에 공부할 때는 이해되지 않는 개념이였고, 어떤식으로 진행되는지 이해하지 못했으나
실제로 코드를 만들어보니 이해할 수 있었다.
11. 결론
세션방식, 토큰방식 모두 장단점이 존재하는 인증인가 방식이다
세션방식은 보안이 좋으나 서버에 저장하는 데이터가 많을 수록 부하가 심하다는 단점이 있고
토큰방식은 클라이언트에서 토큰을 저장하기에 보안이 취약하나
RefreshToken을 사용하면 어느정도 해결이 가능하다 또한 확장이 용의하다는 장점이 있다
모든 방식에는 장단점이 존재하니 자신의 상황에 맞게 사용하면 좋을 것 같다.