국비교육(22-23)

82일차(1)/Android App(44) : 로그아웃 기능 구현

서리/Seori 2023. 2. 6. 15:05

82일차(1)/Android App(44) : 로그아웃 기능 구현

 

 

- 로그인 기능 코드 리뷰

 

AndroidController

- logincheck 요청에서 Map 요청

- Map을 responsebody로 요청하면 { } 이렇게 json 문자열로 응답된다.

 

- session을 사용해서 저장된 id가 없으면 isLogin이라는 키 값으로 값을 담아준다.

- 저장된 id가 있으면 true와 아이디를 담아준다.

 

- 클라이언트가 웹브라우저가 아니고 안드로이드 앱이므로! json으로 응답한다.

 

[ 웹서버가 json으로 응답하는 case ]
1. 웹브라우저에서 ajax 요청을 해올 때
2. 모바일 App에서 http 요청을 해올 때

 

1.  웹브라우저에서 ajax 요청을 해올 때

   {"isLogin":true} 이런 형식의 json 문자열을 받아서
   javascript 실행환경에서 {isLogin:true} 형식의 object로 변환해서 사용한다.

- js 환경에서는 점을 찍어서 바로 사용하면 된다.

ex) let obj -= {isLogin:true};

      let result = obj.isLogin;

- json은 javascript 표기법을 모방한 것이므로 js 형태로 쉽게 바뀐다.

 

 

2. 모바일 App 에서 http 요청을 해올 때

- {"isLogin":true} 이런 형식의 json 문자열을 받아서

  java에서 아래와 같이 문자열을 넣어준다.

  JSONObject obj=new JSONObject("{"isLogin":true}");

  boolean result = obj.getBoolean("isLogin");

 


 

LoginCheckTask

 

 

- 이 문자열을 Strings 배열의 0번 방에 전달해서,

  HttpURLConnection객체를 사용해서 http 요청을 한다.

- 그러면 서버는 json형식의 문자열을 응답하고,

  이것을 클라이언트쪽에서 BufferedReader를 사용해서 반복문으로 읽어낸다.

 

- 이 읽어낸 문자열을 JSONObject 객체를 사용해서 읽어낸다.

 


 

** 로그아웃 기능 만들기

- 로그아웃 액티비티로 이동해서 로그아웃 처리되도록 할 예정

 

- 그냥 Main Activity 에서 로그아웃 처리되도록 할 수도 있지만,

 다른 액티비티에서도 로그아웃이 가능하게 하려면 로그아웃용 액티비티를 따로 만드는 것이 낫다.

 

 

새 액티비티 생성

LogoutActivity

package com.example.step18login;

import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;

import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;

import com.example.step18login.databinding.ActivityLogoutBinding;

import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;

public class LogoutActivity extends AppCompatActivity {
    //필드
    ActivityLogoutBinding binding;
    String sessionId;
    SharedPreferences pref;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_logout);

        pref=PreferenceManager.getDefaultSharedPreferences(this);
        sessionId=pref.getString("sessionId", "");

        //로그아웃 태스크 실행하기
        new LogoutTask().execute(AppConstants.BASE_URL+"/logout");
    }
    class LogoutTask extends AsyncTask<String, Void, Boolean> {

        @Override
        protected Boolean doInBackground(String... strings) {
            //로그아웃 처리를 해주는 url
            String requestUrl=strings[0];
            //서버가 http 요청에 대해서 응답하는 문자열을 누적할 객체
            StringBuilder builder=new StringBuilder();
            HttpURLConnection conn=null;
            InputStreamReader isr=null;
            BufferedReader br=null;
            boolean isSuccess=false;
            try{
                //URL 객체 생성
                URL url=new URL(requestUrl);
                //HttpURLConnection 객체의 참조값 얻어오기
                conn=(HttpURLConnection)url.openConnection();
                if(conn!=null){//연결이 되었다면
                    conn.setConnectTimeout(20000); //응답을 기다리는 최대 대기 시간
                    conn.setRequestMethod("GET");//Default 설정
                    conn.setUseCaches(false);//케쉬 사용 여부

                    if(sessionId != null) {
                        conn.setRequestProperty("Cookie", sessionId);
                    }

                    //응답 코드를 읽어온다.
                    int responseCode=conn.getResponseCode();

                    if(responseCode==200){//정상 응답이라면...
                        //서버가 출력하는 문자열을 읽어오기 위한 객체
                        isr=new InputStreamReader(conn.getInputStream());
                        br=new BufferedReader(isr);
                        //반복문 돌면서 읽어오기
                        while(true){
                            //한줄씩 읽어들인다.
                            String line=br.readLine();
                            //더이상 읽어올 문자열이 없으면 반복문 탈출
                            if(line==null)break;
                            //읽어온 문자열 누적 시키기
                            builder.append(line);
                        }
                    }
                }
                List<String> cookList=conn.getHeaderFields().get("Set-Cookie");
                if(cookList != null){
                    for(String tmp : cookList){
                        if(tmp.contains("JSESSIONID")){
                            String sessionId=tmp.split(";")[0];
                            SharedPreferences.Editor editor=pref.edit();
                            editor.putString("sessionId", sessionId);
                            editor.apply();
                            LogoutActivity.this.sessionId=sessionId;
                        }
                    }
                }
                //출력받은 문자열 전체 얻어내기
                JSONObject obj=new JSONObject(builder.toString());
                isSuccess=obj.getBoolean("isSuccess");
            }catch(Exception e){//예외가 발생하면
                Log.e("LoginCheckTask", e.getMessage());
            }finally {
                try{
                    if(isr!=null)isr.close();
                    if(br!=null)br.close();
                    if(conn!=null)conn.disconnect();
                }catch(Exception e){}
            }

            return isSuccess;
        }

        @Override
        protected void onPostExecute(Boolean isSuccess) {
            super.onPostExecute(isSuccess);
            if(isSuccess){
                new AlertDialog.Builder(LogoutActivity.this)
                        .setTitle("알림")
                        .setMessage("로그 아웃 되었습니다")
                        .setNeutralButton("확인", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                finish();//액티비티 종료
                            }
                        })
                        .create()
                        .show();
            }
        }
    }
}

 

activity_logout.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".LogoutActivity"
    android:background="#cecece">

</androidx.constraintlayout.widget.ConstraintLayout>

 

(equals->execute로 수정)

- 들어있는 값을 가지고 요청하기

 

- 로그아웃시 누가 로그아웃 요청을 하는지 세션 아이디를 전달해 주어야 한다.

- 이 sessionId는 sharedPreference에 저장되어 있다.

- 로그인하려면 이 sessionId를 요청할때 같이 쿠키로 전달해줘야 한다.

 

- Device File Explorer의 data>data>모듈명>shared_prefs 폴더로 들어가면 XML문서가 만들어져 있는 것을 볼 수 있다.

 

- 그 안에는 이렇게 sessionId값이 저장되어 있다.

- 이것은 저번에 발급받은것 이라... 새것을 받아야 한다.

- 서버에서 새로 발급받으면 이 xml파일 안에 저장되는 값이 달라진다.

 

 

MainActivity 수정

package com.example.step18login;

import android.content.Intent;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;

import androidx.appcompat.app.AppCompatActivity;

import com.example.step18login.databinding.ActivityMainBinding;

import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;

public class MainActivity extends AppCompatActivity {
    //activity_main.xml 문서와 대응되는 데이터 타입
    ActivityMainBinding binding;
    //session id 값을 영구 저장할 SharedPreferences
    SharedPreferences pref;
    //session id 값을 임시 저장할 필드
    String sessionId;
    //로그인된 id 값을 저장할 필드
    String id;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //ActivityMainBinding 객체의 참조값 얻어와서 필드에 저장
        binding=ActivityMainBinding.inflate(getLayoutInflater());
        //화면 구성하기
        setContentView(binding.getRoot());
        //로그인 버튼을 누르면
        binding.login.setOnClickListener(v -> {
            //로그인 액티비티로 이동한다.
            Intent intent=new Intent(this, LoginActivity.class);
            startActivity(intent);
        });
        //로그아웃 버튼을 누르면
        binding.logout.setOnClickListener(v -> {
            //로그아웃 액티비티로 이동한다.
            Intent intent=new Intent(this, LogoutActivity.class);
            startActivity(intent);
        });       
    }

    @Override
    protected void onStart() {
        super.onStart();
        //SharedPreferences 객체의 참조값 얻어와서 필드에 저장하기
        pref= PreferenceManager.getDefaultSharedPreferences(this);
        //저장된 session id 가 있는지 읽어와 본다.(없다면 기본값은 빈 문자열)
        sessionId=pref.getString("sessionId", "");

        //로그인 여부를 체크하는 작업을 할 비동기 task
        new LoginCheckTask().execute(AppConstants.BASE_URL+"/logincheck");
    }

    //로그인 여부를 체크하는 작업을 할 비동기 task
    class LoginCheckTask extends AsyncTask<String, Void, Boolean> {

        @Override
        protected Boolean doInBackground(String... strings) {
            //로그인 체크 url
            String requestUrl=strings[0];
            //서버가 http 요청에 대해서 응답하는 문자열을 누적할 객체
            StringBuilder builder=new StringBuilder();
            HttpURLConnection conn=null;
            InputStreamReader isr=null;
            BufferedReader br=null;
            boolean isLogin=false;
            try{
                //URL 객체 생성
                URL url=new URL(requestUrl);
                //HttpURLConnection 객체의 참조값 얻어오기
                conn=(HttpURLConnection)url.openConnection();
                if(conn!=null){//연결이 되었다면
                    conn.setConnectTimeout(20000); //응답을 기다리는 최대 대기 시간
                    conn.setRequestMethod("GET");//Default 설정
                    conn.setUseCaches(false);//케쉬 사용 여부
                    //App에 저장된 session id가 있다면 요청할 때 쿠키로 같이 보내기
                    if(!sessionId.equals("")) {
                        //JSESSIONID=xxx 형식의 문자열을 쿠키로 보내기
                        conn.setRequestProperty("Cookie", sessionId);
                    }

                    //응답 코드를 읽어온다.
                    int responseCode=conn.getResponseCode();

                    if(responseCode==200){//정상 응답이라면...
                        //서버가 출력하는 문자열을 읽어오기 위한 객체
                        isr=new InputStreamReader(conn.getInputStream());
                        br=new BufferedReader(isr);
                        //반복문 돌면서 읽어오기
                        while(true){
                            //한줄씩 읽어들인다.
                            String line=br.readLine();
                            //더이상 읽어올 문자열이 없으면 반복문 탈출
                            if(line==null)break;
                            //읽어온 문자열 누적 시키기
                            builder.append(line);
                        }
                    }
                }
                //서버가 응답한 쿠키 목록을 읽어온다.
                List<String> cookList=conn.getHeaderFields().get("Set-Cookie");
                //만일 쿠키가 존재한다면
                if(cookList != null){
                    //반복문 돌면서
                    for(String tmp : cookList){
                        //session id 가 들어있는 쿠키를 찾아내서
                        if(tmp.contains("JSESSIONID")){
                            //session id만 추출해서
                            String sessionId=tmp.split(";")[0];
                            //SharedPreferences 를 편집할 수 있는 객체를 활용해서
                            SharedPreferences.Editor editor=pref.edit();
                            //sessionId 라는 키값으로 session id 값을 저장한다.
                            editor.putString("sessionId", sessionId);
                            editor.apply(); //apply() 는 비동기로 저장하기 때문에 실행의 흐름이 잡혀있지 않다.
                            //필드에도 담아둔다.
                            MainActivity.this.sessionId=sessionId;
                        }
                    }
                }
                //출력받은 문자열 전체 얻어내기
                JSONObject obj=new JSONObject(builder.toString());
                /*
                    {"isLogin":false} or {"isLogin":true, "id","gura"}
                    서버에서 위와 같은 형식의 문자열을 응답할 예정이다.
                 */
                Log.d("서버가 응답한 문자열", builder.toString());
                //로그인 여부를 읽어와서
                isLogin=obj.getBoolean("isLogin");
                //만일 로그인을 했다면
                if(isLogin){
                    //필드에 로그인된 아이디를 담아준다.
                    id=obj.getString("id");
                }
            }catch(Exception e){//예외가 발생하면
                Log.e("LoginCheckTask", e.getMessage());
            }finally {
                try{
                    if(isr!=null)isr.close();
                    if(br!=null)br.close();
                    if(conn!=null)conn.disconnect();
                }catch(Exception e){}
            }
            //로그인 여부를 리턴하면 아래의 onPostExecute() 메소드에 전달된다.
            return isLogin;
        }

        @Override
        protected void onPostExecute(Boolean isLogin) {
            super.onPostExecute(isLogin);
            //여기는 UI 스레드이기 때문에 UI와 관련된 작업을 할 수 있다.
            //TextView에 로그인 여부를 출력하기
            if(isLogin){
                binding.userInfo.setText(id+" 님 로그인중...");
            }else{
                binding.userInfo.setText("로그인이 필요 합니다.");
            }
        }
    }
}

 

- MainActivity onCreate에 로그아웃 버튼 바인딩 추가하기

 

 


 

STS - Android Controller에 추가하기

@RequestMapping("/api/logout")
@ResponseBody
public Map<String, Object> logout(HttpSession session){
  session.invalidate();
  Map<String, Object> map=new HashMap<>();
  map.put("isSuccess", true);
  return map;
}

- 로그아웃을 누르면 STS의 logout 메소드가 호출되어서 

 저장되어있는 session값이 초기화된다.

 

 

- 이렇게 창이 나오고, 확인을 누르면 메인 액티비티로 이동한다.

 xml문서에 있는 sessionId 가 달라진 것을 볼 수있다.

 

- MainActivity의 여기서 이 값이 쿠키에 저장될 수 있도록 한다.

 

- 지금은 아이디, 비번이 gura, 1234 일 때 동작하도록 만들어본 것이다.

- 이제 다른 아이디 등으로 할수있도록 수정해 보기