Hola, Amigos! Продолжаем разговор о безопасности приложений на Flutter.
Даже при корректной аутентификации приложение остается уязвимым, если сетевой слой и серверная валидация реализованы неправильно. В этой части поговорим о защите сетевых запросов, доверии к данным и проверке OTP.
1. SSL pinning — обязательное требование
Без pinning HTTPS не спасает от атаки MITM (Man In The Middle), когда злоумышленники перехватывают трафик между приложением и сервером, а потом читают или подменяют запросы через прокси или wi-fi:
Это блокирует:
- proxy
- fake Wi-Fi
- подмену сертификатов
2. OTP всегда проверяется на сервере
Проверка OTP на клиенте — критическая уязвимость:
Только сервер:
3. Клиентская валидация — не финальная. Данные всегда проверяются на сервере:
Эти меры закрывают большую часть рисков, но сами по себе они не решают все. Даже при защищенном трафике и корректной логике на бэке приложение остается уязвимым на уровне устройства и интерфейса.
В следующей части разберем защиту UI, работу с сессиями и ограничения на уровне устройства.
Даже при корректной аутентификации приложение остается уязвимым, если сетевой слой и серверная валидация реализованы неправильно. В этой части поговорим о защите сетевых запросов, доверии к данным и проверке OTP.
1. SSL pinning — обязательное требование
Без pinning HTTPS не спасает от атаки MITM (Man In The Middle), когда злоумышленники перехватывают трафик между приложением и сервером, а потом читают или подменяют запросы через прокси или wi-fi:
final context = SecurityContext(withTrustedRoots: false);
context.setTrustedCertificatesBytes(certBytes);
final httpClient = HttpClient(context: context);
final dio = Dio()
..httpClientAdapter = IOHttpClientAdapter(
createHttpClient: () => httpClient,
);
Это блокирует:
- proxy
- fake Wi-Fi
- подмену сертификатов
2. OTP всегда проверяется на сервере
Проверка OTP на клиенте — критическая уязвимость:
if (enteredOtp == "123456") success();
Только сервер:
await api.verifyOtp(
mobile: mobile,
otp: enteredOtp,
);
3. Клиентская валидация — не финальная. Данные всегда проверяются на сервере:
bool isValidAmount(String value) {
final amount = double.tryParse(value);
return amount != null && amount > 0 && amount < 100000;
}
Эти меры закрывают большую часть рисков, но сами по себе они не решают все. Даже при защищенном трафике и корректной логике на бэке приложение остается уязвимым на уровне устройства и интерфейса.
В следующей части разберем защиту UI, работу с сессиями и ограничения на уровне устройства.
🔥16❤5😍5🙊1
Hola, Amigos! Даже идеально защищенный backend не поможет, если приложение уязвимо на уровне устройства. В финальной части посмотрим на безопасность со стороны устройства и пользовательского интерфейса.
1. Запрещайте скриншоты на чувствительных экранах
Некоторые чувствительные данные не должны попадать в скриншоты:
2. Используйте биометрию
Биометрия нужна не только для логина.
Подходит для различных сценариев:
- входа
- платежей
- доступа к профилю
3. Автоматический логаут и контроль сессии
Выход при:
- уходе в фон
- бездействии
- истечении токена
4. Root / Jailbreak — блокировать
Не забывайте про использование в нескольких местах и рандомных триггерах, чтобы труднее было подвергнуть приложение реверс-инжинирингу.
5. Код и логи в продакшене
Надеюсь, советы были полезны и помогут избежать типовых ошибок при разработке приложений на Flutter. Если тема откликнулась — пишите в комментарии!
1. Запрещайте скриншоты на чувствительных экранах
Некоторые чувствительные данные не должны попадать в скриншоты:
@override
void initState() {
super.initState();
if (Platform.isAndroid) {
FlutterWindowManager.addFlags(
FlutterWindowManager.FLAG_SECURE,
);
}
}
2. Используйте биометрию
Биометрия нужна не только для логина.
final auth = LocalAuthentication();
await auth.authenticate(
localizedReason: "Verify to access your bank account",
options: const AuthenticationOptions(
biometricOnly: true,
),
);
Подходит для различных сценариев:
- входа
- платежей
- доступа к профилю
3. Автоматический логаут и контроль сессии
class SessionManager {
Timer? _timer;
void start(void Function() onExpire) {
_timer?.cancel();
_timer = Timer(
const Duration(minutes: 5),
onExpire,
);
}
void refresh() {
_timer?.cancel();
}
}
Выход при:
- уходе в фон
- бездействии
- истечении токена
4. Root / Jailbreak — блокировать
final isRooted = await RootChecker.isRooted();
if (isRooted) {
exit(0);
}
Не забывайте про использование в нескольких местах и рандомных триггерах, чтобы труднее было подвергнуть приложение реверс-инжинирингу.
5. Код и логи в продакшене
flutter build apk --obfuscate --split-debug-info=./symbols
flutter build ios --obfuscate
if (kDebugMode) {
print("Token: $token");
}
Надеюсь, советы были полезны и помогут избежать типовых ошибок при разработке приложений на Flutter. Если тема откликнулась — пишите в комментарии!
🔥14❤11👍4❤🔥3
Hola, Amigos! Сегодня делимся простыми, но рабочими лайфхаками, которые помогут сделать ваше Flutter-приложение быстрее, легче и стабильнее.
Делитесь в комментариях, пользовались чем-то?
Делитесь в комментариях, пользовались чем-то?
👍11🔥10❤6
Hola, Amigos! Сегодня разбираем полезные и часто недооцененные виджеты во Flutter, которые реально спасают в реальных проектах 🙂
1. AbsorbPointer. Он блокирует взаимодействия (тапания, жесты) для своего потомка полностью, когда absorbing is true.
Use case: запретить двойные тапы на кнопке во время состояния загрузки.
Позволяет UI оставаться видимым и интерактивным для анимаций, но останавливает ввод пользователя.
2. IgnorePointer. Отключает жесты, но не блокирует layout hit testing.
Use case: чтобы UI выглядел «активным», но не реагировал на пользователя.
Подойдет, чтобы временно отключить взаимодействие без изменения визуального
AbsorbPointer не пропускает какие-либо действия под собой, а IgnorePointer блокирует только взаимодействие именно с ним.
3. Offstage. Убирает виджет из рендеринга/layout'а, но оставляет его в дереве виджетов.
Use case: пре-билдить сложные или дорогие части UI (табы, экраны), но пока не показывать их.
Почему помогает: скрытый виджет остается живым (state, controllers, animations) без влияния на видимый
1. AbsorbPointer. Он блокирует взаимодействия (тапания, жесты) для своего потомка полностью, когда absorbing is true.
Use case: запретить двойные тапы на кнопке во время состояния загрузки.
AbsorbbPointer(
absorbing: isLoading,
child: isLoadingButton(
child: ElevatedButton(
onPressed: () { /* submit */ },
child: Text("Submit"),
),
),
)
Позволяет UI оставаться видимым и интерактивным для анимаций, но останавливает ввод пользователя.
2. IgnorePointer. Отключает жесты, но не блокирует layout hit testing.
Use case: чтобы UI выглядел «активным», но не реагировал на пользователя.
IgnorePointer(
ignoring: isReadOnly,
child: Slider(
value: sliderValue,
onChanged: (_) {},
),
)
Подойдет, чтобы временно отключить взаимодействие без изменения визуального
layout и footprint виджета.AbsorbPointer не пропускает какие-либо действия под собой, а IgnorePointer блокирует только взаимодействие именно с ним.
3. Offstage. Убирает виджет из рендеринга/layout'а, но оставляет его в дереве виджетов.
Use case: пре-билдить сложные или дорогие части UI (табы, экраны), но пока не показывать их.
Offstage(
offstage: !showSettings,
child: SettingsPanel(),
)
Почему помогает: скрытый виджет остается живым (state, controllers, animations) без влияния на видимый
layout и без перестроек каждый раз.Please open Telegram to view this post
VIEW IN TELEGRAM
❤12🔥12👍9
Hola, Amigos! Продолжаем разбор полезных Flutter-виджетов для адаптивной верстки. Во второй части еще несколько инструментов, которые помогут сделать UI стабильным на разных экранах.
1. FractionallySizedBox. Позволяет задать размер дочернего виджета как долю от размера родителя (по ширине и/или высоте).
Use case: когда нужно, чтобы кнопка или контейнер занимали, например, 80% ширины родителя без hardcoded значений.
Помогает сохранять пропорции на разных экранах и устройствах → UI выглядит консистентно и адаптивно.
2. FittedBox. Масштабирует дочерний виджет так, чтобы он вписался в доступное пространство, сохраняя пропорции (
Use case: когда нужно вписать крупный текст или иконку в небольшую карточку или контейнер без overflow.
Предотвращает ошибки переполнения (overflow) и гарантирует корректное масштабирование UI.
Что возьмете в работу?
1. FractionallySizedBox. Позволяет задать размер дочернего виджета как долю от размера родителя (по ширине и/или высоте).
Use case: когда нужно, чтобы кнопка или контейнер занимали, например, 80% ширины родителя без hardcoded значений.
FractionallySizedBox(
widthFactor: 0.8,
child: ElevatedButton(
onPressed: () {},
child: Text("Continue"),
),
)
Помогает сохранять пропорции на разных экранах и устройствах → UI выглядит консистентно и адаптивно.
2. FittedBox. Масштабирует дочерний виджет так, чтобы он вписался в доступное пространство, сохраняя пропорции (
aspect ratio).Use case: когда нужно вписать крупный текст или иконку в небольшую карточку или контейнер без overflow.
FittedBox(
child: Text(
"Responsive Title",
style: TextStyle(fontSize: 40),
),
)
Предотвращает ошибки переполнения (overflow) и гарантирует корректное масштабирование UI.
Что возьмете в работу?
👍12🔥7👌4❤2
Hola, amigos! Сегодня рассмотрим четыре инструмента, которые помогают писать чище код, избавляться от хардкода и не жертвовать производительностью.
1. LayoutBuilder работает с реальными ограничениями родительского виджета, чтобы интерфейс получился гибким и предсказуемым на любых устройствах
2. AnimatedSwitcher позволяет аккуратно анимировать переходы через fade, slide или scale и сразу делает интерфейс «дороже»
3. TweenAnimationBuilder закрывает до 80% простых анимаций
4. RepaintBoundary изолирует часть UI и не дает ей перерисовываться без необходимости
А что для в работе используете вы?
1. LayoutBuilder работает с реальными ограничениями родительского виджета, чтобы интерфейс получился гибким и предсказуемым на любых устройствах
LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth > 700) {
return Row(
children: [
Sidebar(),
Expanded(child: Content()),
],
);
}
return Content();
},
);
2. AnimatedSwitcher позволяет аккуратно анимировать переходы через fade, slide или scale и сразу делает интерфейс «дороже»
AnimatedSwitcher(
duration: Duration(milliseconds: 300),
child: Text('$count', key: ValueKey(count)),
);
3. TweenAnimationBuilder закрывает до 80% простых анимаций
TweenAnimationBuilder<double>(
tween: Tween(begin: 0, end: progress),
duration: Duration(milliseconds: 500),
builder: (context, value, child) {
return LinearProgressIndicator(value: value);
},
);
4. RepaintBoundary изолирует часть UI и не дает ей перерисовываться без необходимости
RepaintBoundary(
child: ExpensiveWidget(),
)
А что для в работе используете вы?
👍14❤4🔥2
Hola, amigos! Сегодня разберем подборку Flutter-инструментов, которые прокачивают взаимодействие с пользователем.
1. Dismissible позволяет легко реализовать логику swipe-to-delete.
2. Tooltip показывает краткую подсказку при долгом нажатии (mobile) или наведении (web/desktop).
3. Draggable подходит, чтобы реализовать drag-and-drop, как в Trello или корзине интернет-магазина.
4. ReorderableListView идеально подходит для настроек, плейлистов и любых кастомных списков.
А какими виджетами для UX чаще всего пользуетесь вы? Делитесь в комментариях.
1. Dismissible позволяет легко реализовать логику swipe-to-delete.
Dismissible(
key: Key(item.id),
background: Container(color: Colors.red),
onDismissed: (direction) => deleteItem(item.id),
child: ListTile(
title: Text("Swipe me to delete"),
),
)
2. Tooltip показывает краткую подсказку при долгом нажатии (mobile) или наведении (web/desktop).
Tooltip(
message: 'Download PDF',
child: IconButton(
icon: Icon(Icons.download),
onPressed: () {},
),
)
3. Draggable подходит, чтобы реализовать drag-and-drop, как в Trello или корзине интернет-магазина.
Draggable<Color>(
data: Colors.blue,
feedback: Container(
height: 100,
width: 100,
color: Colors.blue.withOpacity(0.5),
),
childWhenDragging: Container(
height: 100,
width: 100,
color: Colors.grey,
),
child: Container(
height: 100,
width: 100,
color: Colors.blue,
),
);
4. ReorderableListView идеально подходит для настроек, плейлистов и любых кастомных списков.
ReorderableListView(
onReorder: (oldIndex, newIndex) {
setState(() {
if (newIndex > oldIndex) newIndex -= 1;
final item = list.removeAt(oldIndex);
list.insert(newIndex, item);
});
},
children: list
.map(
(item) => ListTile(
key: ValueKey(item),
title: Text(item.toString()),
),
)
.toList(),
);
А какими виджетами для UX чаще всего пользуетесь вы? Делитесь в комментариях.
👍8❤5👀3