[Springboot] Oauth2 와 Session 간의 로그인 연계

해니·2023년 2월 16일
post-thumbnail




협력사와 진행했던 프로젝트 내용 중 로그인 연계 부분에 대해 정리해 보려고 한다. 😏
협력사에서는 OAuth 방식을 사내에서는 세션(Session) 방식을 사용하여 로그인 처리를 하는 상황이었다.




1. 로그인 과정


1. 사용자 접속 시도

  • 협력사 사이트 내에서 사내 접속 버튼을 클릭하여 접속을 시도한다.

2. 로그인 요청

3. accessToken 유효성 확인

4. 로그인 처리







🔔 로그인 인증 방식


1) 세션(Session) 방식

  • 특징

    • 세션이란 서버의 컴퓨터(서버에 세션 저장소)에 클라이언트의 정보를 기억하고 유지하는 것이다.
    • 세션은 서비스가 돌아가는 서버 측에 데이터를 저장하고, 세션의 키(혹은 ID라고도 함)값만을 클라이언트 쿠키에 남겨둔다.
    • 브라우저는 필요할 때마다 쿠키에 있는 세션 키값을 이용하여 서버에 저장된 데이터를 사용하게 된다.
  • 장점

    • 개발자 입장에서 구현이 매우 쉽다.
    • 서버에 세션 ID값만 가지고 접근할 수 있고 서버에 저장하기 때문에 접근이 매우 용이하고 관리가 효율적이다.
  • 단점

    • 서버에서 클라이언트의 상태를 모두 유지하게 된다면 서버 컴퓨터의 과부하가 걸릴 수 있다.
    • 서버의 세션을 유지시킬 때 서버의 인스턴스가 여러 개가 된다면, 모든 서버끼리 같은 세션을 공유하여야 하므로 확장이 어렵고 세션 정보 전용 데이터베이스를 만들어야하는 번거로움이 있다.



    개발 서버의 JSessionID들이 충돌되어 계속 로그아웃 되는 현상을 겪기도 했었다.. 🙄




2) Oauth(OpenID Authentication) 방식

  • 특징
    • 인터넷 사용자들이 비밀번호를 제공하지 않고 다른 웹사이트 상의 자신들의 정보에 대해 웹사이트나 애플리케이션의 접근 권한을 부여할 수 있는 공통적인 수단으로서 사용되는, 접근 위임을 위한 개방형 표준
    • 최근엔 Oauth 2.0을 사용하는데 기존에 1.0에 있어서 보안적인 문제가 있어 보안적인 문제를 개선하고 다양한 확장성 기능을 추가한 버전
  • 장점
    • 사용자들에게 외부 계정(Resource Server → Naver , Facebook , Google)을 기반으로 간편히 회원가입 클릭 한 번으로 간편하게 로그인할 수 있음
    • 연동되는 외부 웹 어플리케이션에서 제공하는 기능을 간편하게 사용할 수 있음
  • 단점
    • 개발자 입장에서 구현이 가장 복잡한 로그인 구현 방식
    • 사용자 인증 과정에서 웹서비스에 맞는 최소한의 사용자 정보를 얻기 위해 추가 요청을 해야 하는 경우가 있음





1. 로그인 요청

  • 협력사에서 사내로 로그인을 요청한다.
  • 쿼리 스트링 방식으로 accessToken를 전달 받는다.

@RequestMapping(value = "/test_login", produces="application/json;charset=UTF-8")
	public ModelAndView test_login(@RequestParam Map<String, String> queryParameters, HttpServletResponse resp, HttpServletRequest req) throws Exception{

        Map<String, String> returnMap = new HashMap<String, String>();

        returnMap.put("accessToken", queryParameters.get("accessToken"));   // 쿼리 스트링 방식으로 데이터 전달 받기 

        String jsonStr = gson.toJson(returnMap);	// Object(map) -> json String

    }




2. accessToken 유효성 확인

  • accessToken 유효성 Request
    • 전달 받은 accessToken이 유효한지 협력사측으로 요청을 보낸다.
  • 유효성 결과 Response
    • 응답 결과가 true일 경우, 사내에서 로그인을 진행한다.

@RequestMapping(value = "/test_login", produces="application/json;charset=UTF-8")
	public ModelAndView test_login(@RequestParam Map<String, String> queryParameters, HttpServletResponse resp, HttpServletRequest req) throws Exception{


        // 1. 로그인 요청

        Map<String, Object> return_map = new HashMap<>();
		StringBuilder sb = new StringBuilder();
		Map<String, String> returnMap = new HashMap<String, String>();
		URL url = null;
		HttpURLConnection conn = null;
		OutputStreamWriter os = null;
        BufferedReader br = null;

        returnMap.put("accessToken", queryParameters.get("accessToken"));   // 쿼리 스트링 방식으로 데이터 전달 받기 

        String jsonStr = gson.toJson(returnMap);	// Object(map) -> json String

        try {

            //  2. accessToken 유효성 확인

            url = new URL("https://www.haeni.co.kr/api/v1/auth");
            conn = (HttpURLConnection) url.openConnection();

            conn.setRequestMethod("POST");
    		conn.setRequestProperty("Content-Type", "application/json");
    		conn.setDoOutput(true);


        } catch (MalformedURLException e) {
			log.error("error msg : The URL address in incorrect!");
			
		} catch (IOException e) {
			log.error("error msg : it can't connect to the web page!");
			
        }


		BufferedReader br = null;
		
		try {
			
			int responseCode = conn.getResponseCode();
			
			if( responseCode >= HttpURLConnection.HTTP_OK && responseCode < HttpURLConnection.HTTP_BAD_REQUEST) {	// 정상 호출
				
				br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
				
			} else {	// 에러 발생
				
				br = new BufferedReader(new InputStreamReader(conn.getErrorStream(), "UTF-8"));
				
			}
			
			String line = "";
			
			while ((line = br.readLine()) != null) {
				sb.append(line.trim());
			}

			return_map = gson.fromJson(sb.toString(), new TypeToken<HashMap<String, Object>>(){}.getType()); 

			
		} catch (Exception e) {
			
			log.info("err :" + sb.toString());
			
			return_map.put("result", false);
			
			
		} finally {

			os.close();
			br.close();
			conn.disconnect();
			
		}


        return new ModelAndView("jsp/test/auth").addAllObjects(return_map);

    }





4. 로그인 처리

  • 응답 데이터 기준으로 그룹 조회 후 , 해당 그룹이 존재하면 로그인 처리를 한다.


// auth.jsp


<script type="text/javascript" src="/resource/js/jquery.min.js"></script>
<script type="text/javascript">

$(document).ready(function() {

    if("${result}"=='true'){
		
		let json = {
			'result': "${result}"
			,'id': "${id}"
			,'dexpire': "${dexpire}"
			,'status': "${status}"
		};
		
		check_id_branch(json)
		
	} else {
		
		alert("오류가 발생했습니다.");
		location.href="https://haeni.co.kr" // 협력사 홈페이지로 이동 
		
	}

});



function check_id_branch(params) { //	응답 데이터 기준으로 그룹 조회
		$.ajax({
			method : "POST",
			url : "/checkId",
			data : JSON.stringify(params),
			dataType : 'json',
			contentType : 'Content-Type: application/json',
			beforeSend : function(xhr) {
				xhr.setRequestHeader("Content-type", "application/json");
			},
			success : function(data) {
				if (data.status == '000') {
					// 로그인 시도 ... 
					on_login(data)
				} else { //if(data.status=='-100')
					alert(data.msg);
					location.href="https://haeni.co.kr"
				}
			},
			error : function(jqXHR, textStatus, errorThrown) {
				alert("오류가 발생했습니다.");
				location.href="https://haeni.co.kr"
			}
		});
    }

function on_login() {
	
	var sendData = document.sendData;
	sendData.action = '/login/open?from=haeni';
	sendData.submit();	
}







출처
https://choi-notion-blog.vercel.app/post/%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EA%B5%AC%ED%98%84-%EB%B0%A9%EC%8B%9D(JWT-,-Session-,-OAuth-2.0)-eda48fe7582440529050d5f00e721c80
https://inpa.tistory.com/entry/WEB-%F0%9F%93%9A-OAuth-20-%EA%B0%9C%EB%85%90-%F0%9F%92%AF-%EC%A0%95%EB%A6%AC





profile
💻 ⚾️ 🐻 이전했어요..! ➡️ https://dev-haeni.tistory.com/

0개의 댓글