안드로이드 자동로그인 세션 - andeuloideu jadonglogeu-in sesyeon

1. 토큰과 세션의 차이점

이전 프로젝트에서는 세션을 이용한 로그인을 구현했었는데

이번에는 모바일 어플리케이션을 만들고 있기 때문에

토큰 기반의 인증 방식을 채택하게 되었다.

그리고 토큰 방식을 공부하다보니, 세션에 대한 이해도 좀 더 깊어진 것 같다. 

그 때는 대체 어떻게 구현했던 건지ㅎㅎ

간단하게 정리하자면

세션은

로그인한다 -> 로그인한 상태라는 것을 서버(또는 DB)에 저장

서버와 클라이언트가 연결된 상태로(Stateful), 다른 요청들을 처리하게 된다. 

서버가 클라이언트와 연결된 상태를 유지해야 하기 때문에

로그인한 유저가 엄청나게 많아진다면? 

그리고 서버를 다른 방식으로 확장하고 싶다면? 

이런 경우에 서버가 버거워하거나, 엄청나게 번거로워질 수 있다. 

그래서 토큰은 무엇이 다르냐.. 하면

로그인을 할 때 서버가 클라이언트에 토큰을 준다. 

-> 클라이언트는 매 요청시마다 토큰을 같이 보내준다. 

-> 서버는 연결상태(로그인여부?)를 확인할 필요없이 토큰만 있으면, 작업을 처리한다. (Stateless)

서버와 클라이언트가 계속 연결되어 있는 것이 아니기 때문에 확장성이 좋고, 여러 도메인에서도 이용할 수 있고, 쿠키를 전달함으로써 발생할 수 있는 보안 이슈에도 강하다. 


2. 그래서 JWT 란 무엇인가

위에서 설명한 토큰 기반의 로그인을 구현할 수 있도록 나온 것이 바로 JWT 이다. 

JWT 는 그 자체가 필요한 정보를 다 가지고 있고, header 에 넣어서 전달하는 간단한 방식으로 사용할 수 있다. 

아주 길게 만들어진다. 

만들어지는 방식은 다른 포스팅에서 다루고, 직접 token 을 사용해봐야겠다. 


3. 클라이언트에서 JWT 이용하여 로그인 구현하기

(서버는 내가 구현하지 않았기 때문에 나중에 더 알아보고 포스팅할 예정)

1) 로그인을 하면, 토큰을 받는다.  

아래 포스트맨 사진을 보면 로그인했을 때, Set-Cookie 에 JWT 가 들어있는 것을 확인할 수 있다. 

안드로이드 자동로그인 세션 - andeuloideu jadonglogeu-in sesyeon
안드로이드 자동로그인 세션 - andeuloideu jadonglogeu-in sesyeon
포스트맨 버전에 따라 위에 있는 Cookies에서도 확인 가능한 듯

2) 전달받은 토큰을 AsyncStorage 에 저장한다. 

AsyncStorage.setItem(key, item) 이용!

// login post 할 때 

fetch(`${fakeserver}/auth/login`, {
      method : 'POST',
      body : JSON.stringify(logindata),
      headers : {
        'Content-Type' : 'application/json',
      },
    }).then((res) => {
    // res.headers 에 set-cookie 가 담겨서 온다. 
      const cookie = res.headers['map']['set-cookie'];
      
      // AsyncStorage.setItem 을 이용하여 저장!
      AsyncStorage.setItem('token', cookie);
      if (res.status === 200 || res.status === 201) {
        res.json()
      .then((data) =>  {
      // 뒤에서 언급할 refreshtoken 도 저장해준다. 
        const refreshtoken = data["refreshToken"];
        AsyncStorage.setItem('refreshtoken', refreshtoken);
        // 로그인 성공하면 메인 화면으로 이동
        this.props.navigation.navigate('Habits');
      });
      } else if (res.status === 400) {
        alert('입력한 값을 다시 확인해주세요.');
      } else if (res.status === 500) {
        alert('서버와의 연결이 불안정합니다.\n잠시 후에 다시 시도해주세요.');
      }
    })
    .catch((error) => console.log('fetch error', error))
  };

3) 요청할 때마다 토큰을 header에 넣어 같이 보내준다. 

그렇지 않으면, No Authentication 에러를 받게 될 것!

 // data get 요청을 보낼 때 
 
 async getdata() {
    let token = '';
    // AsyncStorage 에서 토큰을 찾아온다. 
    await AsyncStorage.getItem('token', (err, result) => {
      token = result;
    });
    
    // header에 토큰을 넣어준다. 
    let header = new Headers();
    header.append('Cookie', token);
    const myInit = {
      method : 'GET',
      headers : header,
    }

    fetch(`${fakeserver}/users/info`, myInit)
      .then((res) => {
        if (res.status === 200 || res.status === 201) {
          res.json()
          .then( 
          // 생략
          })
        }
      })
      .catch(async (err) => {
        console.log(err)
        console.log('get request failed!');
        // 여기서 실패했다면 refresh token 도 기한이 지났다는 뜻이므로, AsyncStorage 를 비우고, 
        // 로그인 화면으로 이동하게 한다. 
        await AsyncStorage.clear();
        this.props.navigation.navigate('AuthLoading');
      });

포스트맨에서는 아래처럼 header에 넣어서 요청을 보낼 수 있다.  

x-access-token 에 토큰만 넣거나, Cookie에 모든 정보를 넣거나, 서버와 맞춰서 하면 되는 것 같다. 

안드로이드 자동로그인 세션 - andeuloideu jadonglogeu-in sesyeon

4. Refresh Token 사용하여 자동 로그인 구현하기

로그인할 때 그냥 token 뿐만 아니라, refresh token 도 받아서 저장했었다!

  const refreshtoken = data["refreshToken"];
  AsyncStorage.setItem('refreshtoken', refreshtoken);

token 이 만료되었을 때 refresh token을 갖고 있으면

그걸로 refresh 요청을 보내고, 응답으로 새로운 토큰을 받을 수 있다. 

그러면 새로 받은 토큰을 저장해서 그걸 넣어서 요청을 보내면 된다. 

refresh token 까지 만료가 된다면 그 때는 로그인화면으로 이동시킨다. 

언제 refresh 요청을 날려야 할지가 궁금했었는데,

처음 서비스에 진입했을 때 항상 토큰을 새로 발급해주면 된다고 한다.

(다른 방법이 있을 수도 있겠지만? 일단 내가 얻은 답)

1) refresh token 을 불러와서 refresh 요청을 보낸다. 새로운 토큰을 받아서 저장

 // refresh 요청하기
 
 async getrefreshtoken () {
    let refreshtoken = '';
    // refreshtoken을 AsyncStorage 에서 찾아온다. 
    await AsyncStorage.getItem('refreshtoken', (err, result) => {
      refreshtoken = result;
    });
    // body 에 넣어주고
    const myInit = {
      method : 'POST',
      headers : {
        'Content-Type' : 'application/json',
      },
      body : JSON.stringify(
        { refreshToken : refreshtoken },
      ),
    };
    
    // refresh 요청을 보내서 응답으로 새로운 토큰을 받는다. 
    fetch(`${fakeserver}/auth/refresh`, myInit)
    .then((res) => {
      const newcookie = res.headers['map']['set-cookie'];
      if (res.status === 200 || res.status === 201) {
        res.json()
        .then(async (data) => {
          // 새로운 토큰을 저장
          await AsyncStorage.setItem('token', newcookie);
        });
      }
    })
    .catch(async (err) => {
    // refresh 요청이 실패했다면, AsyncStorage 를 비우고, 로그인화면으로 이동한다.
      console.log('refresh request failed!', err);
      await AsyncStorage.clear();
      this.props.navigation.navigate('AuthLoading');
    });
  }

2) 서비스에 처음 진입할 때마다 refresh 요청을 보내서 token을 새로 저장한다. 

나는 메인화면의 componentDidmount 에서 했는데, 그냥 로그인 성공하고 나서 실행하면 될 것 같다. 

  public async componentDidMount() {
    this.getrefreshtoken();
    this.getdata();
  }

5. 로그아웃 

따로 로그아웃 컴포넌트를 작성하진 않고, drawer navigation의 text 컴포넌트에 넣었다. 

 <Text
    onPress={() => {
      Alert.alert(
        '로그아웃하시겠습니까?',
        '',
        [
          {
            text: 'Cancel',
            onPress: () => console.log('Cancel Pressed'),
            style: 'cancel',
          },
          {text: 'OK', onPress: () => {
            fetch(`${fakeserver}/auth/login`, {
              method : 'POST',
              headers : {
                'Content-Type' : 'application/json',
              },
            })
            .then(async (res) => {
            // 로그아웃에 성공하면, AsyncStorage 를 비우고, 로그인화면으로 돌아간다. 
              await AsyncStorage.clear();
              navigation.navigate('AuthLoading');
            });
          },
          },
        ],
        { cancelable: false },
      );

    }
    }
    style={styles.uglyDrawerItem}>Logout
  </Text>

참고 링크

토큰, JWT  :  https://velopert.com/2350

[JWT] 토큰(Token) 기반 인증에 대한 소개 | VELOPERT.LOG

소개 토큰(Token) 기반 인증은 모던 웹서비스에서 정말 많이 사용되고 있습니다. 여러분이 API 를 사용하는 웹서비스를 개발한다면, 토큰을 사용하여 유저들의 인증작업을 처리하는것이 가장 좋은 방법입니다. 토큰 기반 인증 시스템을 선택하는데에는 여러가지 이유가 있는데요, 그 중 주요 이유들은 다음과 같습니다 Stateless 서버 Stateless 서버를 이해하려면 먼저 Stateful 서버가 무엇인지 알아야합니다. Stateful 서버는 클라이언트에게

velopert.com

안드로이드 자동로그인 세션 - andeuloideu jadonglogeu-in sesyeon

로그인, 토큰 저장, navigation : https://busy.org/@anpigon/2x9aze-react-native

[React Native] 인증(로그인) 상태에 따라 화면 분기하기 - Busy

React Navigation의 Authentication flows 문서를 참고하여 작성하였습니다. 참고: https://reactnavigation.org/docs/en/auth-flow.html Authentication flows 일반적... by anpigon

busy.org

안드로이드 자동로그인 세션 - andeuloideu jadonglogeu-in sesyeon