아래 내용을 정리한다. 다만 여기선 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()
댓글 없음:
댓글 쓰기