Flutter로 로그인 구현하기

Jocy·2022년 8월 1일
3
post-thumbnail

요청에 의한 접근 권한 체크

이전 글에서 앱에서 로그인 상태 유지를 위한 방법으로 FlutterSecureStorage를 사용하였다.
이 방법은 로그인 상태를 유지한 것이지 클라이언트가 서버에 특정 정보를 요청할 때
적절한 권한을 가진 클라이언트가 요청한 요청인지에 대해서 권한 체크를 할 수가 없다.
클라이언트가 GET이나 POST 요청한다고 해서 서버는 무작정 해당 정보를 접근할 수 있게하면 안된다.
서버가 인증한 클라이언트일 때 특정 정보에 요청 및 접근 할 수 있게 해야 된다.

세션 로그인 토큰 로그인

일반적으로 로그인 권한을 인증하는 방식에는 세션 로그인토큰 로그인 2가지가 있다.
모바일과 웹의 환경이 달라서 로그인을 구현하기 위해서 어떻게 구현하는지 정리 해보았다.

세션 로그인

  • 클라이언트(브라우저)에서 서버로 로그인 요청을 하면 서버에서 세션을 생성하고
    세션 ID를 클라이언트로 보낸 후 클라이언트는 쿠키를 통해 세션을 유지하는 방식이다.
  • 그러나 앱으로 로그인을 구현하려면 네이티브 앱에는 세션이 없기 때문에 구현할 수 없다.
    하이브리드 앱이라면 웹뷰를 사용해서 100% 웹 앱 의 형태를 지녔다면 웹뷰의 세션을 통해
    이러한 로그인 방식을 구현할 수 있다.
  • 세션 로그인은 IP 단위로 유지되기 때문에 와이파이 사용 등 IP 변경이 잦은
    모바일 기기에서는 세션이 자주 풀리고, 웹뷰 내부에는 세션이 있더라도
    REST API 호출 시에는 그 세션 정보가 없다

이러한 이유로 네이티브 와 하이브리드 등의 모바일에서는 세션 로그인이 권장되지 않거나 불가능하다.

토큰 로그인

  • 토큰 로그인 의 경우에는 클라이언트에서 서버로 로그인 요청을 하면 회원 정보를 확인한 후 
    AccessToken을 발급하고 클라이언트는 서버와 통신 시에 이 토큰과 같이 정보를 담아서 요청하면 된다.
  • 토큰 로그인은 보안 이슈가 있는데, 바로 AccessToken탈취당하면 대처법이 없다는 것이다.
    이를 해결하기 위해 AccessToken에 만료 기간을 두고 RefreshToken을 통해
    이를 지속적으로 갱신 해준다.
    AccessToken을 탈취 당하더라도, 기간이 만료되면 재사용이 불가능하게 된다.
    서버에서 사용자 인증을 통해 발급받은 RefreshToken을 통해서만 갱신할 수 있기 때문에 안전하다.
  • RefreshToken 또한 지속적으로 갱신해서 클라이언트에게 발급한다.
    하지만 클라이언트와 서버 쪽 모두 개발 과정이 더 복잡해진다는 점이 있다.

JWT 토큰

JWT는 JSON Web Token의 약자로, 각 객체 사이에서 속성 정보를
JSON 데이터 구조로 표현하고 암호화를 통해 정보를 전달하는 Token을 말한다.
JWT 토큰은 다양한 프로그래밍 언어를 지원해서 Flutter에서도 활용이 가능하다.

JWT 토큰의 구조

JWT 토큰을 받으면 . 을 기준으로 Header, Payload, Verify Signature 순서로 들어간다.

  • header (0번째)
    • typ : 토큰의 타입을 지정(JWT)
    • alg : 해싱 알고리즘을 지정, 보통 HMAC SHA256/RSA 사용
  • payload (1번째)
    • registered 클레임 : 토큰에 대한 정보를 담기위해 이미 이름이 정해진 클레임
    • public 클레임 : 충돌이 방지된 이름을 가지고 있는 클레임
    • private 클레임 : 클라이언트 <-> 서버 간의 협의 후 사용되는 클레임
  • verify signature (2번째)
    • header의 인코딩 값과 payload의 인코딩값을 합친후 주어진 비밀키

토큰 발급 및 저장 과정

Node.js (Express)

(...)
if (user_data.password === body.password) {
      const { user_id, phone_number} = user_data;

      const PAYLOAD = { type: 'JWT', user_id, phone_number }; // Payload에 담을 값
      const SECRET_KEY = secretKey.secretKey; // config에 담은 secret_key 정보
      const OPTION = secretKey.options; // 토큰의 option값
      const token = jwt.sign(PAYLOAD, SECRET_KEY, OPTION); // jwt.sign으로 3가지 인자를 token 담아 클라이언트에게 넘겨준다.

      res.status(200).send({ token });    
    } else {
      res.status(404).send('비밀번호가 일치하지 않습니다.');      
}

App (Flutter)

static final storage = FlutterSecureStorage(); // 토큰 값과 로그인 유지 정보를 저장, SecureStorage 사용

(...)

Response response = await dio.post('api/user/login', data: param);
if (response.statusCode == 200) { // 로그인을 성공하면
    String token = response.data['token']; // response의 token키에 담긴 값을 token 변수에 담아서
	Map<String, dynamic> payload = Jwt.parseJwt(token); // 토큰 내부의 값을 Map 구조에 담는다
    loginID = payload['user_id'];
    var val = jsonEncode(Login('$token', '$loginID')); 토큰 값과 로그인 유지 정보를 va변수에 담는다
    await storage.write( key: 'login', value: val ); // login key에 SecureStorage에 담는다
    return true;
} else {
  (...)
}

토큰 검증 과정

Node.js (Express)

const authUtil = {
    checkToken: async (req, res, next) => {
        var token = req.headers.token;
        // 토큰이 없을 경우
        if (!token)
            return res.json(util.fail(CODE.BAD_REQUEST, MSG.EMPTY_TOKEN));
        const user = await jwt.verify(token);
        // 유효기간 만료된 경우
        if (user === TOKEN_EXPIRED)
            return res.json(util.fail(CODE.UNAUTHORIZED, MSG.EXPIRED_TOKEN));
        // 유효하지 않는 토큰인 경우
        if (user === TOKEN_INVALID)
            return res.json(util.fail(CODE.UNAUTHORIZED, MSG.INVALID_TOKEN));
      	// 유효하지 않는 id인 경우
        if (user.idx === undefined)
            return res.json(util.fail(CODE.UNAUTHORIZED, MSG.INVALID_TOKEN));
        req.idx = user.idx;
        next();
    }
}
profile
Software Engineer

3개의 댓글

comment-user-thumbnail
2023년 7월 3일

안녕하세요, 웹 사이트애서, 트래픽이 몰리거나 과부하가 걸려도, 로그인이 풀리지 않게, 작업이
가능할까요? 비용을 지불하고, 작업을 부탁드리고, 싶습니다... 전화번호는 010 8927 4588이며, 카카오톡 아이디는, kkkkyun입니다. 보신다면, 문자나 카톡 꼭 좀 부탁드리고 싶습니다..!

1개의 답글
comment-user-thumbnail
2023년 11월 21일

혹시 "세션 로그인은 IP 단위로 유지되기 때문에 와이파이 사용 등 IP 변경이 잦은 모바일 기기에서는 세션이 자주 풀리고, 웹뷰 내부에는 세션이 있더라도 REST API 호출 시에는 그 세션 정보가 없다" 이 정보는 어디서 보신걸까요? 제가 알던 개념이랑 달라서 공부해보고 싶어서요!

답글 달기