Flutter认证授权插件flutter_appauth的使用
Flutter认证授权插件flutter_appauth的使用
Flutter AppAuth Plugin
Flutter AppAuth 是一个用于用户认证和授权的插件,它基于 AppAuth 构建。该插件支持PKCE扩展,这对于某些提供者是必需的。
重要提示
- 此插件要求应用程序使用AndroidX。有关详细信息,请参阅Flutter AndroidX兼容性。
- 如果Chrome自定义标签在您的Android应用中无法正常工作,请确保您使用的是最新版本的此插件、Android Studio、Gradle分发版和Android Gradle插件。
来自身份提供商的教程
入门指南
首先创建插件实例:
FlutterAppAuth appAuth = FlutterAppAuth();
然后执行授权和认证请求:
final AuthorizationTokenResponse result = await appAuth.authorizeAndExchangeCode(
AuthorizationTokenRequest(
'<client_id>',
'<redirect_url>',
discoveryUrl: '<discovery_url>',
scopes: ['openid', 'profile', 'email', 'offline_access', 'api'],
),
);
或者使用发行者URL代替发现URL:
final AuthorizationTokenResponse result = await appAuth.authorizeAndExchangeCode(
AuthorizationTokenRequest(
'<client_id>',
'<redirect_url>',
issuer: '<issuer>',
scopes: ['openid', 'profile', 'email', 'offline_access', 'api'],
),
);
如果已知服务器端点,则可以显式指定它们:
final AuthorizationTokenResponse result = await appAuth.authorizeAndExchangeCode(
AuthorizationTokenRequest(
'<client_id>',
'<redirect_url>',
serviceConfiguration: AuthorizationServiceConfiguration(
authorizationEndpoint: '<authorization_endpoint>',
tokenEndpoint: '<token_endpoint>',
endSessionEndpoint: '<end_session_endpoint>'
),
scopes: [...]
),
);
检测用户取消
try {
await appAuth.authorize(...); // 或 authorizeAndExchangeCode(...)
} on FlutterAppAuthUserCancelledException catch (e) {
// 处理用户取消
}
刷新令牌
final TokenResponse result = await appAuth.token(TokenRequest(
'<client_id>',
'<redirect_url>',
discoveryUrl: '<discovery_url>',
refreshToken: '<refresh_token>',
scopes: ['openid', 'profile', 'email', 'offline_access', 'api']
));
结束会话
await appAuth.endSession(EndSessionRequest(
idTokenHint: '<idToken>',
postLogoutRedirectUrl: '<postLogoutRedirectUrl>',
serviceConfiguration: AuthorizationServiceConfiguration(
authorizationEndpoint: '<authorization_endpoint>',
tokenEndpoint: '<token_endpoint>',
endSessionEndpoint: '<end_session_endpoint>'
)
));
错误处理
try {
await appAuth.authorize(...);
} on FlutterAppAuthPlatformException catch (e) {
final FlutterAppAuthPlatformErrorDetails details = e.details;
// 根据来自AppAuth的错误处理异常
} catch (e) {
// 处理其他错误
}
完整示例代码
以下是一个完整的Flutter应用示例,演示了如何使用flutter_appauth
进行OAuth2.0认证:
import 'dart:convert';
import 'dart:io' show Platform;
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_appauth/flutter_appauth.dart';
import 'package:http/http.dart' as http;
void main() => runApp(const MyApp());
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool _isBusy = false;
final FlutterAppAuth _appAuth = const FlutterAppAuth();
String? _codeVerifier;
String? _nonce;
String? _authorizationCode;
String? _refreshToken;
String? _accessToken;
String? _idToken;
String? _error;
final TextEditingController _authorizationCodeTextController =
TextEditingController();
final TextEditingController _accessTokenTextController =
TextEditingController();
final TextEditingController _accessTokenExpirationTextController =
TextEditingController();
final TextEditingController _idTokenTextController = TextEditingController();
final TextEditingController _refreshTokenTextController =
TextEditingController();
String? _userInfo;
final String _clientId = 'interactive.public';
final String _redirectUrl = 'com.duendesoftware.demo:/oauthredirect';
final String _issuer = 'https://demo.duendesoftware.com';
final String _discoveryUrl =
'https://demo.duendesoftware.com/.well-known/openid-configuration';
final String _postLogoutRedirectUrl = 'com.duendesoftware.demo:/';
final List<String> _scopes = <String>[
'openid',
'profile',
'email',
'offline_access',
'api'
];
final AuthorizationServiceConfiguration _serviceConfiguration =
const AuthorizationServiceConfiguration(
authorizationEndpoint: 'https://demo.duendesoftware.com/connect/authorize',
tokenEndpoint: 'https://demo.duendesoftware.com/connect/token',
endSessionEndpoint: 'https://demo.duendesoftware.com/connect/endsession',
);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: SafeArea(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
Visibility(
visible: _isBusy,
child: const LinearProgressIndicator(),
),
const SizedBox(height: 8),
ElevatedButton(
child: const Text('Sign in with no code exchange'),
onPressed: () => _signInWithNoCodeExchange(),
),
const SizedBox(height: 8),
ElevatedButton(
child: const Text(
'Sign in with no code exchange and generated nonce'),
onPressed: () => _signInWithNoCodeExchangeAndGeneratedNonce(),
),
const SizedBox(height: 8),
ElevatedButton(
onPressed: _authorizationCode != null ? _exchangeCode : null,
child: const Text('Exchange code'),
),
const SizedBox(height: 8),
ElevatedButton(
child: const Text('Sign in with auto code exchange'),
onPressed: () => _signInWithAutoCodeExchange(),
),
if (Platform.isIOS || Platform.isMacOS)
Padding(
padding: const EdgeInsets.all(8.0),
child: ElevatedButton(
child: const Text(
'Sign in with auto code exchange using ephemeral '
'session',
textAlign: TextAlign.center,
),
onPressed: () => _signInWithAutoCodeExchange(
externalUserAgent: ExternalUserAgent
.ephemeralAsWebAuthenticationSession),
),
),
if (Platform.isIOS)
Padding(
padding: const EdgeInsets.all(8.0),
child: ElevatedButton(
child: const Text(
'Sign in with auto code exchange using '
'SFSafariViewController',
textAlign: TextAlign.center,
),
onPressed: () => _signInWithAutoCodeExchange(
externalUserAgent:
ExternalUserAgent.sfSafariViewController),
),
),
ElevatedButton(
onPressed: _refreshToken != null ? _refresh : null,
child: const Text('Refresh token'),
),
const SizedBox(height: 8),
ElevatedButton(
onPressed: _idToken != null
? () async {
await _endSession();
}
: null,
child: const Text('End session'),
),
if (Platform.isIOS || Platform.isMacOS)
Padding(
padding: const EdgeInsets.all(8.0),
child: ElevatedButton(
onPressed: _idToken != null
? () async {
await _endSession(
externalUserAgent: ExternalUserAgent
.ephemeralAsWebAuthenticationSession);
}
: null,
child:
const Text('End session using ephemeral session'),
)),
if (Platform.isIOS)
Padding(
padding: const EdgeInsets.all(8.0),
child: ElevatedButton(
onPressed: _idToken != null
? () async {
await _endSession(
externalUserAgent: ExternalUserAgent
.sfSafariViewController);
}
: null,
child: const Text(
'End session using SFSafariViewController'),
)),
const SizedBox(height: 8),
if (_error != null) Text(_error ?? ''),
const SizedBox(height: 8),
const Text('authorization code'),
TextField(
controller: _authorizationCodeTextController,
),
const Text('access token'),
TextField(
controller: _accessTokenTextController,
),
const Text('access token expiration'),
TextField(
controller: _accessTokenExpirationTextController,
),
const Text('id token'),
TextField(
controller: _idTokenTextController,
),
const Text('refresh token'),
TextField(
controller: _refreshTokenTextController,
),
const Text('test api results'),
Text(_userInfo ?? ''),
],
),
),
),
),
);
}
Future<void> _endSession(
{ExternalUserAgent externalUserAgent =
ExternalUserAgent.asWebAuthenticationSession}) async {
try {
_setBusyState();
await _appAuth.endSession(EndSessionRequest(
idTokenHint: _idToken,
postLogoutRedirectUrl: _postLogoutRedirectUrl,
serviceConfiguration: _serviceConfiguration,
externalUserAgent: externalUserAgent));
_clearSessionInfo();
} catch (e) {
_handleError(e);
} finally {
_clearBusyState();
}
}
void _clearSessionInfo() {
setState(() {
_codeVerifier = null;
_nonce = null;
_authorizationCode = null;
_authorizationCodeTextController.clear();
_accessToken = null;
_accessTokenTextController.clear();
_idToken = null;
_idTokenTextController.clear();
_refreshToken = null;
_refreshTokenTextController.clear();
_accessTokenExpirationTextController.clear();
_userInfo = null;
});
}
Future<void> _refresh() async {
try {
_setBusyState();
final TokenResponse result = await _appAuth.token(TokenRequest(
_clientId, _redirectUrl,
refreshToken: _refreshToken, issuer: _issuer, scopes: _scopes));
_processTokenResponse(result);
await _testApi(result);
} catch (e) {
_handleError(e);
} finally {
_clearBusyState();
}
}
Future<void> _exchangeCode() async {
try {
_setBusyState();
final TokenResponse result = await _appAuth.token(TokenRequest(
_clientId, _redirectUrl,
authorizationCode: _authorizationCode,
discoveryUrl: _discoveryUrl,
codeVerifier: _codeVerifier,
nonce: _nonce,
scopes: _scopes));
_processTokenResponse(result);
await _testApi(result);
} catch (e) {
_handleError(e);
} finally {
_clearBusyState();
}
}
Future<void> _signInWithNoCodeExchange() async {
try {
_setBusyState();
final AuthorizationResponse result = await _appAuth.authorize(
AuthorizationRequest(_clientId, _redirectUrl,
discoveryUrl: _discoveryUrl, scopes: _scopes, loginHint: 'bob'),
);
_processAuthResponse(result);
} catch (e) {
_handleError(e);
} finally {
_clearBusyState();
}
}
Future<void> _signInWithNoCodeExchangeAndGeneratedNonce() async {
try {
_setBusyState();
final Random random = Random.secure();
final String nonce =
base64Url.encode(List<int>.generate(16, (_) => random.nextInt(256)));
final AuthorizationResponse result = await _appAuth.authorize(
AuthorizationRequest(_clientId, _redirectUrl,
discoveryUrl: _discoveryUrl,
scopes: _scopes,
loginHint: 'bob',
nonce: nonce),
);
_processAuthResponse(result);
} catch (e) {
_handleError(e);
} finally {
_clearBusyState();
}
}
Future<void> _signInWithAutoCodeExchange(
{ExternalUserAgent externalUserAgent =
ExternalUserAgent.asWebAuthenticationSession}) async {
try {
_setBusyState();
final AuthorizationTokenResponse result =
await _appAuth.authorizeAndExchangeCode(
AuthorizationTokenRequest(_clientId, _redirectUrl,
serviceConfiguration: _serviceConfiguration,
scopes: _scopes,
externalUserAgent: externalUserAgent),
);
_processAuthTokenResponse(result);
await _testApi(result);
} catch (e) {
_handleError(e);
} finally {
_clearBusyState();
}
}
void _handleError(Object e) {
if (e is FlutterAppAuthUserCancelledException) {
setState(() {
_error = 'The user cancelled the flow!';
});
} else if (e is FlutterAppAuthPlatformException) {
setState(() {
_error = e.platformErrorDetails.toString();
});
} else if (e is PlatformException) {
setState(() {
_error = 'Error\n\nCode: ${e.code}\nMessage: ${e.message}\n'
'Details: ${e.details}';
});
} else {
setState(() {
_error = 'Error: $e';
});
}
}
void _clearBusyState() {
setState(() {
_isBusy = false;
});
}
void _setBusyState() {
setState(() {
_error = '';
_isBusy = true;
});
}
void _processAuthTokenResponse(AuthorizationTokenResponse response) {
setState(() {
_accessToken = _accessTokenTextController.text = response.accessToken!;
_idToken = _idTokenTextController.text = response.idToken!;
_refreshToken = _refreshTokenTextController.text = response.refreshToken!;
_accessTokenExpirationTextController.text =
response.accessTokenExpirationDateTime!.toIso8601String();
});
}
void _processAuthResponse(AuthorizationResponse response) {
setState(() {
_codeVerifier = response.codeVerifier;
_nonce = response.nonce;
_authorizationCode =
_authorizationCodeTextController.text = response.authorizationCode!;
_isBusy = false;
});
}
void _processTokenResponse(TokenResponse response) {
setState(() {
_accessToken = _accessTokenTextController.text = response.accessToken!;
_idToken = _idTokenTextController.text = response.idToken!;
_refreshToken = _refreshTokenTextController.text = response.refreshToken!;
_accessTokenExpirationTextController.text =
response.accessTokenExpirationDateTime!.toIso8601String();
});
}
Future<void> _testApi(TokenResponse? response) async {
final http.Response httpResponse = await http.get(
Uri.parse('https://demo.duendesoftware.com/api/test'),
headers: <String, String>{'Authorization': 'Bearer $_accessToken'});
setState(() {
_userInfo = httpResponse.statusCode == 200 ? httpResponse.body : '';
_isBusy = false;
});
}
}
以上代码展示了如何使用flutter_appauth
插件进行OAuth2.0认证,并提供了多个按钮来测试不同的认证流程。
更多关于Flutter认证授权插件flutter_appauth的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter认证授权插件flutter_appauth的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,flutter_appauth
是一个用于在 Flutter 应用中实现 OAuth 2.0 和 OpenID Connect 认证的插件。以下是一个简单的代码示例,展示了如何使用 flutter_appauth
插件进行认证授权。
首先,确保你已经在 pubspec.yaml
文件中添加了 flutter_appauth
依赖:
dependencies:
flutter:
sdk: flutter
flutter_appauth: ^x.y.z # 请替换为最新版本号
然后,运行 flutter pub get
来获取依赖。
接下来是代码示例:
import 'package:flutter/material.dart';
import 'package:flutter_appauth/flutter_appauth.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: AuthScreen(),
);
}
}
class AuthScreen extends StatefulWidget {
@override
_AuthScreenState createState() => _AuthScreenState();
}
class _AuthScreenState extends State<AuthScreen> {
final FlutterAppAuth _auth = FlutterAppAuth();
Future<void> _authorizeAndExchangeToken() async {
try {
// 配置授权请求
final AuthorizationTokenResponse result = await _auth.authorizeAndExchangeCode(
AuthorizationRequest(
clientId: 'YOUR_CLIENT_ID',
redirectUri: Uri.parse('YOUR_REDIRECT_URI'), // 确保这个 URI 在你的授权服务器上注册
endpoint: AuthorizationEndpoint(
baseUrl: Uri.parse('https://YOUR_AUTHORIZATION_SERVER'),
authorizationEndpoint: '/oauth/authorize',
tokenEndpoint: '/oauth/token',
),
scopes: ['read', 'write'], // 替换为你的实际作用域
),
);
// 打印返回的 token 信息
print('Authorization Token Response: $result');
// 你可以在这里处理 token,比如保存到本地或者发送到服务器
} catch (e) {
print('Error: $e');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter AppAuth Example'),
),
body: Center(
child: ElevatedButton(
onPressed: _authorizeAndExchangeToken,
child: Text('Authorize'),
),
),
);
}
}
在上面的代码中:
FlutterAppAuth
插件被用于处理 OAuth 2.0 授权。AuthorizationRequest
包含了客户端 ID、重定向 URI、授权端点和请求的作用域。authorizeAndExchangeCode
方法会启动授权流程,并在用户完成授权后返回授权令牌响应(AuthorizationTokenResponse
)。
注意事项:
- 请确保将
YOUR_CLIENT_ID
、YOUR_REDIRECT_URI
和https://YOUR_AUTHORIZATION_SERVER
替换为你实际的客户端 ID、重定向 URI 和授权服务器 URL。 - 确保重定向 URI 在你的授权服务器上已经注册,并且与你在代码中使用的 URI 匹配。
- 根据你的需求,可能需要调整作用域(
scopes
)和其他参数。
这个示例展示了基本的认证授权流程,你可以根据需要进行扩展和定制。