Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/azure/11.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
我应该如何在Django应用程序中使用AAD实现用户SSO(使用Django Microsoft身份验证后端模块)?_Django_Azure_Azure Active Directory_Single Sign On - Fatal编程技术网

我应该如何在Django应用程序中使用AAD实现用户SSO(使用Django Microsoft身份验证后端模块)?

我应该如何在Django应用程序中使用AAD实现用户SSO(使用Django Microsoft身份验证后端模块)?,django,azure,azure-active-directory,single-sign-on,Django,Azure,Azure Active Directory,Single Sign On,我正在开发一个Django(2.2.3)应用程序,安装了Azure AD来处理SSO。我能够按照快速启动文档的要求,使用我的Microsoft标识或添加到Django用户表中的标准用户名和密码登录到Django管理面板。这一切都是现成的,很好 我的问题(真的)很简单,就是“我下一步做什么?”。从用户的角度来看,我希望他们: 导航到我的应用程序(example.com/或example.com/content)-Django会意识到它们没有经过身份验证,而且 自动将它们重定向到同一窗口中的SSO

我正在开发一个Django(2.2.3)应用程序,安装了Azure AD来处理SSO。我能够按照快速启动文档的要求,使用我的Microsoft标识或添加到Django用户表中的标准用户名和密码登录到Django管理面板。这一切都是现成的,很好

我的问题(真的)很简单,就是“我下一步做什么?”。从用户的角度来看,我希望他们:

  • 导航到我的应用程序(example.com/或example.com/content)-Django会意识到它们没有经过身份验证,而且
    • 自动将它们重定向到同一窗口中的SSO门户,或
    • 将他们重定向到example.com/login,这要求他们单击将打开SSO的按钮 窗口中的门户(这是默认管理情况下发生的情况)
  • 允许他们登录并使用其Microsoft帐户的MFA
  • 成功后,将其重定向到我的
    @login\u所需的
    页面(example.com/content)
  • 目前,在我的导航(example.com/)的根目录中,我有以下内容:

        def index(request):
            if request.user.is_authenticated:
                return redirect("/content")
            else:
                return redirect("/login")
    
    我最初的想法是简单地将
    重定向(“/login”)
    更改为
    重定向(授权url)
    ——这就是我的问题开始的地方

    据我所知,没有任何方法可以获取上下文处理器的当前实例(?)或
    microsoft\u auth
    插件的后端来调用
    authorization\u url()
    函数并从
    views.py
    重定向用户

    好的。。。然后我想我应该实例化生成authURL的
    MicrosoftClient
    类。这不起作用-不是100%确定原因,但它认为这可能与后端/上下文处理器上实际的
    MicrosoftClient
    实例使用的某些状态变量与我的实例不一致有关

    最后,我尝试模拟自动
    /admin
    页面所做的操作—为用户提供一个SSO按钮供其单击,并在单独的窗口中打开Azure门户。在仔细研究之后,我意识到我基本上也有同样的问题——身份验证URL作为内联JS传递到管理员登录页面模板中,该模板稍后用于在客户端异步创建Azure窗口

    作为一项健全性检查,我尝试手动导航到管理员登录页面中显示的auth URL,这确实起了作用(尽管重定向到
    /content
    没有起作用)

    在这一点上,考虑到我认为我是在为自己制造困难,我觉得我在以完全错误的方式处理这整件事。遗憾的是,我找不到任何关于如何完成这部分过程的文档


    那么,我做错了什么

    再过几天,我终于自己解决了这些问题,并进一步了解了Django的工作原理

    我缺少的链接是来自(第三方)Django模块的上下文处理器如何/在何处将其上下文传递到最终呈现的页面。我没有意识到,默认情况下,我也可以在任何模板中访问microsoft_auth包中的变量(例如模板中使用的
    授权\u url
    )。知道了这一点,我能够实现一个稍微简单的版本,这个版本与管理面板使用的基于JS的登录过程相同

    假设将来读到这篇文章的人都经历了与我相同的(学习)过程(特别是这个软件包),我可能会猜到下面几个问题

    第一个是“我已成功登录…我如何代表用户做任何事情?!”。有人会假设您将获得用户的访问令牌以用于将来的请求,但在编写此包时,默认情况下似乎没有以任何明显的方式完成。软件包的文档只能让您登录到管理面板

    (在我看来,不是很明显)答案是您必须将
    MICROSOFT\u AUTH\u AUTHENTICATE\u HOOK
    设置为一个可以在成功身份验证时调用的函数。它将被传递给登录用户(模型)和他们的令牌JSON对象,供您随意使用。经过深思熟虑,我选择使用
    AbstractUser
    扩展我的用户模型,并将每个用户的令牌与其他数据一起保留

    models.py

    class User(AbstractUser):
        access_token = models.CharField(max_length=2048, blank=True, null=True)
        id_token = models.CharField(max_length=2048, blank=True, null=True)
        token_expires = models.DateTimeField(blank=True, null=True)
    
    aad.py

    from datetime import datetime
    from django.utils.timezone import make_aware
    
    def store_token(user, token):
        user.access_token = token["access_token"]
        user.id_token = token["id_token"]
        user.token_expires = make_aware(datetime.fromtimestamp(token["expires_at"]))
        user.save()
    
    设置.py

    MICROSOFT_AUTH_EXTRA_SCOPES = "User.Read"
    MICROSOFT_AUTH_AUTHENTICATE_HOOK = "django_app.aad.store_token"
    
    请注意
    MICROSOFT\u AUTH\u EXTRA\u SCOPES
    设置,这可能是您的第二个/次要问题-包中的默认范围设置为
    SCOPE\u MICROSOFT=[“openid”、“email”、“profile”]
    ,如何添加更多内容并不明显。我需要添加
    用户。至少要阅读
    。请记住,该设置需要一个以空格分隔的作用域字符串,而不是一个列表


    一旦您拥有了访问令牌,就可以自由地向Microsoft Graph API发出请求。它们在这方面非常有用。

    因此我在Django中基于。 希望这能帮助一些人

    import logging
    import uuid
    from os import getenv
    
    import msal
    import requests
    from django.http import JsonResponse
    from django.shortcuts import redirect, render
    from rest_framework.generics import ListAPIView
    
    logging.getLogger("msal").setLevel(logging.WARN)
    
    # Application (client) ID of app registration
    CLIENT_ID = "<appid of client registered in AD>"
    
    TENANT_ID = "<tenantid of AD>"
    
    CLIENT_SECRET = getenv("CLIENT_SECRET")
    
    AUTHORITY = "https://login.microsoftonline.com/" + TENANT_ID
    
    # This resource requires no admin consent
    GRAPH_ENDPOINT = 'https://graph.microsoft.com/v1.0/me'
    
    SCOPE = ["User.Read"]
    
    LOGIN_URI = "https://<your_domain>/login"
    
    # This is registered as a redirect URI in app registrations in AD
    REDIRECT_URI = "https://<your_domain>/authorize"
    
    
    class Login(ListAPIView):
        '''initial login
        '''
    
        def get(self, request):
            session = request.session
    
            id_token_claims = get_token_from_cache(session, SCOPE)
            if id_token_claims:
                access_token = id_token_claims.get("access_token")
    
                if access_token:
                    graph_response = microsoft_graph_call(access_token)
    
                    if graph_response.get("error"):
                        resp = JsonResponse(graph_response, status=401)
    
                    else:
                        resp = render(request, 'API_AUTH.html', graph_response)
    
                else:
                    session["state"] = str(uuid.uuid4())
                    auth_url = build_auth_url(scopes=SCOPE, state=session["state"])
                    resp = redirect(auth_url)
            else:
                session["state"] = str(uuid.uuid4())
                auth_url = build_auth_url(scopes=SCOPE, state=session["state"])
                resp  = redirect(auth_url)
    
            return resp
    
    
    class Authorize(ListAPIView):
        '''authorize after login
        '''
    
        def get(self, request):
            session = request.session
    
            # If states don't match login again
            if request.GET.get('state') != session.get("state"):
                return redirect(LOGIN_URI)
    
            # Authentication/Authorization failure
            if "error" in request.GET:
                return JsonResponse({"error":request.GET.get("error")})
    
            if request.GET.get('code'):
                cache = load_cache(session)
                result = build_msal_app(cache=cache).acquire_token_by_authorization_code(
                    request.GET['code'],
                    # Misspelled scope would cause an HTTP 400 error here
                    scopes=SCOPE,
                    redirect_uri=REDIRECT_URI
                )
    
                if "error" in result:
                    resp = JsonResponse({"error":request.GET.get("error")})
                else:
                    access_token = result["access_token"]
    
                    session["user"] = result.get("id_token_claims")
                    save_cache(session, cache)
    
                    # Get user details using microsoft graph api call
                    graph_response = microsoft_graph_call(access_token)
    
                    resp = render(request, 'API_AUTH.html', graph_response)
    
            else:
                resp = JsonResponse({"login":"failed"}, status=401)
    
            return resp
    
    
    def load_cache(session):
        '''loads from msal cache
        '''
        cache = msal.SerializableTokenCache()
        if session.get("token_cache"):
            cache.deserialize(session["token_cache"])
        return cache
    
    def save_cache(session,cache):
        '''saves to msal cache
        '''
        if cache.has_state_changed:
            session["token_cache"] = cache.serialize()
    
    def build_msal_app(cache=None, authority=None):
        '''builds msal cache
        '''
        return msal.ConfidentialClientApplication(
            CLIENT_ID, authority=authority or AUTHORITY,
            client_credential=CLIENT_SECRET, token_cache=cache)
    
    def build_auth_url(authority=None, scopes=None, state=None):
        '''builds auth url per tenantid
        '''
        return build_msal_app(authority=authority).get_authorization_request_url(
            scopes or [],
            state=state or str(uuid.uuid4()),
            redirect_uri=REDIRECT_URI)
    
    def get_token_from_cache(session, scope):
        '''get accesstoken from cache
        '''
        # This web app maintains one cache per session
        cache = load_cache(session)
        cca = build_msal_app(cache=cache)
        accounts = cca.get_accounts()
        # So all account(s) belong to the current signed-in user
        if accounts:
            result = cca.acquire_token_silent(scope, account=accounts[0])
            save_cache(session, cache)
            return result
    
    def microsoft_graph_call(access_token):
        '''graph api to microsoft
        '''
        # Use token to call downstream service
        graph_data = requests.get(
            url=GRAPH_ENDPOINT,
            headers={'Authorization': 'Bearer ' + access_token},
            ).json()
    
        if "error" not in graph_data:
            return {
                "Login" : "success",
                "UserId" : graph_data.get("id"),
                "UserName" : graph_data.get("displayName"),
                "AccessToken" : access_token
                }
    
        else:
            return {"error" : graph_data}
    
    导入日志
    导入uuid
    从操作系统导入getenv
    进口msal
    导入请求
    从django.http导入JsonResponse
    从django.shortcuts导入重定向,渲染
    从rest\u framework.generics导入ListAPIView
    logging.getLogger(“msal”).setLevel(logging.WARN)
    #应用程序注册的应用程序(客户端)ID
    客户_ID=“”
    租户_ID=“”
    CLIENT\u SECRET=getenv(“CLIENT\u SECRET”)
    权威=”https://login.microsoftonline.com/“+租户ID
    #此资源不需要管理员同意
    图https://graph.microsoft.com/v1.0/me'
    作用域=[“User.Read”]
    登录\u URI=”https:///login"
    #这在AD中的应用程序注册中注册为重定向URI
    重定向_URI=”https:///authorize"
    类登录(ListAPIView):
    ''初始登录
    '''
    def get(自我,请求):
    会话=r