https://jsilver720.tistory.com/26
스프링으로 깃허브(GitHub) 소셜로그인
저는 졸업프로젝트에서 깃허브를 이용해 소셜로그인을 진행하는데요. 오늘은 그 과정에 대해서 살펴보겠습니다.초기 설정 과정먼저, 브라우저 창에 www.github.com/settings 를 입력해줍니다. G
jsilver720.tistory.com
제 이전 포스트에서는 깃허브 소셜로그인 하는 방법에 대해서 코드와 함께 말씀드렸는데요.
이번에는 찐 마무리를 위해 레디스에 대해서 설명하겠습니다.
로그인을 하고 나면, 보통 JWT 토큰을 발급합니다. access Token, refresh Token 이렇게 보통 두 종류의 토큰을 함께 발급해줍니다.
JWT 는 왜 사용할까요???
JWT 토큰은 유저의 신원이나 권한을 결정하는 정보를 담고 있는 데이터 조각입니다. (JWT 에 대한 자세한 설명은 다루지 않겠습니다.) JWT 토큰을 사용해서 클라이언트와 서버는 안전하게 통신합니다. 왜냐하면 JWT 토큰 인증방식은 비밀키(개인키 or 대칭키)로 암호화를 하기 때문입니다.
그런데 문제는 탈취를 당했을 때 입니다. JWT 토큰을 탈취한 사람은 마치 신뢰할만한 사람인 것처럼 인증을 통과할 수 있고, 서버는 해당 클라이언트가 토큰을 탈취 당한지도, 이 토큰이 탈취 당한 토큰인지도 분별할 수 없습니다. 따라서 유효기간을 두어서 일정한 시간마다 토큰을 재발급받게 하여 혹여나 토큰이 탈취 당하더라도 금방 무효화 시켜버려서 안전한 통신을 유지합니다.
그런데 유효기간을 짧게 두면 사용자가 여러번 로그인을 해야하고, 유효기간을 길게 두면 보안상 탈취 위험에서 벗어날 수 없다는 문제를 가지고 있습니다. 해결법은 유효기간이 다른 access Token 과 refresh Token 을 두는 것입니다!
access Token vs. refresh Token
access token (엑세스 토큰) 은 유효기간이 짧은 토큰입니다. 일반적으로 30분 ~ 1시간으로 설정해두는 것 같습니다.
refresh token (리프레시 토큰) 의 유효기간은 상대적으로 길어서 2주 정도로 설정합니다.
리프레시 토큰은 엑세스 토큰이 만료되면 재발급 받는데에 사용합니다. 그래서 서버는 해당 클라이언트의 리프레시 토큰을 저장해두었다가, 클라이언트가 리프레시 토큰을 보내며 "나 엑세스 재발급해죠!" 라는 요청을 보내면 서버의 데이터베이스에서 리프레시 토큰으로 유효한 유저인지, 어떤 유저인지 판별하여 새롭게 엑세스토큰을 발급해서 보내주게 됩니다. 그러면 클라이언트는 새롭게 발급받은 엑세스토큰을 사용하여 서버와 안전한 통신을 이어나갈 수 있는 것이죠!
레디스(redis) 는 그러면 어디서 사용하죠?!
로그인을 하고 나면! 엑세스 토큰과 리프레시 토큰을 발급할 것이고, 서버 입장에선 로그인한 클라이언트 즉, 유효한 토큰을 발급받아서 사용하고 있는 클라이언트의 리프레시 토큰 모음이 필요합니다! 그래야 해당 리프레시 토큰으로 엑세스 토큰을 재발급해줄 수 있으니까요.
그러면 레디스에 저장되어야하는 시점은 '로그인이 되었을 때 + 2가지 토큰을 발급해줄 때'! 가 되겠습니다.
레디스(redis) 설치하기
https://herojoon-dev.tistory.com/170
Mac에서 Redis(레디스) 설치하기
목표 Mac OS에서 Redis 설치하기 Mac OS에서 Redis 실행 Redis 실행 상태 확인 Redis CLI를 이용해서 Redis 사용해보기 해보기 1. Mac OS에서 Redis 설치 // Homebrew(Mac OS용 패키지 관리자) 설치 여부 확인 brew --versio
herojoon-dev.tistory.com
저는 이 블로그를 참고해서 설치했습니다!
레디스 (redis) 사용하기
@Slf4j
@Service
@RequiredArgsConstructor
public class TokenService {
@Value("${jwt.refresh-token-expiration}")
private long refreshTokenExpiration;
private final RedisTemplate<String, Object> redisTemplate;
public void storeToken(String token, String githubId) {
// redis 에 토큰 저장
redisTemplate.opsForValue().set(githubId, token, refreshTokenExpiration, TimeUnit.MILLISECONDS);
}
public boolean checkTokenExists(String token) {
// 들어온 토큰이 redis 에 있는지 확인
Boolean result = redisTemplate.hasKey(token);
return result != null && result;
}
public void invalidateToken(String githubId) {
// redis 에서 토큰 삭제
redisTemplate.delete(githubId);
}
}
저는 TokenService 라는 레디스에 접근하기 위한 서비스 클래스를 하나 만들었습니다.
총 3개의 메소드를 만들었고, 각각의 역할을 레디스에 저장, 조회, 삭제 이렇게 구성하였습니다.
public LoginResponse githubLogin(String code){
log.info("[LoginService.githubLogin]");
String accessToken = getAccessToken(code);
String githubId = getUserInfo(accessToken);
boolean memberStatus = false;
Optional<Member> member = memberRepository.findByGithubIdAndStatus(githubId, BaseStatus.ACTIVE);
String jwtAccessToken = null;
String jwtRefreshToken = null;
LoginResponse.Profile profile = null;
if(member.isPresent()){
Member member1 = member.get();
memberStatus = true;
jwtAccessToken = jwtProvider.createAccessToken(githubId);
jwtRefreshToken = jwtProvider.createRefreshToken(githubId);
// 레디스 저장
tokenService.storeToken(jwtRefreshToken, githubId);
profile = new LoginResponse.Profile(member1.getNickname(), member1.getAvatar().getAvatarId(), member1.getColor().getColorId());
}
return new LoginResponse(memberStatus,githubId,jwtAccessToken,jwtRefreshToken, profile);
}
이건 소셜로그인을 하고 난 후 로그인에 대한 서비스 코드입니다.
요청으로 들어온 유저가 데이터베이스에 있는지 확인하고 있다면 해당 객체의 값과 엑세스토큰, 리프레시토큰은 응답 바디에 담아서 반환합니다. 만약 없다면 저는 null 을 반환해주도록 구현하였는데요! 잘보시면 중간에 토큰 두개를 발급하고 나서 리프레시 토큰과 깃허브 아이디를 레디스에 저장하고 있는 것을 확인하실 수 있습니다.
여기서! 다시 tokenService 의 storeToken 을 가서 살펴봐주세요. 레디스라고 하는건 key-value 로 저장하는 방식을 사용하고 있는데요 저는 리프레시토큰을 키값으로 사용하는 것이 아니라 깃허브id 를 키값으로 저장하고 있습니다. 따라서 깃허브 아이디로 리프레시토큰을 검색할 수 있는거죠!
@Transactional
public CreateAvatarResponse createAvatar(CreateAvatarRequest createAvatarRequest) {
log.info("[MemberService.createAvatar]");
String githubId = createAvatarRequest.getGithubId();
if (memberRepository.existsByGithubIdAndStatus(githubId, BaseStatus.ACTIVE)) {
throw new MemberException(ALREADY_EXIST_MEMBER);
} else {
String nickname = createAvatarRequest.getNickname();
Long avatarId = createAvatarRequest.getAvatarId();
Avatar avatar = avatarRepository.findByAvatarIdAndStatus(avatarId, BaseStatus.ACTIVE)
.orElseThrow(() -> new MemberException(NOT_FOUND_AVATAR));
Long colorId = createAvatarRequest.getColorId();
Color color = colorRepository.findByColorIdAndStatus(colorId, BaseStatus.ACTIVE)
.orElseThrow(()-> new ColorException(NOT_FOUND_COLOR));
Member member = new Member(githubId, nickname, avatar, color, BaseStatus.ACTIVE);
memberRepository.save(member);
String jwtAccessToken = jwtProvider.createAccessToken(githubId);
String jwtRefreshToken = jwtProvider.createRefreshToken(githubId);
// 레디스 저장
tokenService.storeToken(jwtRefreshToken, githubId);
return new CreateAvatarResponse(jwtAccessToken,jwtRefreshToken);
}
}
이건 회원가입할 때의 코드인데요. 저의 서비스 로직은
로그인 -> 토큰 발급
회원가입 -> 성공하면 바로 로그인 처리 -> 토큰 발급
이렇기 때문에! 회원가입을 하고 나서도 토큰을 발급하고, 레디스에 저장하는 과정이 필요합니다.
트랜잭션 어노테이션을 통해서 문제 없이 코드가 전부 실행되어야 한번에 저장될 수 있도록 하였습니다. 그러니까 당연히 중간에 문제가 생기게되면 토큰도 발급되지 않겠죠????
마찬가지로 여기서도 깃허브 아이디를 키로, 리프레시토큰을 value 로 저장하고 있는 것을 코드로 확인하실 수 있습니다.
저장된걸 확인하고 싶으시다면 위의 블로그를 참고해서 redis-cli 라고 입력하고 keys * 를 입력하면 저장된 모습을 확인하실 수 있습니다!
끝!
'프로젝트' 카테고리의 다른 글
WebRTC offer 와 answer 가 뭐지 (0) | 2024.08.23 |
---|---|
Mediasoup 초기 시작 세팅하기 (0) | 2024.08.22 |
서버 배포 후 카카오 로그인 먹통현상.. (0) | 2024.08.01 |
스프링으로 깃허브(GitHub) 소셜로그인 (0) | 2024.07.02 |
IntelliJ 랑 AWS RDS 연동하는법 (0) | 2024.05.09 |