[Flutter] 스크린샷 막기

osung·2022년 12월 1일
18
post-thumbnail

우리 컨텐츠 지켜!

나는 모 회사에서 OTT서비스라이브 스트리밍 서비스를 함께 개발하고 있다
당연히 회사에 들어오는 독점 컨텐츠도 꽤 많은 편인데.. 중요한 건 컨텐츠 화면에서 녹화가 가능해 불법 사이트로 유통이 많이 된다는 점, 그동안은 플랫폼 인지도를 위해 어느 정도 손해를 감수하고 풀어 두었지만 이제는 막을 때가 되었다는 내부의 목소리에 따라 앱 내에 캡처 방지 기능을 구현했다

* 제대로 막으려면 DRM을 적용해야하지만 개발에 시간이 걸려 일단 앱에 임시 조치가 취해졌다.

1. 안드로이드

실제로 개발에 착수했을 때 안드로이드는 flutter_windowmanager 라이브러리로 코드 한줄만 쓰면 쉽게 막을 수 있었다 캡처하면 OS내에서 멘트도 출력해준다

// 캡처 방지 활성화
FlutterWindowManager.addFlags(FlutterWindowManager.FLAG_SECURE);

// 캡처 방지 비활성화
FlutterWindowManager.clearFlags(FlutterWindowManager.FLAG_SECURE);

2. IOS

ios도 pub.dev에 몇개의 라이브러리가 올라와있었다

  1. ios_insecure_screen_detector
  2. screen_protector

라이브러리를 까보니 MethodChannel로 구현한 복잡하지 않은 로직이었고 굳이 라이브러리를 추가 할 정도 까진 아니어서 해당 링크를 참고해 구현했다 swift의 UITextField 개체를 이용한 방법인데 isSecureTextEntry를 활성화 하면 ios에서 보안으로 캡처,녹화를 막는다고 한다.(참조)

isSecureTextEntry

A Boolean value that indicates whether a text object disables copying, and in some cases, prevents recording/broadcasting and also hides the text.

전체코드

AppDelegate.swift

주석 아래 코드만 추가해주면 된다

import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  //UITextField 초기화
  private var textField = UITextField()
  
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
  	//함수 추가
    makeSecureYourScreen() 
    
    //MethodChannel생성
    let controller : FlutterViewController = self.window?.rootViewController as! FlutterViewController
    let securityChannel = FlutterMethodChannel(name: "secureShotChannel", binaryMessenger: controller.binaryMessenger)
    securityChannel.setMethodCallHandler({
            (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
            if call.method == "secureIOS" {
                self.textField.isSecureTextEntry = true
            } else if call.method == "unSecureIOS" {
                self.textField.isSecureTextEntry = false
            }
    })
    
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
  
  //UITextField View에 추가
  private func makeSecureYourScreen() {
        if (!self.window.subviews.contains(textField)) {
            self.window.addSubview(textField)
            textField.centerYAnchor.constraint(equalTo: self.window.centerYAnchor).isActive = true
            textField.centerXAnchor.constraint(equalTo: self.window.centerXAnchor).isActive = true
            self.window.layer.superlayer?.addSublayer(textField.layer)
            textField.layer.sublayers?.first?.addSublayer(self.window.layer)
        }
    }
}

secure_shot.dart

abstract class SecureShot {
  SecureShot._();

  static const _channel = MethodChannel('secureShotChannel');
  
  static void on() {
    if (Platform.isAndroid) {
      FlutterWindowManager.addFlags(FlutterWindowManager.FLAG_SECURE);
    } else if (Platform.isIOS) {
      _channel.invokeMethod("secureIOS");
    }
  }
  
  static void off() {
    if (Platform.isAndroid) {
      FlutterWindowManager.clearFlags(FlutterWindowManager.FLAG_SECURE);
    } else if (Platform.isIOS) {
      _channel.invokeMethod("unSecureIOS");
    }
  }
}

view.dart

  
  void initState() {
    super.initState();
    SecureShot.on();
  }


  
  void dispose() {
    SecureShot.off();
    super.dispose();
  }
}

혹시 틀린 부분이 있다면 댓글 주세요!!!

profile
킹왕짱 개발자가 될테야

2개의 댓글

comment-user-thumbnail
2022년 12월 4일

오 네이티브 레벨로 해주지 않아도 되는군요 좋네요..!
OS 메세지 어떻게 뜨는지도 궁금하네요 ㅎㅎ

1개의 답글