如何使用 Azure AD 对 VueJS 应用程序进行身份验证?

Posted

技术标签:

【中文标题】如何使用 Azure AD 对 VueJS 应用程序进行身份验证?【英文标题】:How do you authenticate a VueJS app with Azure AD? 【发布时间】:2017-11-10 06:45:41 【问题描述】:

我正在使用 VueJS 2.x 框架设置应用程序,它需要通过 Azure Active Directory 服务对用户进行身份验证。我已经有服务所需的“登录信息”(身份验证和令牌 URL)。

到目前为止,我只遇到过one article,它显示了 VueJS 中的设置,但它依赖于第三方服务 (Auth0) - 在此过程中添加了不必要的卷积。

当aren't any VueJS npm modules 允许轻松进行身份验证时,您将如何进行?还是必须依赖 Vue 之外的库,例如 Adal JS?

任何建议都会有所帮助。

【问题讨论】:

如果您托管在 Azure 应用服务中,则可以使用 EasyAuth:docs.microsoft.com/en-us/azure/app-service-mobile/…。这几乎就是 Authentication-as-a-Service(我不会缩写),因为您不必为它编写代码,它会在请求到达您的应用程序之前发生。 【参考方案1】:

为了解决这个问题,我依靠 ADAL JS。我已经提供了一个 Vue + Vue-Router 示例应用程序here - 但我将在下面包含重要的部分。

在你的 package.json 中:

"dependencies": 
    "adal-angular": "^1.0.15",
    "vue": "^2.5.2",
    "vue-router": "^3.0.1"
,

ADAL JS 库的基本包装模块:

import AuthenticationContext from 'adal-angular/lib/adal.js'

const config = 
  tenant: 'your aad tenant',
  clientId: 'your aad application client id',
  redirectUri: 'base uri for this application',
  cacheLocation: 'localStorage'
;

export default 
  authenticationContext: null,
  /**
   * @return Promise
   */
  initialize() 
    this.authenticationContext = new AuthenticationContext(config);

    return new Promise((resolve, reject) => 
      if (this.authenticationContext.isCallback(window.location.hash) || window.self !== window.top) 
        // redirect to the location specified in the url params.
        this.authenticationContext.handleWindowCallback();
      
      else 
        // try pull the user out of local storage
        let user = this.authenticationContext.getCachedUser();

        if (user) 
          resolve();
        
        else 
          // no user at all - go sign in.
          this.signIn();
        
      
    );
  ,
  /**
   * @return Promise.<String> A promise that resolves to an ADAL token for resource access
   */
  acquireToken() 
    return new Promise((resolve, reject) => 
      this.authenticationContext.acquireToken('<azure active directory resource id>', (error, token) => 
        if (error || !token) 
          return reject(error);
         else 
          return resolve(token);
        
      );
    );
  ,
  /**
   * Issue an interactive authentication request for the current user and the api resource.
   */
  acquireTokenRedirect() 
    this.authenticationContext.acquireTokenRedirect('<azure active directory resource id>');
  ,
  /**
   * @return Boolean Indicates if there is a valid, non-expired access token present in localStorage.
   */
  isAuthenticated() 
    // getCachedToken will only return a valid, non-expired token.
    if (this.authenticationContext.getCachedToken(config.clientId))  return true; 
    return false;
  ,
  /**
   * @return An ADAL user profile object.
   */
  getUserProfile() 
    return this.authenticationContext.getCachedUser().profile;
  ,
  signIn() 
    this.authenticationContext.login();
  ,
  signOut() 
    this.authenticationContext.logOut();
  

在应用程序的入口点(如果您使用 vue-cli,则为 main.js):

import Vue from 'vue'
import App from './App'
import router from './router'
import authentication from './authentication'

// Init adal authentication - then create Vue app.
authentication.initialize().then(_ => 
  /* eslint-disable no-new */
  new Vue(
    el: '#app',
    router,
    template: '<App/>',
    components:  App 
  );
);

对于您的 Vue 路由器配置:

import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
import authentication from '../authentication'

Vue.use(Router)

const router = new Router(
  mode: 'history',
  routes: [
    
      path: '/',
      name: 'HelloWorld',
      component: HelloWorld,
      meta: 
        requiresAuthentication: true
      
    
  ]
)

// Global route guard
router.beforeEach((to, from, next) => 
  if (to.matched.some(record => record.meta.requiresAuthentication)) 
    // this route requires auth, check if logged in
    if (authentication.isAuthenticated()) 
      // only proceed if authenticated.
      next();
     else 
      authentication.signIn();
    
   else 
    next();
  
);

export default router;

在你的 Vue 组件中:

import authentication from './authentication'
...
computed: 
  isAuthenticated() 
    return authentication.isAuthenticated();
  
,
methods: 
  logOut() 
    authentication.signOut();
  

将访问令牌添加到请求标头

以下是 vue-resource http 拦截器的示例,但任何方法都可以。

Vue.http.interceptors.push(function (request, next) 
  auth.acquireToken().then(token => 
    // Set default request headers for every request
    request.headers.set('Content-Type', 'application/json');
    request.headers.set('Ocp-Apim-Subscription-Key', 'api key');
    request.headers.set('Authorization', 'Bearer ' + token)
    // continue to next interceptor
    next();
  );
);

希望这可以节省一些时间:)

【讨论】:

除了使用 http(而不是推荐的 Axios)之外,这也得到了支持。它与我们使用的非常接近(尽管有些不同) 我理解您使用的是隐式授权是否正确? 在您的第二个代码块中,您有 &lt;azure active directory resource id&gt; 这是什么,因为 Azure 中没有带有该标签的内容? 嗨,马特,我能够使用您的示例通过 adal 对 azure AD 进行身份验证,现在我需要调用我在下面的代码块中查看的 REST api,什么是“资源 ID”意义?它是来自 AD webApi 的 clientID 吗? acquireToken() return new Promise((resolve, reject) => this.authenticationContext.acquireToken('resource id', (error, token) => if (error || !token) return reject(error); 其他 返回解析(令牌); ); ); ,【参考方案2】:

免责声明:我是这个插件的作者。

通过 npm 使用vue-adal:

npm install vue-adal

基本用法

import Adal from 'vue-adal'
Vue.use(Adal, 
// This config gets passed along to Adal, so all settings available to adal can be used here.
  config: 
    // 'common' (multi-tenant gateway) or Azure AD Tenant ID
    tenant: '<guid>',

    // Application ID
    clientId: '<guid>',

    // Host URI
    redirectUri: '<host addr>',

    cacheLocation: 'localStorage'
  ,

  // Set this to true for authentication on startup
  requireAuthOnInitialize: true,

  // Pass a vue-router object in to add route hooks with authentication and role checking
  router: router
)
```

重要:确保将路由器上的模式设置为“历史”,这样它就不会使用哈希!这将对服务器端产生影响。

new Router(
  mode: 'history', // Required for Adal library
  ... // Rest of router init
)

npm 上有更多使用说明,github 上还有说明 + sample

【讨论】:

谢谢。注意:您应该添加免责声明,说明您是此插件的作者。 完成。欣赏小费。 @Xiaosu 1.3.x 及以上版本,支持IE11。如果您遇到问题/在 github 上打开问题,请告诉我。 @survirtual 感谢您的回复。当我在 vue-cli 项目中使用 vue-adal 作为依赖项时,它在 IE 11 中不起作用。如果我只使用 adal.js,那就可以了。 那是用最新的版本吗? 1.3.1昨天推送了。【参考方案3】:

我不确定是否有一个库可以帮助 Vue 应用程序的安全性。但是,我们可以轻松地利用 Adal.js 进行身份验证。

我写了一个简单的demo供大家参考:

Index.html

<html>
<head>
  <script src="https://unpkg.com/vue"></script>
  <script src="node_modules\adal-angular\lib\adal.js"></script>
  <script src="config.js"></script>
  <script>
    var authContext = new AuthenticationContext(config);

    function login() 
      authContext.login();
    

    function init(configOptions) 
    if (configOptions) 
      // redirect and logout_redirect are set to current location by default
      var existingHash = window.location.hash;
      var pathDefault = window.location.href;
      if (existingHash) 
        pathDefault = pathDefault.replace(existingHash, "");
      
      configOptions.redirectUri = configOptions.redirectUri || pathDefault;
      configOptions.postLogoutRedirectUri =
      configOptions.postLogoutRedirectUri || pathDefault;

      // create instance with given config
     else 
      throw new Error("You must set configOptions, when calling init");
    

    // loginresource is used to set authenticated status
    updateDataFromCache(authContext.config.loginResource);
    

    var _oauthData = 
      isAuthenticated: false,
      userName: "",
      loginError: "",
      profile: ""
    ;
    var updateDataFromCache = function(resource) 
      // only cache lookup here to not interrupt with events
      var token = authContext.getCachedToken(resource);
      _oauthData.isAuthenticated = token !== null && token.length > 0;
      var user = authContext.getCachedUser() ||  userName: "" ;
      _oauthData.userName = user.userName;
      _oauthData.profile = user.profile;
      _oauthData.loginError = authContext.getLoginError();
    ;

    function saveTokenFromHash() 
      var hash = window.location.hash;
      var requestInfo = authContext.getRequestInfo(hash);
      if (authContext.isCallback(hash)) 
        // callback can come from login or iframe request

        var requestInfo = authContext.getRequestInfo(hash);
        authContext.saveTokenFromHash(requestInfo);
        window.location.hash = "";

        if (requestInfo.requestType !== authContext.REQUEST_TYPE.LOGIN) 
          authContext.callback = window.parent.AuthenticationContext().callback;
        
      
    

    function isAuthenticate() 
      return _oauthData.isAuthenticated;
    

    saveTokenFromHash();

    init(config);
  </script>
</head>

<body>
<div id="app">
  <p v-if="_oauthData.isAuthenticated">Hello  oauthData.userName </p>
  <button onclick="login()" v-else>Login</button>
</div>

<script>
  var app = new Vue(
    el: "#app",
    data: 
      oauthData: _oauthData
    
  );
</script>
</body>
</html>

config.js

var config = 
  tenant: 'xxx.onmicrosoft.com',
  clientId: '',
  redirectUri: '',
  cacheLocation: 'localStorage'
;

【讨论】:

感谢您抽出宝贵时间。试过后我会回复你的。 是的,但您必须查看 adal-vanilla。不过这个答案是不对的,因为 adal 已经为您完成了大部分工作。 @Coreus 上面的代码示例对我来说效果很好。大多数其他库只是包装 adal.js 以使其适合框架,例如 adal-vanilla 对于 adal 1.0.14,上面的大部分内容都不需要,因为它在其他地方处理。例如:authContext.isCallback(window.location.hash) 我最终以这种方式使用了一些东西:gist.github.com/psignoret/50e88652ae5cb6cc157c09857e3ba87f 我还使用了1.0.14 版本的AdalJS。上面的代码不是完美的解决方案,它只是帮助开发人员开始使用 Azure AD 保护 VueJS 应用程序。如果您已经有了解决方案,您可以将其发布并标记为答案,以便有相同问题的其他社区可以轻松识别答案。【参考方案4】:

您可以使用 Adal javascript。但是,我建议您对该解决方案在安全方面进行更多研究,它似乎不符合新的安全建议,即使用 PKCE(请参阅https://oauth.net/2/grant-types/implicit/)。我找不到任何相关的 JavaScript 文档。

【讨论】:

这取决于授权机构。如果他们使用 2FA,你就更清楚了。【参考方案5】:

这对我来说很难,所以我在这里发帖 - 希望这会为某人节省一些时间:

我的问题是,我不仅需要使用 azure-ad 对我的 vue.js 应用程序进行身份验证,还需要获取用户所属的安全组。

为了实现这一点,我做了以下事情:

我使用了上面提到的 vue-adal 示例应用程序(您可以在:https://github.com/survirtual/vue-adal 中找到它) - 在示例文件夹下。

但我仍然需要进行一些更改以使其按我需要的方式运行。问题是,在使用我的用户登录后,示例应用程序使用 windows.net 图形 API 来使用来自用户身份验证的令牌检索用户信息,所以我必须在 main.js 中进行更改:

const graphApiBase = `https://graph.windows.net`
const graphApiResource = '00000002-0000-0000-c000-000000000000'

到这里:

const graphApiBase = `https://graph.microsoft.com/v1.0`
const graphApiResource = '00000003-0000-0000-c000-000000000000'

此外,在返回 url 组件中,我必须更改 axios 查询以获取用户所属的安全组......所以我更改了这个(在 home.vue 文件中):

async getUserInfo () 
    let res = await this.$graphApi.get(`me`, 
        params: 
            'api-version': 1.6
    
)

到这里:

async getUserInfo () 
    let res = await this.$graphApi.post(`/me/getMemberGroups`, 
        securityEnabledOnly: true
    )
    console.log(res)
    return res.data

然后我从 API 收到的数据包含用户所属的安全组...

【讨论】:

请注意 vue-adal 不再被积极维护,不应该用于任何严肃的项目。请改用 ms-adal。 @Coreus 你是说ms-asal吗?我找不到ms-adal 库。 他们改名了等等。 MS 正在远离 MS-Adal,现在建议改用 MS-MSAL。你可以在这里找到:npmjs.com/package/msal/v/1.3.0-beta.0

以上是关于如何使用 Azure AD 对 VueJS 应用程序进行身份验证?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用身份服务器 3 和 microsoft 团队应用程序使用 Azure AD 令牌进行身份验证

使用 Xamarin Forms 使用自签名证书对 Azure AD 进行身份验证

使用 Azure Ad 中的服务应用程序限制对组或个人邮箱的访问

如何使用 Azure AD B2C 保护 Blazor Wasm 应用访问的 Azure 函数?

如何对 Azure AD 中特定区域的用户进行身份验证和授权?

如何使 Azure AD access_token 在 Microsoft Graph API 之外对其签名验证兼容?