繁体   English   中英

如何连接跨平台 Flutter 应用程序与 Azure AD

[英]How do I hook up a cross platform Flutter app with Azure AD

我在 Flutter 中创建了一个跨平台应用程序(移动、桌面和 Web),我想将其设置为通过 Azure AD 进行身份验证。

我知道您可以为移动设备添加一些软件包,甚至可以为 web 添加一些软件包,但我无法找到适用于桌面的工作解决方案。

我认为我可以在设备上打开浏览器并使用它来登录用户,但它需要一个 URI 来重定向到用户通过身份验证时,并且应用程序才能获取我可以使用的令牌打电话给我的 API。 不过,我看不出这是如何工作的,因为应用程序托管在用户设备上,而不是像网站一样设置在具有 IP 的服务器上。

任何可能的解决方案或指导将不胜感激。

找到一个 MS 文档,您可以按照在您的桌面应用程序中添加 Azure 身份验证。

请参阅: 在 WPF 桌面应用程序中使用 Microsoft 身份平台登录用户并调用 ASP.NET 核心 WebA14742387014A86747CED7014

还有另一种方法,但使用 Azure AD B2C: 使用 Azure AD B2C 在示例 WPF 桌面应用程序中配置身份验证

应用程序注册和架构如下图所示:

在此处输入图像描述

我最终结合使用了这个较旧的 Facebook 身份验证教程以及有关如何获取本机应用程序令牌以创建如下所示的小型身份验证服务的 Microsoft 文档

我使用了以下 pub 包:

  • url_launcher
  • flutter_dotenv
  • http

认证服务:

import 'dart:async';
import 'dart:io';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:research_library_viewer/Models/Token.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:http/http.dart' as http;

class AuthenticationService {
  String tenant = dotenv.env['MSAL_TENANT']!;
  String clientId = dotenv.env['MSAL_CLIENT_ID']!;
  String clientSecret = dotenv.env['MSAL_CLIENT_SECRET']!;
  String redirectURI = dotenv.env['MSAL_LOGIN_REDIRECT_URI']!;
  String scope = dotenv.env['MSAL_CLIENT_SCOPE']!;
  String authority = dotenv.env['MSAL_AUTHORITY_URI']!;

  Future<Stream<String>> _server() async {
    final StreamController<String> onCode = StreamController();
    HttpServer server =
        await HttpServer.bind(InternetAddress.loopbackIPv4, 8080);
    server.listen((HttpRequest request) async {
      final String? code = request.uri.queryParameters["code"];
      request.response
        ..statusCode = 200
        ..headers.set("Content-Type", ContentType.html.mimeType)
        ..write("<html><h1>You can now close this window</h1></html>");
      await request.response.close();
      await server.close(force: true);
      if (code != null) {
        onCode.add(code);
        await onCode.close();
      }
    });
    return onCode.stream;
  }

  String getAuthUrl() {
    String authUrl =
        "http://$authority/$tenant/oauth2/v2.0/authorize?client_id=$clientId&response_type=code&redirect_uri=$redirectURI&response_mode=query&scope=$scope";
    return authUrl;
  }

  Map<String, dynamic> getTokenParameters(String token, bool refresh) {
    Map<String, dynamic> tokenParameters = <String, dynamic>{};
    tokenParameters["client_id"] = clientId;
    tokenParameters["scope"] = scope;
    tokenParameters["client_secret"] = clientSecret;

    if (refresh) {
      tokenParameters["refresh_token"] = token;
      tokenParameters["grant_type"] = "refresh_token";
    } else {
      tokenParameters["code"] = token;
      tokenParameters["redirect_uri"] = redirectURI;
      tokenParameters["grant_type"] = "authorization_code";
    }

    return tokenParameters;
  }

  Future<Token> getToken() async {
    String url = getAuthUrl();
    Stream<String> onCode = await _server();
    if (await canLaunch(url)) {
      await launch(url);
    } else {
      throw "Could not launch $url";
    }
    final String code = await onCode.first;
    final Map<String, dynamic> tokenParameters =
        getTokenParameters(code, false);
    final response = await http.post(
        Uri.https(
          'login.microsoftonline.com',
          '$tenant/oauth2/v2.0/token',
        ),
        headers: <String, String>{
          'Content-Type': 'application/x-www-form-urlencoded'
        },
        body: tokenParameters);
    if (response.statusCode == 200) {
      return tokenFromJson(response.body);
    } else {
      throw Exception('Failed to acquire token');
    }
  }

  Future<Token> refreshToken(String? refreshToken) async {
    if (refreshToken == null) {
      return getToken();
    } else {
      final Map<String, dynamic> tokenParameters = getTokenParameters(refreshToken, true);
      final response = await http.post(
        Uri.https(
          'login.microsoftonline.com',
          '$tenant/oauth2/v2.0/token',
        ),
        headers: <String, String>{
          'Content-Type': 'application/x-www-form-urlencoded'
        },
        body: tokenParameters);
      if (response.statusCode == 200) {
        return tokenFromJson(response.body);
      } else {
        throw Exception('Failed to acquire token');
      }
    }
  }
}

代币:

import 'dart:convert';

Token tokenFromJson(String str) {
  final jsonData = json.decode(str);
  return Token.fromJson(jsonData);
}

class Token {
  String accessToken;
  String tokenType;
  num? expiresIn;
  String? refreshToken;
  String? idToken;
  String? scope;

  Token({
    required this.accessToken,
    required this.tokenType,
    this.expiresIn,
    this.refreshToken,
    this.idToken,
    this.scope,

  });

  factory Token.fromJson(Map<String, dynamic> json) => Token(
        accessToken: json["access_token"],
        tokenType: json["token_type"],
        expiresIn: json["expires_in"],
        refreshToken: json["refresh_token"],
        idToken: json["id_token"],
        scope: json["scope"],
      );

  Map<String, dynamic> toJson() => {
        "access_token": accessToken,
        "token_type": tokenType,
        "expires_in": expiresIn,
        "refresh_token": refreshToken,
        "id_token": idToken,
        "scope": scope,
      };
}

我认为这仍然可以改进很多,但是如果您面临类似的挑战,这绝对是一个开始。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM