如何使用 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 应用程序进行身份验证?的主要内容,如果未能解决你的问题,请参考以下文章