요즘 앱/웹 통합 서비스에서는 하나의 계정으로 여러 디바이스에서 로그인하는 다중 로그인 기능이 필수입니다.
하지만 이를 대충 구현하면 보안에 구멍이 생기기 쉽습니다. 제가 구현한 서비스에서는 JWT 기반 구조 안에서 보안까지 고려한 다중 로그인 환경을 설계하고 적용하도록 노력했는데, 그 과정을 공유해보고자 합니다.
원래는 보안을 위해 refreshToken을 쿠키에 담아 전달하고 있었습니다.
하지만 모바일 앱 환경에서는 쿠키 사용이 제한된다는 점을 고려해,
지금은 refreshToken을 쿠키가 아닌 응답 바디로 전달하도록 수정하였습니다.
UserRefreshToken
에 추가 :deviceType
(MOBILE/PC)os
(iOS, Android, Windows 등)deviceId
(mobile: 실제 디바이스 고유값 / web: UUID)@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Entity
@Builder
public class UserRefreshToken {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long userId;
private String token; // 리프레쉬 토큰
private String deviceId; // 로그인한 디바이스 Id
@Enumerated(EnumType.STRING)
private DeviceType deviceType; // 로그인한 디바이스 타입(PC, MOBILE)
private String os; // 로그인한 os 환경(Window, MacOS...)
private LocalDateTime lastUsedAt; // 마지막 접속 시각
}
필드명 | 설명 |
---|---|
deviceType | MOBILE , PC (모바일 웹은 MOBILE로 분류합니다) |
os | iOS, Android, Windows, macOS 등 |
deviceId | 고유 디바이스 식별자 (mobile: 디바이스 ID / web: UUID) |
❗️ 모바일 브라우저도 deviceType은 MOBILE로 설정하는 것이 정책 분기 및 통계 집계 시 유리합니다!
❗️ 웹 환경에서는 모바일처럼 고유한 deviceId를 직접 추출할 수 없습니다.
따라서 웹에서는 초기 접속 시 UUID를 생성해 localStorage에 저장하고, 이후부터는 이를 deviceId로 활용하도록 처리하였습니다.
deviceId
, deviceType
, os
refreshToken
deviceId
(헤더)deviceType
, os
(헤더)deviceId
와 refreshToken
을 기준으로 해당 디바이스 세션만 삭제합니다.로그인 기능은 늘 간단해 보이지만, 막상 구현하려 들면 생각보다 복잡합니다.
서비스 정책에 맞추면서도, 과도한 리소스를 쓰지 않도록 설계하는 데 많은 고민이 필요했고, 그 과정에서 보안까지 신경 쓰느라 쉽지 않았습니다.
돌이켜보면 여전히 부족한 부분들이 눈에 띄지만, 이 부분은 앞으로도 계속 개선해 나갈 예정입니다.
지금까지의 고민과 시행착오가 누군가에게 작은 참고가 되길 바랍니다.