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 일 때 동작하도록 만들어본 것이다.
- 이제 다른 아이디 등으로 할수있도록 수정해 보기
'국비교육(22-23)' 카테고리의 다른 글
[Android Studio] 모듈 의존성 제거 (모듈 삭제) (0) | 2023.02.07 |
---|---|
82일차(2)/Android App(45) : Interceptor 활용하기 (0) | 2023.02.07 |
80일차(1)/Android App(43) : 로그인 기능 구현 (0) | 2023.02.03 |
79일차(1)/Android App(42) : Http 요청으로 Oracle DB 출력하기 (0) | 2023.02.02 |
78일차(1)/Android App(41) : Http POST방식 요청하기(4) - JSONArray 활용 (1) | 2023.01.30 |