วิธีหลีกเลี่ยงการติดตั้งไฟล์นโยบาย JCE“ Unlimited Strength” เมื่อปรับใช้แอปพลิเคชัน


169

ฉันมีแอพที่ใช้การเข้ารหัส AES 256 บิตซึ่ง Java ไม่รองรับ ฉันรู้เพื่อให้มันทำงานได้อย่างถูกต้องฉันติดตั้ง JOC ไม่ จำกัด จำนวนขวดในแฟ้มความปลอดภัย นี่เป็นเรื่องปกติสำหรับฉันในการเป็นนักพัฒนาฉันสามารถติดตั้งได้

คำถามของฉันคือเนื่องจากแอพนี้จะเผยแพร่ผู้ใช้ปลายทางส่วนใหญ่จะไม่ได้ติดตั้งไฟล์นโยบายเหล่านี้ การให้ผู้ใช้ดาวน์โหลดสิ่งเหล่านี้เพียงเพื่อให้แอพพลิเคชั่นไม่ใช่ทางออกที่น่าสนใจ

มีวิธีทำให้แอพของฉันทำงานโดยไม่เขียนทับไฟล์บนเครื่องผู้ใช้ปลายทางหรือไม่? ซอฟต์แวร์บุคคลที่สามที่สามารถจัดการได้โดยไม่ต้องติดตั้งไฟล์นโยบาย? หรือวิธีอ้างอิงไฟล์นโยบายเหล่านี้จากภายใน JAR หรือไม่




11
ฉันสงสัยว่าเจตนาของ Sun / Oracle คือลูกค้าจะใช้รหัสลับที่ไม่ปลอดภัยเพื่อให้ NSA สามารถสอดแนมการเชื่อมต่อได้ ฉันไม่ได้ล้อเล่นหรือเป็นหวาดระแวง แต่การเข้ารหัสจะถือว่าเป็นอาวุธและมีการห้ามการส่งออกการเข้ารหัสที่ใช้ร่วมกัน
เลื่อน

คำตอบ:


175

มีวิธีแก้ไขปัญหาที่ยกมาทั่วไปสองสามปัญหา น่าเสียดายที่สิ่งเหล่านี้ไม่เป็นที่น่าพอใจอย่างสิ้นเชิง:

  • ติดตั้งแฟ้มนโยบายความแข็งแรงไม่ จำกัด แม้ว่านี่จะเป็นทางออกที่เหมาะสมสำหรับเวิร์คสเตชั่นสำหรับการพัฒนาของคุณ แต่มันก็กลายเป็นปัญหาใหญ่อย่างรวดเร็ว (หากไม่ใช่สิ่งกีดขวางบนถนน) เพื่อให้ผู้ใช้ที่ไม่ใช่ด้านเทคนิคติดตั้งไฟล์ในคอมพิวเตอร์ทุกเครื่อง นอกจากนี้ไม่มีทางที่จะแจกจ่ายไฟล์กับโปรแกรมของคุณ; จะต้องติดตั้งในไดเรกทอรี JRE (ซึ่งอาจเป็นแบบอ่านอย่างเดียวเนื่องจากสิทธิ์)
  • ข้าม JCE APIและการใช้ห้องสมุดการเข้ารหัสอื่นเช่นปราสาท Bouncy วิธีนี้ต้องใช้ไลบรารี 1MB เพิ่มเติมซึ่งอาจเป็นภาระที่สำคัญขึ้นอยู่กับแอปพลิเคชัน นอกจากนี้ยังรู้สึกว่าโง่กับฟังก์ชั่นซ้ำซ้อนที่รวมอยู่ในไลบรารีมาตรฐาน เห็นได้ชัดว่า API นั้นแตกต่างจากอินเตอร์เฟส JCE ปกติอย่างสิ้นเชิง (BC ใช้ผู้ให้บริการ JCE แต่นั่นไม่ได้ช่วยเพราะข้อ จำกัด ความแข็งแรงของคีย์ถูกนำไปใช้ก่อนส่งมอบให้กับการใช้งาน) โซลูชันนี้จะไม่อนุญาตให้คุณใช้ชุดรหัสเข้ารหัส TLS (SSL) 256 บิตเนื่องจาก ไลบรารี TLS มาตรฐานเรียก JCE ภายในเพื่อพิจารณาข้อ จำกัด ใด ๆ

แต่แล้วก็มีการสะท้อนกลับ มีอะไรที่คุณไม่สามารถใช้การสะท้อนกลับได้หรือไม่?

private static void removeCryptographyRestrictions() {
    if (!isRestrictedCryptography()) {
        logger.fine("Cryptography restrictions removal not needed");
        return;
    }
    try {
        /*
         * Do the following, but with reflection to bypass access checks:
         *
         * JceSecurity.isRestricted = false;
         * JceSecurity.defaultPolicy.perms.clear();
         * JceSecurity.defaultPolicy.add(CryptoAllPermission.INSTANCE);
         */
        final Class<?> jceSecurity = Class.forName("javax.crypto.JceSecurity");
        final Class<?> cryptoPermissions = Class.forName("javax.crypto.CryptoPermissions");
        final Class<?> cryptoAllPermission = Class.forName("javax.crypto.CryptoAllPermission");

        final Field isRestrictedField = jceSecurity.getDeclaredField("isRestricted");
        isRestrictedField.setAccessible(true);
        final Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        modifiersField.setInt(isRestrictedField, isRestrictedField.getModifiers() & ~Modifier.FINAL);
        isRestrictedField.set(null, false);

        final Field defaultPolicyField = jceSecurity.getDeclaredField("defaultPolicy");
        defaultPolicyField.setAccessible(true);
        final PermissionCollection defaultPolicy = (PermissionCollection) defaultPolicyField.get(null);

        final Field perms = cryptoPermissions.getDeclaredField("perms");
        perms.setAccessible(true);
        ((Map<?, ?>) perms.get(defaultPolicy)).clear();

        final Field instance = cryptoAllPermission.getDeclaredField("INSTANCE");
        instance.setAccessible(true);
        defaultPolicy.add((Permission) instance.get(null));

        logger.fine("Successfully removed cryptography restrictions");
    } catch (final Exception e) {
        logger.log(Level.WARNING, "Failed to remove cryptography restrictions", e);
    }
}

private static boolean isRestrictedCryptography() {
    // This matches Oracle Java 7 and 8, but not Java 9 or OpenJDK.
    final String name = System.getProperty("java.runtime.name");
    final String ver = System.getProperty("java.version");
    return name != null && name.equals("Java(TM) SE Runtime Environment")
            && ver != null && (ver.startsWith("1.7") || ver.startsWith("1.8"));
}

เพียงโทรremoveCryptographyRestrictions()จาก static initializer หรือก่อนทำการดำเนินการเข้ารหัสใด ๆ

JceSecurity.isRestricted = falseส่วนหนึ่งเป็นสิ่งที่จำเป็นในการใช้ยันต์ 256 บิตโดยตรง อย่างไรก็ตามหากไม่มีการดำเนินการอื่นสองรายการCipher.getMaxAllowedKeyLength()จะยังคงรายงาน 128 และชุดรหัส TLS 256- บิตจะไม่ทำงาน

รหัสนี้ทำงานบน Oracle Java 7 และ 8 และข้ามกระบวนการบน Java 9 และ OpenJDK โดยอัตโนมัติซึ่งไม่จำเป็น เป็นแฮ็คที่น่าเกลียดที่สุดมันอาจไม่ทำงานกับ VM ของผู้ค้ารายอื่น

นอกจากนี้ยังไม่สามารถใช้กับ Oracle Java 6 ได้เนื่องจากคลาส JCE ส่วนตัวนั้นมีความสับสน การทำให้งงงวยไม่ได้เปลี่ยนจากรุ่นเป็นรุ่นดังนั้นจึงยังคงเป็นไปได้ในทางเทคนิคเพื่อรองรับ Java 6


23
วิธีแก้ปัญหาการไตร่ตรองอาจละเมิดข้อตกลงสิทธิ์การใช้งาน Java : "F. JAVA TECHNOLOGY ข้อ จำกัด คุณไม่สามารถ ... เปลี่ยนพฤติกรรมของ ... คลาสอินเทอร์เฟซหรือแพคเกจย่อยที่ระบุว่า 'java', 'javax' , 'sun', 'oracle' หรือการประชุมที่คล้ายกัน ... "
M. Dudley

14
@ M.Dudley น่าจะเป็น ตรวจสอบกับทนายความก่อนจัดส่งผลิตภัณฑ์ที่มีรหัสนี้หากเกี่ยวข้องกับคุณ
ntoskrnl

3
@peabody การรวม 100MB JRE กับโปรแกรมของคุณเป็นตัวเลือกในบางกรณี แต่ถ้าไม่มีผู้ใช้จะยังคงต้องติดตั้งไฟล์นโยบายด้วยตนเองแม้ว่าคุณจะรวมไฟล์เหล่านั้นไว้ในโปรแกรมของคุณ (เนื่องจากสาเหตุต่างๆเช่นการอนุญาตไฟล์) จากประสบการณ์ของฉันผู้ใช้หลายคนไม่สามารถทำได้
ntoskrnl

8
ดูเหมือนว่าโซลูชันการสะท้อนกลับเพิ่งหยุดทำงานใน 1.8.0_112 ใช้งานได้ใน 1.8.0_111 แต่ไม่ใช่ 112
John L

3
@JohnL ฉันใช้สิ่งนี้ในแอปพลิเคชัน หลังจากพบปัญหากับfinalฟิลด์ใน 8u111 ฉันแก้ไขเพื่อให้สามารถเปลี่ยนฟิลด์สุดท้ายตามคำตอบนี้ ผลที่ได้คือเรื่องเดียวกันกับรุ่นใหม่ ntoskrnl ยกเว้นว่าผมไม่ได้ประกาศเป็นmodifiersField finalหนึ่งในผู้ใช้ของฉันรายงานว่ามันใช้งานได้ใน 8u112 เช่นกัน
Arjan

87

ตอนนี้ไม่จำเป็นสำหรับJava 9หรือสำหรับ Java 6, 7 หรือ 8 รีลีสล่าสุด :)

สำหรับJDK-8170157นโยบายการเข้ารหัสแบบไม่ จำกัด จะถูกเปิดใช้งานตามค่าเริ่มต้น

รุ่นที่เฉพาะเจาะจงจากปัญหา JIRA:

  • Java 9 (10, 11 เป็นต้น): รีลีสอย่างเป็นทางการ!
  • Java 8u161 หรือใหม่กว่า (วางจำหน่ายแล้ว )
  • Java 7u171 หรือใหม่กว่า (ใช้ได้เฉพาะผ่าน 'My Oracle Support')
  • Java 6u181 หรือใหม่กว่า (ใช้ได้เฉพาะผ่าน 'My Oracle Support')

โปรดทราบว่าหากมีเหตุผลแปลก ๆ ที่จำเป็นต้องมีพฤติกรรมเก่าใน Java 9 ก็สามารถตั้งค่าโดยใช้:

Security.setProperty("crypto.policy", "limited");

4
ในความเป็นจริงนโยบายนี้เป็นค่าเริ่มต้นดังนั้นจึงไม่จำเป็นต้องดำเนินการใด ๆ ใน Java 9!
ntoskrnl

ตั้งแต่ 2018/01/14 (Oracle JDK ล่าสุดคือ 8u151 / 152) นี่ยังไม่เปิดใช้งานโดยค่าเริ่มต้นบน Java 8 ดีกว่าหนึ่งปีหลังจากคำตอบนี้ถูกเขียนขึ้นครั้งแรก ... อย่างไรก็ตามตามjava.com/en/jre -jdk-cryptoroadmap.htmlสิ่งนี้มีไว้สำหรับ GA ใน 2018/01/16
Alex

ในกรณีของฉันและสำหรับฉันที่จะได้รับเครื่องหมายของ A ในเว็บไซต์นี้: ssllabs.com/ssltest ... ฉันต้องตั้งค่าด้วยวิธีนี้: Security.setProperty ("crypto.policy", "unlimited"); จากนั้น ... ตั้งค่า server.ssl.ciphers ในแอปพลิเคชันของฉันคุณสมบัติที่มีอัลกอริทึม 256 ตัวที่ระบุไว้ในบทความนี้ -> weakdh.org/sysadmin.html
Artanis Zeratul

ยังเกี่ยวข้องกับการติดตั้ง OpenJDK 8 ดู: stackoverlow-Article: นโยบาย JCE รวมอยู่กับ openjdk 8 หรือไม่
leole

22

นี่คือวิธีแก้ปัญหา: http://middlesphere-1.blogspot.ru/2014/06/this-code-allows-to-break-limit-if.html

//this code allows to break limit if client jdk/jre has no unlimited policy files for JCE.
//it should be run once. So this static section is always execute during the class loading process.
//this code is useful when working with Bouncycastle library.
static {
    try {
        Field field = Class.forName("javax.crypto.JceSecurity").getDeclaredField("isRestricted");
        field.setAccessible(true);
        field.set(null, java.lang.Boolean.FALSE);
    } catch (Exception ex) {
    }
}

นี่เป็นทางออกเดียวกับของฉันยกเว้นไม่มีส่วน "defaultPolicy" โพสต์ของบล็อกนั้นได้รับคำตอบจากฉันแล้ว
ntoskrnl

1
แต่สิ่งนี้ถูกต้องหรือไม่ รหัสนี้สามารถท้าทายความปลอดภัยของแอปพลิเคชันในแบบเรียลไทม์ได้หรือไม่ ฉันไม่แน่ใจโปรดช่วยฉันเข้าใจว่ามันส่งผลกระทบ
จาน

1
ฉันได้รับข้อผิดพลาดหลังจากทำงานนี้:java.security.InvalidKeyException: Wrong algorithm: AES or Rijndael required
Andy

3
ในฐานะของ Java 8 บิวด์ 111 วิธีการแก้ปัญหานี้จะไม่เพียงพอเนื่องจากisRestrictedฟิลด์กลายเป็นขั้นสุดท้าย ( bugs.openjdk.java.net/browse/JDK-8149417 ) คำตอบของ @ ntoskrnl ดูแลการรวมตัวดัดแปลง "สุดท้าย" ที่เป็นไปได้ ความคิดเห็นของ @ M.Dudley เกี่ยวกับข้อตกลงสิทธิ์การใช้งาน Java ยังคงมีผลบังคับใช้เช่นกัน
MPelletier


13

ตั้งแต่ JDK 8u102 โซลูชันที่โพสต์ซึ่งอาศัยการสะท้อนจะไม่ทำงานอีกต่อไป: ฟิลด์ที่โซลูชันเหล่านี้ตั้งค่าอยู่ในขณะนี้final( https://bugs.openjdk.java.net/browse/JDK-8149417 )

ดูเหมือนว่าจะกลับไปที่ (a) โดยใช้ Bouncy Castle หรือ (b) ติดตั้งไฟล์นโยบาย JCE


7
คุณสามารถใช้การไตร่ตรองไตร่ตรองเพิ่มเติม / คำถาม / 3301635/…
ไฟฟ้าทั่วไป

ใช่โซลูชั่นของ @ M.Dudley จะยังคงใช้งานได้กับisRestrictedฟิลด์เพราะจะดูแลการเพิ่มตัวแก้ไข "ขั้นสุดท้าย" ที่เป็นไปได้
MPelletier

1
รุ่นใหม่ JDK 8u151 มี "คุณสมบัติความปลอดภัยใหม่เพื่อควบคุมนโยบายการเข้ารหัส" บรรทัดล่างสุด: ลบ "#" ออกจากบรรทัด "# crypto.policy = unlimited" ใน "lib \ security \ java.security": oracle.com/technetwork/java/javase/8u151-relnotes-3850493.html
hemisphire

8

สำหรับห้องสมุดเข้ารหัสทางเลือกที่มีลักษณะที่ปราสาท Bouncy มันมี AES และฟังก์ชั่นเพิ่มเติมมากมาย มันเป็นไลบรารีโอเพนซอร์ซเสรี คุณจะต้องใช้ Bouncy Castle API ที่มีน้ำหนักเบาและเป็นเจ้าของเพื่อให้ใช้งานได้


19
พวกเขาเป็นผู้ให้บริการเข้ารหัสลับที่ยอดเยี่ยม แต่ยังคงต้องการไฟล์ JCE ที่ไม่ จำกัด จำนวนเพื่อให้สามารถทำงานกับคีย์ขนาดใหญ่ได้
John Meagher

16
หากคุณใช้ Bouncy Castle API โดยตรงคุณไม่จำเป็นต้องใช้ไฟล์ความแข็งแกร่งแบบไม่ จำกัด
laz

4

คุณสามารถใช้วิธีการ

javax.crypto.Cipher.getMaxAllowedKeyLength(String transformation)

เพื่อทดสอบความยาวของคีย์ที่มีให้ใช้และแจ้งผู้ใช้เกี่ยวกับสิ่งที่เกิดขึ้น สิ่งที่ระบุว่าแอปพลิเคชันของคุณกลับไปเป็นคีย์ 128 บิตเนื่องจากไฟล์นโยบายไม่ได้รับการติดตั้งตัวอย่างเช่น ผู้ใช้ที่ใส่ใจเรื่องความปลอดภัยจะทำการติดตั้งไฟล์นโยบายส่วนคนอื่น ๆ จะใช้คีย์ที่อ่อนกว่านี้ต่อไป


3

สำหรับแอปพลิเคชันของเราเรามีสถาปัตยกรรมเซิร์ฟเวอร์ไคลเอ็นต์และเราอนุญาตให้ถอดรหัส / เข้ารหัสข้อมูลในระดับเซิร์ฟเวอร์เท่านั้น ดังนั้นไฟล์ JCE จึงจำเป็นเท่านั้น

เรามีปัญหาอื่นที่เราจำเป็นต้องปรับปรุง jar การรักษาความปลอดภัยบนเครื่องไคลเอนต์ผ่าน JNLP มันจะเขียนทับไลบรารีใน${java.home}/lib/security/และ JVM ในการรันครั้งแรก

นั่นทำให้มันใช้งานได้


2

นี่คือคำตอบntoskrnlรุ่นที่ปรับปรุงแล้ว นอกจากนี้ยังมีฟังก์ชั่นเพื่อลบตัวดัดแปลงสุดท้ายเช่นArjan ที่กล่าวถึงในความคิดเห็น

รุ่นนี้ใช้งานได้กับ JRE 8u111 หรือใหม่กว่า

private static void removeCryptographyRestrictions() {
    if (!isRestrictedCryptography()) {
        return;
    }
    try {
        /*
         * Do the following, but with reflection to bypass access checks:
         * 
         * JceSecurity.isRestricted = false; JceSecurity.defaultPolicy.perms.clear();
         * JceSecurity.defaultPolicy.add(CryptoAllPermission.INSTANCE);
         */
        final Class<?> jceSecurity = Class.forName("javax.crypto.JceSecurity");
        final Class<?> cryptoPermissions = Class.forName("javax.crypto.CryptoPermissions");
        final Class<?> cryptoAllPermission = Class.forName("javax.crypto.CryptoAllPermission");

        Field isRestrictedField = jceSecurity.getDeclaredField("isRestricted");
        isRestrictedField.setAccessible(true);
        setFinalStatic(isRestrictedField, true);
        isRestrictedField.set(null, false);

        final Field defaultPolicyField = jceSecurity.getDeclaredField("defaultPolicy");
        defaultPolicyField.setAccessible(true);
        final PermissionCollection defaultPolicy = (PermissionCollection) defaultPolicyField.get(null);

        final Field perms = cryptoPermissions.getDeclaredField("perms");
        perms.setAccessible(true);
        ((Map<?, ?>) perms.get(defaultPolicy)).clear();

        final Field instance = cryptoAllPermission.getDeclaredField("INSTANCE");
        instance.setAccessible(true);
        defaultPolicy.add((Permission) instance.get(null));
    }
    catch (final Exception e) {
        e.printStackTrace();
    }
}

static void setFinalStatic(Field field, Object newValue) throws Exception {
      field.setAccessible(true);

      Field modifiersField = Field.class.getDeclaredField("modifiers");
      modifiersField.setAccessible(true);
      modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);

      field.set(null, newValue);
   }

private static boolean isRestrictedCryptography() {
    // This simply matches the Oracle JRE, but not OpenJDK.
    return "Java(TM) SE Runtime Environment".equals(System.getProperty("java.runtime.name"));
}

มันใช้งานได้ดี แต่บรรทัดนั้น((Map<?, ?>) perms.get(defaultPolicy)).clear();ทำให้เกิดข้อผิดพลาดของคอมไพเลอร์ การใส่ความคิดเห็นดูเหมือนจะไม่ส่งผลกระทบต่อการทำงานของมัน บรรทัดนี้จำเป็นหรือไม่?
Andreas Unterweger

2

นี่คือเวอร์ชันที่แก้ไขของโค้ดของ @ ntoskrnl ที่มีการisRestrictedCryptographyตรวจสอบตามจริงCipher.getMaxAllowedKeyLengthการบันทึก slf4j และการสนับสนุนการกำหนดค่าเริ่มต้นแบบซิงเกิลจาก application bootstrap เช่นนี้:

static {
    UnlimitedKeyStrengthJurisdictionPolicy.ensure();
}

รหัสนี้จะหยุดยั้งการสะท้อนอย่างถูกต้องเมื่อเริ่มใช้นโยบายแบบไม่ จำกัด ใน Java 8u162 ตามที่ @ cranphin คำตอบคาดการณ์ไว้


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.security.NoSuchAlgorithmException;
import java.security.Permission;
import java.security.PermissionCollection;
import java.util.Map;

// /programming/1179672/how-to-avoid-installing-unlimited-strength-jce-policy-files-when-deploying-an
public class UnlimitedKeyStrengthJurisdictionPolicy {

    private static final Logger log = LoggerFactory.getLogger(UnlimitedKeyStrengthJurisdictionPolicy.class);

    private static boolean isRestrictedCryptography() throws NoSuchAlgorithmException {
        return Cipher.getMaxAllowedKeyLength("AES/ECB/NoPadding") <= 128;
    }

    private static void removeCryptographyRestrictions() {
        try {
            if (!isRestrictedCryptography()) {
                log.debug("Cryptography restrictions removal not needed");
                return;
            }
            /*
             * Do the following, but with reflection to bypass access checks:
             *
             * JceSecurity.isRestricted = false;
             * JceSecurity.defaultPolicy.perms.clear();
             * JceSecurity.defaultPolicy.add(CryptoAllPermission.INSTANCE);
             */
            Class<?> jceSecurity = Class.forName("javax.crypto.JceSecurity");
            Class<?> cryptoPermissions = Class.forName("javax.crypto.CryptoPermissions");
            Class<?> cryptoAllPermission = Class.forName("javax.crypto.CryptoAllPermission");

            Field isRestrictedField = jceSecurity.getDeclaredField("isRestricted");
            isRestrictedField.setAccessible(true);
            Field modifiersField = Field.class.getDeclaredField("modifiers");
            modifiersField.setAccessible(true);
            modifiersField.setInt(isRestrictedField, isRestrictedField.getModifiers() & ~Modifier.FINAL);
            isRestrictedField.set(null, false);

            Field defaultPolicyField = jceSecurity.getDeclaredField("defaultPolicy");
            defaultPolicyField.setAccessible(true);
            PermissionCollection defaultPolicy = (PermissionCollection) defaultPolicyField.get(null);

            Field perms = cryptoPermissions.getDeclaredField("perms");
            perms.setAccessible(true);
            ((Map<?, ?>) perms.get(defaultPolicy)).clear();

            Field instance = cryptoAllPermission.getDeclaredField("INSTANCE");
            instance.setAccessible(true);
            defaultPolicy.add((Permission) instance.get(null));

            log.info("Successfully removed cryptography restrictions");
        } catch (Exception e) {
            log.warn("Failed to remove cryptography restrictions", e);
        }
    }

    static {
        removeCryptographyRestrictions();
    }

    public static void ensure() {
        // just force loading of this class
    }
}

-1

ระหว่างการติดตั้งโปรแกรมของคุณเพียงแจ้งผู้ใช้และมีสคริปต์ Batch DOS หรือดาวน์โหลดสคริปต์ Bash shell และคัดลอก JCE ไปยังตำแหน่งระบบที่เหมาะสม

ฉันเคยต้องทำสิ่งนี้เพื่อเว็บเซอร์ฟเวอร์ของเซิร์ฟเวอร์และแทนที่จะเป็นผู้ติดตั้งอย่างเป็นทางการฉันเพิ่งให้สคริปต์เพื่อติดตั้งแอปก่อนที่ผู้ใช้จะสามารถเรียกใช้งานได้ คุณสามารถทำให้แอปไม่สามารถรันได้จนกว่าจะรันสคริปต์การตั้งค่า คุณสามารถทำให้แอปบ่นว่า JCE หายไปจากนั้นขอให้ดาวน์โหลดและรีสตาร์ทแอปได้หรือไม่


7
"ทำให้แอปของฉันทำงานโดยไม่เขียนทับไฟล์บนเครื่องของผู้ใช้ปลาย"
เอริก

ฉันแก้ไขคำตอบทั้งหมดตั้งแต่คำตอบเริ่มต้นของฉันผิด
djangofan
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.