아래 내용을 정리한다. 다만 여기선 Django 1.9 를 사용한다.
Tutorial: Adding Facebook/Twitter/Google Authentication to a Django Application - Art & Logic
위의 글에서 python-social-auth 의 사용법을 이야기 해준다.
- https://github.com/omab/python-social-auth
- Welcome to Python Social Auth’s documentation! — Python Social Auth documentation
설치 및 설정
절차
- login 관련 application 만들기
- manage.py startapp thirdlogin
- INSTALLED_APPS 에 추가('thirdlogin')
- login view 만들기, url 설정
- python-social-auth 설치 및 설정
- pip install python-social-auth
- settings 설정
- manage.py syncdb
위의 내용중에 설명이 필요한 것들만 아래 세부적인 사항을 적어놓았다.
login view 만들기
from django.template.context_processors import csrf from django.views.generic import TemplateView class HomeView(TemplateView): template_name = "third-base.html" def get(self, request, *args, **kwargs): context = self.get_context_data(**kwargs) context.update(csrf(request)) context.update({'user': request.user}) return self.render_to_response(context) def get_context_data(self, **kwargs): context = super(HomeView, self).get_context_data(**kwargs) return context
login view url 설정
url(r'^home/$', HomeView.as_view(), name='home'),
python-social-auth 의 settings 설정
INSTALLED_APPS = ( ... 'social.apps.django_app.default', ... ) TEMPLATES = [{ ... 'OPTIONS': { 'debug': DEBUG, # see : https://docs.djangoproject.com/en/1.8/ref/templates/upgrading/ 'context_processors': [ 'django.contrib.auth.context_processors.auth', 'django.template.context_processors.debug', 'django.template.context_processors.i18n', 'django.template.context_processors.media', 'django.template.context_processors.static', 'django.template.context_processors.tz', 'django.contrib.messages.context_processors.messages', 'django.template.context_processors.request', 'social.apps.django_app.context_processors.backends', 'social.apps.django_app.context_processors.login_redirect', ], ... }, }] AUTHENTICATION_BACKENDS = [ 'social.backends.facebook.FacebookOAuth2', 'social.backends.google.GoogleOAuth2', 'social.backends.twitter.TwitterOAuth', 'django.contrib.auth.backends.ModelBackend', ] SOCIAL_AUTH_FACEBOOK_KEY = "4891384931804" SOCIAL_AUTH_FACEBOOK_SECRET = "348920vdfds715d9fc2ce38154"
url 추가
여기서 auth 는 django 의 기본 logout 을 사용하기 위한 용도이다.urlpatterns = patterns('', ... url('', include('social.apps.django_app.urls', namespace='social')), url('', include('django.contrib.auth.urls', namespace='auth')), ... )
social.apps.django_app.urls
urls 부분만 조금 수정했다. 내용은 같지만, warning 만 줄인것이다.social.apps.dango_app.urls.py
from social.apps.django_app import views urlpatterns = [ url(r'^login/(?P<backend>[^/]+){0}$'.format(extra), views.auth, name='begin'), url(r'^complete/(?P<backend>[^/]+){0}$'.format(extra), views.complete, name='complete'), url(r'^disconnect/(?P<backend>[^/]+){0}$'.format(extra), views.disconnect, name='disconnect'), url(r'^disconnect/(?P<backend>[^/]+)/(?P<association_id>[^/]+){0}$' .format(extra), views.disconnect, name='disconnect_individual'), ]
template
template 에서는 next 에 들어가는 부분이 login 후, 또는 logout 후에 redirect 되는 url 이 된다. next 가 설정되어 있지 않으면 LOGIN_REDIRECT_URL 를 사용한다.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>{% block title %}Third-party Authentication Tutorial{% endblock %}</title> <!-- Bootstrap --> <link href="/static/css/bootstrap.min.css" rel="stylesheet"> <link href="/static/css/bootstrap-theme.min.css" rel="stylesheet"> <link href="/static/css/fbposter.css" rel="stylesheet"> <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries --> <!-- WARNING: Respond.js doesn't work if you view the page via file:// --> <!--[if lt IE 9]> <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script> <script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script> <![endif]--> </head> <body> <div> <h1>Third-party authentication demo</h1> <p> {% if user and not user.is_anonymous %} Hello {{ user.get_full_name|default:user.username }}! <a href="{% url 'auth:logout' %}?next={{ request.path }}">Logout</a><br> {% else %} I don’t think we’ve met before. <a href="{% url 'social:begin' 'facebook' %}?next={{ request.path }}">Login with Facebook</a> {% endif %} </p> </div> <!-- jQuery (necessary for Bootstrap's JavaScript plugins) --> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script> <!-- Include all compiled plugins (below), or include individual files as needed --> <script src="/static/js/bootstrap.min.js"></script> </body> </html>
pipeline
이 django social log in library(이하 psa) 에서는 pipeline 개념을 아는 것이 필요하다.여기서 pipeline 은 function 들의 list 정도로 이해하면 될 것이다. psa 는 login 요청시에 이 pipeline 에 적혀있는 모든 function 들을 수행하게 된다.
그리고 이 function 들은 {}(dict) 나 None 을 return 한다. return 된 값은 이전 function 의 return 값과 merge 해서 다음 pipeline 에 parameter 로 넘어가게 된다.
여기서 pipeline 개념을 좀 더 명확히 알 수 있다.
만약 내가 login 과정중에 어떤 동작을 추가하고 싶다면, 내가 만든 function 을 이 pipeline list 의 원하는 위치에 추가해 주면 된다.
이 pipeline 이 끝나야 로그인이 완료된다. pipeline 중간에 user 가 이미 로그인 됐다는 것은 pipeline 시작시점에 이미 로그인 됐다는 것을 이야기한다.
pipline 에 email 인증 추가
facebook 의 scope 정의
Docs re: facebook extended permissions need updating · Issue #150 · omab/django-social-auth · GitHub
FACEBOOK_AUTH_EXTRA_ARGUMENTS= {'scope': 'user_location,user_about_me,email'}
위의 것도 아니고, 아래 것이 맞다. 2016-01-19
SOCIAL_AUTH_FACEBOOK_SCOPE = ['email'] SOCIAL_AUTH_FACEBOOK_PROFILE_EXTRA_PARAMS = { 'fields': 'id,name,email', }
SCOPE / PROFILE_EXTRA_PARAMS 로 검색하면 facebook.py 에서 찾을 수 있다.
위에서 설정된 param 의 field 는 아래 login flow > complete 에서 self.get_json() 에서 확인 할 수 있다.
login flow
login 버튼을 누를때 아래 url 을 호출하게 한다. next 에는 login 이후에 갈 url 을 적으면 된다.
- /login/facebook/?next=/thirdauth/home/
- https://www.facebook.com/v2.3/dialog/oauth?state=...&redirect_uri=...&client_id=...
- /complete/facebook/?redirect_state=...&code=...&state=...
user 정보 db 저장
아래 do_complete 에 보면 user model 에 대한 작업이 있다. 이 부분에서 DB의- table social_auth_usersocialauth
auth
social.apps.django_app.urls url(r'^login/(?P<backend>[^/]+){0}$'.format(extra), views.auth, name='begin'),
social.apps.django_app.views def auth(request, backend): return do_auth(request.backend, redirect_name=REDIRECT_FIELD_NAME)
social.actions def do_auth(backend, redirect_name='next'): # Clean any partial pipeline data backend.strategy.clean_partial_pipeline() # Save any defined next value into session data = backend.strategy.request_data(merge=False) ... # data 에 parameter 값들이 있다. if redirect_name in data: # Check and sanitize a user-defined GET/POST next field value redirect_uri = data[redirect_name] # 같은 host 의 redirect_uri 인지 검사 if backend.setting('SANITIZE_REDIRECTS', True): redirect_uri = sanitize_redirect(backend.strategy.request_host(), redirect_uri) backend.strategy.session_set( redirect_name, redirect_uri or backend.setting('LOGIN_REDIRECT_URL') ) return backend.start()
social.strategies
class DjangoStrategy(BaseStrategy):
...
def session_set(self, name, value):
self.session[name] = value
if hasattr(self.session, 'modified'):
self.session.modified = True
social.backends.base class BaseOAuth2(OAuthAuth): ... def start(self): #class BaseAuth(object): # Clean any partial pipeline info before starting the process self.strategy.clean_partial_pipeline() if self.uses_redirect(): # self.auth_url() -> 'https://www.facebook.com/v2.3/dialog/oauth?state=xrCXEWKYc2d5TwJnxanAnePJ7gKIL6xt&redirect_uri=http%3A%2F%2Flocal.com%3A9080%2Fcomplete%2Ffacebook%2F%3Fredirect_state%3DxrCXEWKYc2d5TwJnxanAnePJ7gKIL6xt&client_id='121353' return self.strategy.redirect(self.auth_url()) else: return self.strategy.html(self.auth_html())
social.strategies class DjangoStrategy(BaseStrategy): def redirect(self, url): return redirect(url)
django.shortcuts def redirect(to, *args, **kwargs): if kwargs.pop('permanent', False): redirect_class = HttpResponsePermanentRedirect else: redirect_class = HttpResponseRedirect return redirect_class(resolve_url(to, *args, **kwargs))
complete
social.apps.django_app.urls
url(r'^complete/(?P<backend>[^/]+){0}$'.format(extra), views.complete, name='complete'),
social.apps.django_app.views @never_cache @csrf_exempt @psa('{0}:complete'.format(NAMESPACE)) def complete(request, backend, *args, **kwargs): """Authentication complete view""" return do_complete(request.backend, _do_login, request.user, redirect_name=REDIRECT_FIELD_NAME, *args, **kwargs)
social.actions def do_complete(backend, login, user=None, redirect_name='next', *args, **kwargs): data = backend.strategy.request_data() # data = {'code': '...', 'redirect_state': '...', 'state': '...'} is_authenticated = user_is_authenticated(user) user = is_authenticated and user or None partial = partial_pipeline_data(backend, user, *args, **kwargs) if partial: ... else: user = backend.complete(user=user, *args, **kwargs) ... user_model = backend.strategy.storage.user.user_model() if user and not isinstance(user, user_model): return user if is_authenticated: ... elif user: if user_is_active(user): ... ... if backend.setting('SANITIZE_REDIRECTS', True): url = sanitize_redirect(backend.strategy.request_host(), url) or \ backend.setting('LOGIN_REDIRECT_URL') return backend.strategy.redirect(url)
social.backends.base class FacebookOAuth2(BaseAuth2): def complete(self, *args, **kwargs): # class BaseAuth(object): return self.auth_complete(*args, **kwargs) @handle_http_errors def auth_complete(self, *args, **kwargs): ... state = self.validate_state() key, secret = self.get_key_and_secret() response = self.request(self.ACCESS_TOKEN_URL, params={ 'client_id': key, 'redirect_uri': self.get_redirect_uri(state), 'client_secret': secret, 'code': self.data['code'] }) # API v2.3 returns a JSON, according to the documents linked at issue # #592, but it seems that this needs to be enabled(?), otherwise the # usual querystring type response is returned. try: response = response.json() except ValueError: response = parse_qs(response.text) access_token = response['access_token'] return self.do_auth(access_token, response, *args, **kwargs) def request(self, url, method='GET', *args, **kwargs): # class BaseAuth(object): kwargs.setdefault('headers', {}) ... #... set kwargs try: if self.SSL_PROTOCOL: session = SSLHttpAdapter.ssl_adapter_session(self.SSL_PROTOCOL) response = session.request(method, url, *args, **kwargs) else: response = request(method, url, *args, **kwargs) except ConnectionError as err: raise AuthFailed(self, str(err)) response.raise_for_status() return response
backends.facebook def do_auth(self, access_token, response=None, *args, **kwargs): ... data = self.user_data(access_token) ... data['access_token'] = access_token ... kwargs.update({'backend': self, 'response': data}) return self.strategy.authenticate(*args, **kwargs) def authenticate(self, backend, *args, **kwargs): kwargs['strategy'] = self kwargs['storage'] = self.storage kwargs['backend'] = backend return authenticate(*args, **kwargs) # from django.contrib.auth import authenticate def user_data(self, access_token, *args, **kwargs): """Loads user data from service""" ... return self.get_json(self.USER_DATA_URL, params=params) def get_json(self, url, *args, **kwargs): return self.request(url, *args, **kwargs).json()
댓글 없음:
댓글 쓰기