최근에 진행하는 프로젝트에서 페이스북 연동 로그인을 추가해야되는 작업이 생겼다.


물론 나는 Server side 개발자라서 화면단은 최근 만진적이 별로 없지만, 요새는 만져야 되는일이.... ㅜㅜ 


Github 이나 구글등에서 Source 를 찾아도 보고 샘플도 적용해봤는데 내 스타일과 딱히 맞는게 없었다...


그래서 시험삼아 한번 만들어보기로 했는데... 어차피 이번 프로젝트가 스프링 기반이라 스프링에서 제공하는


spring-social-facebook 을 사용하기로 하였다~


현재 spring-social-facebook 버전은 1.1.0 을 지원하고있는데.... 이게 이상하게 POM 에서 에러가 난다.. ㅠㅠ

(Missing artifact org.springframework.social:spring-social-core)


구글링 결과 1.1.1을 쓰라는 얘기가.... ㅎㄷㄷㄷ 에러가 없어졌다 구글짱! ㅎㅎ

(http://stackoverflow.com/questions/26593799/maven-cannot-find-spring-social)


		<dependency>
			<groupId>org.springframework.social</groupId>
			<artifactId>spring-social-facebook</artifactId>
			<version>1.1.1.RELEASE</version>
		</dependency>


Maven 을 사용하니까 위 구문을 추가해줬다. Maven 짱짱맨 ... 근데 Gradle 도 써보고싶다... Gradle의 경우 아래~


dependencies {
    compile 'org.springframework.social:spring-social-facebook:1.1.1.RELEASE'
}


다른 블로그나 사이트에서는 spring-social-facebook 뿐만 아니라 core도 같이 넣어주는데 왜 여기는 facebook 만 있는지 궁금할 수도 있다... Spring social guide 를 보고 판단내린 결과 core의 경우 Saas 를 이용한 모든 서비스 Connection 을 제공하는 것 같은데... 현재 진행하는 프로젝트에서는 facebook 만 해야되니... 굳이 다 가져다 쓸 필요가 없다고 느꼈다...


실제로 Facebook Data 에 접근하는 guide 를 봐도 (http://spring.io/guides/gs/accessing-facebook/)

core 는 사용하고 있지 않다. spring-social-facebook 만 사용하여 접근할 뿐이다... 


Restful API 개발자로 지내다 보니 효율은 높이고 낭비를 없애는 습관이 생겼는데.... 저런게 바로 낭비제거아닐까...??

안쓰는 라이브러리들은 용량만 차지할뿐 쓸데가 없다고 생각한다....(rest api 인데 war 용량이 너무크다?? 한번쯤 살펴봅시다)


Lib 추가되었으면 이제 인증을 해보도록 하자... Facebook 의 경우 OAuth 방식을 제공하는데 

이번 프로젝트에는 OAuth 2.0 방식으로 사용하였다. OAuth 2.0 방식은 여러 사이트들에 자세히 나와있긴한데


한국사람이다보니 한글로 설명되어있는 링크를 걸자면 Daum 이 적절하게 설명을 잘 해 놓은 것 같다. 

http://dna.daum.net/apis/oauth2 <- 딱히 홍보하는건 아니지만 그래도 설명이 좋다. 어차피 개발자 참고용이니


여하튼 로그인 인증부분으로 넘어가서 얘길하자면 Facebook 에서 개발자 권한 받고 그런것들은 다른사이트에서도 많이 설명하니 바로 소스로 넘어가서 설명을 시작하려한다.


나는 Spring Security 를 사용하지 않으므로 일반적인 코드로 사용하였는데

FacebookConnectionFactory 을 사용해서 요청주소값을 만든다. (id, secret 필요)


만들어진 주소를 호출하게되면 인증을 시작하게되고 사용자의 access token이 발급되는데 이 access token으로 사용자 정보 및 기타 정보를 획득할 수 있다.


@Autowired private FacebookConnectionFactory connectionFactory; @Autowired private OAuth2Parameters oAuth2Parameters; public String getAuthorizeUrl(){ OAuth2Operations oauthOperations = connectionFactory.getOAuthOperations(); String authorizeUrl = oauthOperations.buildAuthorizeUrl(GrantType.AUTHORIZATION_CODE, oAuth2Parameters); return authorizeUrl; }


Service 단에 FacebookConnectionFactory , OAuth2Parameters 부분을 주입시켰다.


<bean id="connectionFactory" class="org.springframework.social.facebook.connect.FacebookConnectionFactory">
	<constructor-arg value="${facebook.app.id}" />
	<constructor-arg value="${facebook.app.secret}" />
</bean>
	
<bean id="oAuth2Parameters" class="org.springframework.social.oauth2.OAuth2Parameters">
	<property name="scope" value="${facebook.app.scope}"/>
	<property name="redirectUri" value="${facebook.redirect.uri}"/>
</bean>


ID, Secret 은 노출시키지 않기위해 Jasypt 를 사용해서 암호화 하였으며 굳이 암호화가 필요 없으신 분들은

그냥 쓰셔도 된다. 

Properties 부분을 보자면


#OAuth2 Property facebook.app.id=ENC(xA/0BZ3ZPCiWWsMtwBQf5lnZRZzf0dKv) facebook.app.secret=ENC(lIHSChJLkqjjMzkqQ9ZISYU1a/rJQSyeJHLOa4clJF3ytsCHR4u2EykZUan0O2OO) # scope can be divided by commas (e.g email, user_about_me, user_birthday, user_hometown ... etc) facebook.app.scope=email facebook.redirect.uri=http://localhost:8080/callback


ENC() 로 감싼 부분은 Jasypt 로 암호화 한 부분이고, 그냥 쓰실분들은 프로퍼티 사용없이 바로 대입하시면 된다.

Scope의 경우 comma (,) 를 사용해서 지정할 수 있다 Scope는 Facebook 의 Permission 을 확인하면 된다.

https://developers.facebook.com/docs/facebook-login/permissions/v2.2?locale=ko_KR


사용자의 access token 이 발급되면 이제 해당값으로 사용자의 여러 정보를 갖고올 수 있다.

facebook 에서 callback 을 호출하게되면 code 값을 받을 수 있는데 

@RequestParam("code") String code 스프링에서는 다음과 같이 해당 값을 받을 수 있다. validation은 굳이 하지 않아도..

저 Code 값을 이용해서 사용자 정보를 획득할 수 있는데 코드로 보면 다음과 같다.


@Value("${facebook.redirect.uri}")

private String redirectUri;


public FacebookUserProfileVO getFacebookUserInfo(String code) { OAuth2Operations oauthOperations = connectionFactory.getOAuthOperations(); AccessGrant accessGrant = oauthOperations.exchangeForAccess(code, redirectUri, null); String accessToken = accessGrant.getAccessToken(); Long expireTime = accessGrant.getExpireTime(); if (expireTime != null && expireTime < System.currentTimeMillis()) { accessToken = accessGrant.getRefreshToken(); logger.info("accessToken is expired. refresh token = {}" , accessToken); } Connection<Facebook> connection = connectionFactory.createConnection(accessGrant); Facebook facebook = connection == null ? new FacebookTemplate(accessToken) : connection.getApi(); UserOperations userOperations = facebook.userOperations(); FacebookProfile facebookProfile = null; try { facebookProfile = userOperations.getUserProfile(); } catch (MissingAuthorizationException e) { e.printStackTrace(); // TO DO throw custom exception... } catch (ApiException e) { e.printStackTrace(); // TO DO throw custom exception... } FacebookUserProfileVO facebookUserProfileVO = new FacebookUserProfileVO( facebookProfile.getId() , facebookProfile.getName() , facebookProfile.getFirstName() , facebookProfile.getLastName()); logger.info("Facebook user login is success. {}", facebookUserProfileVO.toString()); return facebookUserProfileVO; }


FacebookUserProfileVO 부분은 개인적으로 만든 VO Instance 이며, 각자의 필요한 정보대로 만들어도 되고 그때그때 마다

FacebookProfile 에서 호출해와도 된다.


위 코드가 정확하다고 말 할 수는 없다. 왜냐면 나도 처음해보는거라서;;

동작 잘 되고, Exception 발생할 소지가 있는 부분은 try...catch 걸었다 발생하는 Exception 들도 지정하였고...

그러니 별 문제 없을거라고 판단되지만 그래도 수정할 부분이 있으면 댓글로 꼭 알려주세요~


Doc 부분을 보니까 token 은 null값을 리턴하지 않는 것 같다.. 

근데 다른값들은 null 값을 리턴할 수 있다고 하니 null 체크는 필수로 해야되고...

token 값 만료에 대한 부분은 확신이 없어서 refresh 할 수 있도록 설정했다.


만약에 Connection<Facebook> 부분에 null 값이 발생하면 refresh 된 토큰 값을 사용해야 하니...


일단 아직까지 테스트중에 문제는 없었다...


위 코드는 정상동작 위주로 짜여진 코드이며, 만약 인증에러나 기타 에러가 발생하게 되면

facebook 에서 에러코드를 callback url 로 리턴하니 Controller 단에서 해당 Error 를 처리해야 될 듯하다.






+ Recent posts