Skip to content

WPGraphQL, JWT Token을 이용하여 WordPress사이트간의 SSO구현하기

WordPress를 Headless CMS로 사용하고 Front end는 Next.js 프레임워크를 사용하여 웹 서비스를 개발중이다. 여러개의 서비스를 준비중인데 하나의 서비스를 이용하는 것 처럼 통합된 사용자 경험을 위해(사실 로그인, 회원가입 이런거 말고 재밌는거 개발해보고 싶어서) WordPress사이트간의 SSO를 직접 구현해 보려고 한다.

출처 – https://medium.com/twolinecode/sso%EB%9E%80-by-%EA%B9%80%EC%A0%95%EC%84%9D%EB%8B%98-62b5cc0da90d

메인이 되는 사이트에서는 WPGraphQL과 해당 플러그인에서 제공하는 다른 확장 플러그인 wp-graphql-jwt-authentication 를 사용해서 JWT Token 기반의 인증/인가 서비스를 사용하고 있다.

메인 사이트에 사용자의 기본정보들을 모두 저장하고, 하위 서비스를 제공하는 서브 사이트들은 해당 서비스에서 필요한 특정 데이터만 저장하는 식으로 구현하려고 한다.

SSO에 관련된 정보를 많이 찾아읽었는데 아래 글에 굉장히 이해하기 쉽게 쓰여있다.
https://co-no.tistory.com/36

먼저 하위 서비스들은 모두 메인 사이트 도메인의 서브도메인이 추가된 형태의 도메인으로 배포하여 same domain으로 취급받도록 하고 토큰을 담은 쿠키를 서브 도메인 사이트에도 적용되도록 설정하였다.

보안과 네트워크에 지식이 별로 없어서인지, 오히려 SSO를 구현할 수많은 아이디어가 떠올랐다. 내가 생각해도 보안상 별로인 것을 제외하고 구현이 비교적 쉬운 아이디어 중에 아래 두 가지 방식중에 고민하였다.

첫 번째 아이디어

첫 번째는 서브 사이트들마다 해당 서비스를 이용하기 위한 별도의 Token을 발행하여 사용하는 것이었다. 그러면 WPGraphQL의 쿼리들을 재사용하면서 보안적인 측면도 하나의 Token만 사용하거나 모든 사이트들의 JWT secret key를 같게 설정하는 것보다 낫겠다는 생각을 했다.

그런데 문제는 WPGraphQL에서 JWT Token을 발행할때는 오직 user의 id, pw로 기본인증을 통하거나 WP자체적인 로그인 감지 로직을 사용하여 인증이 되어야했다.
물론 Mutation을 개발해서 추가하는 것은 어렵지 않은 일이지만 그렇게 할 경우 WordPress서버와 서드파티 플러그인, 그 플러그인의 확장 플러그인에까지 의존성이 생기게 된다.
그리고 무엇보다 Mutation안에서 메인 사이트와 통신을 통해 token의 유효성을 검증해야 하는데 Cafe24의 웹 호스팅 서비스를 이용해 설치한 Wordrpress의 서버에서는 로그를 확인하고 디버깅하기 쉽지 않다.

두 번째 아이디어

두 번째 방법은 WP REST API를 사용하는 방법이다. WPGraphQL처럼 서드파티 플러그인이 아니라 WordPress의 Core에서 제공하는 공식기능이라는 이점이 있다.
그리고, 해당 API KEY의 권한설정을 통해 관리자 권한으로 데이터들을 조작할 수 있기때문에 사용자가 서비스마다 토큰을 발급받아 인증받을 필요가 없다. 물론 권한이 필요한 작업은 메인사이트와의 통신을 통해 쿠키에 저장된 토큰을 통해 인증/인가를 구현했다.

WPGraphQL에 토큰이 유효한지 검증하는 Query는 없지만 Auth token을 Query하려고 하면 자격증명이 필요하기 때문에 사용자의 서비스고유 id와 함께 token을 불러오는 방식으로 해당 부분을 구현했다.

rest api key는 관리자 권한의 user를 생성한 후 편집에 들어가서 생성할 수 있다.
Screenshot 2023-11-30 at 11.24.36 PM.jpg
사용자가 권한이 필요한 액션(ex. 포스팅 업로드)을 하면 쿠키에 메인 사이트에서 발행한 token이 있는지 확인한다. 없다면 메인사이트의 로그인 페이지로 Redirect 시킨다.

token이 있다면 이게 정말 유효한 토큰인지 확인하기 위해 메인사이트에 Query요청을 보낸다. 해당 요청에는 사용자가 서브사이트에 등록되어있는 사용자인지 확인하기 위해 서브 사이트의 id를 같이 요청한다.

서브사이트의 id를 받아오면 해당 id를 이용해 서브 사이트의 rest api로 사용자가 요청한 작업을 진행한다.

서브 사이트의 id가 없다면 해당 사용자는 서브 사이트에 등록이 되어있지 않으니 서브사이트에 Create User를 이용해 사용자를 생성하고 서브 사이트의 id를 다시 메인사이트의 user정보에 저장한다. 이후 해당 id를 이용해 서브 사이트의 rest api로 사용자가 요청한 작업을 진행한다.

위 과정중에 문제가 생겼을때 한쪽의 DB에만 정보가 저장되면 안되기에 Transactional 해야한다.

쉬운 걸 너무 복잡하게 써놓은 것 같은데 쉽게 말하면,

서브사이트에서 사용자가 작업요청을 하면 메인사이트에서 권한을 확인만 하고, 서브 사이트의 관리자 권한으로 액션을 취해준다.

백엔드 서버를 개발할때 처럼 권한을 확인하고 코드로 DB에 입출력을 하듯이 각 사이트의 관리자 권한을 사용하는 것이다. 관리자 권한을 남용하는 것 같아 좋아보이지 않지만 생각해보면 우선 서브사이트에서 이미 검증된 방식으로 Authentication이 이루어진 후에 백엔드에서 Access control을 하는 개념이라 괜찮을 거라 생각했다.

구현

그러기 위해 메인 사이트의 user에 서브 사이트의 id를 저장할 field를 추가한다.

그리고 WPGraphQL로 요청을 통해 조회 및 생성/수정이 가능하도록 기존 query와 mutation을 확장한다.
https://master–wpgraphql-docs.netlify.app/extending/mutations/

	/**
		* Run an action after the additional data has been updated. This is a great spot to hook into to
		* update additional data related to users, such as setting relationships, updating additional usermeta,
		* or sending emails to Kevin... whatever you need to do with the userObject.
		*
		* @param int $user_id The ID of the user being mutated
		* @param array $input The input for the mutation
		* @param string $mutation_name The name of the mutation (ex: create, update, delete)
		* @param AppContext $context The AppContext passed down the resolve tree
		* @param ResolveInfo $info The ResolveInfo passed down the Resolve Tree
		*/

	add_action( 'graphql_user_object_mutation_update_additional_data', 'graphql_register_user_mutation', 10, 5 );

	function graphql_register_user_mutation( $user_id, $input, $mutation_name, $context, $info ) {
		if ( isset( $input['hobbies'] ) ) {
			// Consider other sanitization if necessary and validation such as which
			// user role/capability should be able to insert this value, etc.
			update_user_meta( $user_id, 'hobbies', $input['hobbies'] );
		}
	}

로그인이 완료되면 snippet을 심어서 cookie에 저장하려고했는데 해당 hook이 작동하는 시점이 헤더에 쿠키를 심을 수 있는 시점 이후여서 정상적으로 스니펫이 작동하지 않았다.
그래서 아래 처럼 만약 로그인된 유저라면 그냥 쿠키를 계속 심어줄 수 있도록 했다.

add_action('wp', 'set_user_login_cookie');

function set_user_login_cookie() {
    if ( is_user_logged_in() ) {
        // 사용자가 로그인한 경우에만 쿠키 설정
        $current_user_id = get_current_user_id();
		$database_id = $current_user_id;
		$global_id = base64_encode($object_type . ':' . $database_id);		
		$domain = ".algorixm.co.kr"; 
		$httpOnly = true; 
		$secure = true;
		$token = "test token"; 
		$expires = time() + ( 86400 * 30 );

		setcookie( "userDatabaseId", $database_id, $expires, "/", $domain, $secure, $httpOnly );
		setcookie( "user_token", $token, $expires, "/", $domain, $secure, $httpOnly );
    }
}

이렇게 하면 따로 갱신로직을 작성할 필요없이 사용자가 로그인된채로 사이트내를 돌아다니면 계속해서 기한이 늘어난다.

Leave a Reply

Your email address will not be published. Required fields are marked *