-
[ Spring Boot ] Sever Send Event SSEBackEnd/Spring 2024. 12. 14. 11:57반응형
QR인증을 할때 모바일로 QR을 찍으면 해당 결과를 클라이언트에 보내줘야한다.
이때 socket방식과 sse방식 그리고 event bus 방식이 있다.
socket방식은 클라이언트와 서버 양방향 통신을 하여 서로 데이터를 주고 받을 수 있다.
sse는 클라이언트와 서버간의 단방향 통신만을 하며 한 곳에서만 데이터를 줄 수 있다.
event bus의 경우에는 특정 기능이 실행이 되었을때 데이터를 전달할 수 있다.
QR인증 방식은 이 중 하나를 선택해서 모바일로 QR을 인증할 때 클라이언트에 데이터를 보내주면 된다.
SSE를 활용하여 기능을 구현해보고자 한다.
SSE Emitter
Java에서는 sseEmitter를 활용하여 간단하게 sse를 구성할 수 있다.
간단하게 테스트를 하기위해 controller에서만 작업을 진행했다.
LoginStorageComponent를 하나 만들어서 사용자가 QR을 찍어서 api를 호출할때 이때 해당 정보를 저장하기 위해 저장용 component를 만들었다.
@Component public class LoginStorageComponent { private HashMap<String, Boolean> loginData = new HashMap<>(); public LoginStorageComponent() { loginData.put("time",false); } public void saveLoginData(String key, boolean auth) { loginData.put(key,auth); } public Boolean getData(String key) { return loginData.get(key); } public void removeData(String key) { loginData.remove(key); } }
SSE Emitter를 활용하여 프론트랑 똑같이 연결 시간은 30초로 설정했다.
sseEmitter.complate()를 사용하면 sse연결이 끊어지는데
이는 프론트 혹은 백에서 하던 어디서 하던 한곳이라도 끊어진다면 자동으로 연결이 끊어지기에
프론트에서 끊어지도록 설정을 해서 백에서는 따로 끊어지도록 하지는 않았다.
이후 Thread를 활용하여 5초마다 현재 저장되어있는 loginStorageComponent에서 데이터를 가져와 현재 저장되어있는 데이터를 가져온다.
이후 sseEmitter.send로 클라이언트로 해당 정보를 보내준다.
보내줄때 name은 auth로받을수 있도록 하며 보내는 데이터는 현재 loginStorageComponent에 유저 데이터가 들어왔는지 아닌지에 대한 여부를 보낸다.
@RequestMapping("/sse") @RequiredArgsConstructor @RestController public class SSEController { private final LoginStorageComponent loginStorageComponent; @GetMapping("/connect") public SseEmitter connect() { long timeout = 30_000L; SseEmitter sseEmitter = new SseEmitter(timeout); new Thread(() -> { long startTime = System.currentTimeMillis(); System.out.println("SSE Connect"); try{ while(System.currentTimeMillis() - startTime < timeout) { boolean auth = loginStorageComponent.getData("qrAuth"); sseEmitter.send(SseEmitter.event().name("auth").data(auth)); // client에서 끊으면 자동으로 끊김 // if(auth) { // sseEmitter.complete(); // break; // } Thread.sleep(3000); } } catch (Exception e) { sseEmitter.completeWithError(e); } }).start(); return sseEmitter; } }
이후 모바일에도 QR인증을 하면 해당 백을 호출하기 위해 cors설정에서도 expo도 추가를 해주었다.
@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("http://localhost:5173", "exp://ip주소:8080") .allowedMethods("GET","POST","PUT","DELETE") .allowedHeaders("*"); } }
React Expo
QR Scanner에서 해당 QR을 찍어서 나온 데이터를 활용해 백을 호출한다.
해당 url을 받아와 axios를 활용하여 호출 한다.
이전에는 QR데이터에 QR코드 만료시간을 따로 설정하지는 않았지만
기능을 추가하면서 QR코드에 만료 시간을 추가해주었다.
unix timestamp로 해당 QR코드가 만들어진 시간을 기준으로 30초의 만료시간을 추가했다.
/* unix timestamp 형식으로 변경 */ long expirationTimestamp = Instant.now().getEpochSecond() + 30; JSONObject data = new JSONObject(); data.put("userId", userId); data.put("password", password); data.put("timeout",expirationTimestamp);
axios로 보내진 데이터를 받으면 아래의 controller가 호출되며 해당 QR코드의 만료시간을 체크하여 true 혹은 false로 처리한다.
만약 만료시간이 아니라면 QR을 통해 전달된 userId와 password값을 loginStorageComponent에 저장한다.
@PostMapping("/qr-check") public boolean QrCheck(@RequestBody LoginRequestDTO requestDTO) { long currentTimestamp = Instant.now().getEpochSecond(); if(currentTimestamp < requestDTO.getTimeout()) { loginStorageComponent.saveLoginData("qrAuth",true); return true; } else { return false; } }
React SSE Connect
useEffect로 QR코드가 생성되는 순간 sse를 연결한다.
sse 연결은 new EventSource로 요청할 url을 넣어주면 서로 연결이 되며
백에서 auth로 이름을 설정했기에 sse.addEventListener를 통해 'auth'로 들어온 데이터를 받을때마다 호출한다.
백에서 단순히 QR을 찍었을때 백이 호출되면 true가 되도록 설정했기에 백에서 들어온데이터가 true일때 sse 연결을 끊어준다.
const sseRef = useRef<EventSource | null>(null) useEffect(() => { if(qrImage == null) return const sse = new EventSource('http://localhost:8080/sse/connect') sseRef.current = sse /* onmessage는 sse시 이름이 없는 경우에만 사용 가능함 */ // sse.onmessage = (event) => { // console.log(event.data); // } sse.addEventListener('auth', (event) => { console.log(event.data) if(event.data == "true") { setLoginSuccess('로그인!') sse.close() } }) return () => { sse.close() } }, [qrImage]);
추가적으로 시간이 초과되면 onTimeOut 함수가 실행되며 sse를 끊어준다.
좀 다르게 해보고 싶어서 끊어지면 다시 QR코드를 생성하며 다시 SSE를 연결해주고 시간을 초기화를 시켜주었다.
function onTimeOut() { sseRef.current?.close() handleLoginAPIReauest() }
Result
이러면 로그인 버튼을 누를시 QR코드가 생성이 되며 SSE가 연결되어 백에서 설정한 시간을 기준인 3초 간격으로 값을 받아온다.
false로 계속 나오다가 모바일로 QR코드를 찍는 순간 다음으로 전달된 값은 true가 온것을 확인할 수 있다.
현재는 테스트를 하고자 true가 왔을때 타이머를 없애거나 이후 로직이 없어서 계속 QR코드가 생성되며 다시 sse처리 계속해서 진행되고 있다.
728x90반응형'BackEnd > Spring' 카테고리의 다른 글
[ Java ] 기상청 Api 완벽 활용하기 - 2 ( 시간별 날씨, 주간 날씨 ) (0) 2025.04.05 [ Java ] 기상청 Api 완벽 활용하기 - 1 ( 실시간 날씨, 미세먼지 조회 ) (0) 2025.04.05 [ Spring Boot ] DTO 상속받아서 로그인 정보 사용하기 (0) 2025.03.16 [ Spring Boot ] Google Zxing - QR 코드 생성 (1) 2024.12.11 [Oracle Cloud] DBeaver&Spring boot 연동 (1) 2024.10.26