Safari推送通知

5
我希望为我的网站实现推送通知(仅在 Safari 7 兼容的浏览器中)。 我已经阅读了 Apple 的文档,并成功创建了包含图标.iconset、证书.p12、manifest.json 和 website.json 的包。 现在,当我第一次访问网站时,我想要请求用户的权限。如果他同意,我应该发送这个包。 一切都很清楚,但我不知道该怎么继续下去。
如何从我的文件创建推送包?如何精确地签名它?这个包应该始终是相同的,所以我可以在我的 Mac 上签名并仅上传一个包到我的服务器。
如果您有此技术的经验,请告诉我 :)
3个回答

4
我已经成功地使用Java的REST API创建了推送包,以请求Safari Web推送通知的授权。此外,我遵循了苹果官方网站提供的步骤。
请按照以下步骤创建推送包。
1.从您的苹果帐户创建Web推送通知P12证书。 2.使用包含icon.iconset、certificate.p12、manifest.json和website.json的https for pushPackage创建REST API。p12证书必须是Web推送通知证书。 3.如何创建推送包:请参考下面的Java代码。我使用Java Servlet创建了推送包,提供了2个Web服务端点。
v1/pushpackage/webpushID
v1/log
该Servlet处理Safari推送通知方法发送的推送包请求。
SafariPushPakageAPI.java /* Push Package REST API handler */
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;

import com.safari.Packager;

public class SafariPushPakageAPI extends HttpServlet{

    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    public static  String ServerPath = null;
    private static final String REQUEST_PERMISSION = "/v1/pushPackages/YOUR_WEB_PUSH_ID";
    private static final String REQUEST_ERRORLOG = "/v1/log";

    @Override
    public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doRequest(request, response);
    }
//  /v1/pushPackages/webpushID
//  /v1/log
    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doRequest(request, response);
    }

    private void doRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("===>> SAFARI PUSH NOTIFICATION REQUEST");
        String path = request.getPathInfo();
        System.out.println("PATH ===>> "+path);
        if(path == null){
            doRequestPermission(request, response);
        }else if (path.equalsIgnoreCase(REQUEST_PERMISSION)){
            doRequestPermission(request, response);
        }else if (path.equalsIgnoreCase(REQUEST_ERRORLOG)){
            doRequestShowErrorLog(request, response);
        }else{
            doRequestPermission(request, response);
        }
    }

    private void doRequestPermission(HttpServletRequest request,HttpServletResponse response) {
        try{
            System.out.println("INSIDE REQUEST PERMISSION ==>>>");
            System.out.println(IOUtils.toString(request.getReader()));
            String authToken = StringUtils.isBlank(request.getParameter("token")) ? "UserTokenRT124DFGH" : StringUtils.trimToEmpty(request.getParameter("token"));
            System.out.println("=>>>>>>>>>> USER TOKEN =>>>>>>>>>> "+authToken);
            @SuppressWarnings("deprecation")
            String packagePath =request.getRealPath("pushPackage.raw/icon.iconset/"); // LOCATION WHERE YOUR PUSH PACKAGE FOLDER CONTAIN LOGOS AND website.json file
            response.setContentType("application/zip");
            response.setHeader("Content-Disposition", "attachment;filename=\"pushpackage.zip\"");
            OutputStream out = response.getOutputStream();
            out.write(Packager.createPackageFile(authToken,packagePath));
            response.flushBuffer();
        }catch(IOException ioe){
            ioe.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }   
    }

    private void doRequestShowErrorLog(HttpServletRequest request,HttpServletResponse response) {
        try{
            System.out.println("ERROR LOG STARTED");
            System.out.println(IOUtils.toString(request.getReader()));
            System.out.println("END");
        }catch(Exception e){
            e.printStackTrace();
        }
    }

}

Packager.java

import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

import org.apache.commons.io.IOUtils;
import org.json.JSONArray;
import org.json.JSONObject;

/**
 *
 * @author Ritesh
 */
public class Packager {

    final static String CERTIFICATE_PATH="PATH TO YOUR 12 CERTIFICATE";
    final static String CERTIFICATE_PASS="PASSWORD";

    static String getJSON(String authenticationToken) throws Exception {
        JSONObject obj = new JSONObject();
        obj.put("websiteName", "WEB SITE NAME");
        obj.put("websitePushID", "WEB PUSH ID");
        obj.put("allowedDomains", new JSONArray());

        obj.getJSONArray("allowedDomains").put("https://TEST.EXAMPLE.net");//LIST OF DOMAINS ALLOW

        obj.put("urlFormatString", "https://TEST.EXAMPLE.net/%@");
        obj.put("authenticationToken", authenticationToken);
        obj.put("webServiceURL", "https://API.EXAMPLE.COM");//callback URL WITHOUT WEB SERVICE ENDPOINT NAME

        return obj.toString();
    }

    public static byte[] createPackageFile(String authenticationToken, String path) throws Exception {

        System.out.println("packaging safari file with token: " + authenticationToken);
        ZipHandler zip = new ZipHandler();
        File dir = new File(path);

        for (File file : dir.listFiles()) {          
             InputStream is = new FileInputStream(file);
             byte[] bytes = IOUtils.toByteArray(is);
             zip.addFile("icon.iconset", file.getName(),bytes );
        }       

        zip.addFile("", "website.json", getJSON(authenticationToken).getBytes());

        byte[] manifest = zip.manifest();
        zip.addFile("", "manifest.json", manifest);

        zip.addFile("", "signature", sign(manifest));

        return zip.getBytes();

    }

    static byte[] sign(byte bytesToSign[]) throws Exception {
        return new PKCS7Signer().sign(CERTIFICATE_PATH,CERTIFICATE_PASS, bytesToSign);
    }

    /**
     * Servlet handler , should listen on the callback URL (as in webServiceURL)
     * @param requestPath
     * @param req
     * @param servletRequest
     * @param servletResponse
     * @throws Exception
     */


    public static void main(String[] args) throws Exception {
        Packager.createPackageFile("SafriNotifcation","");
    }               

}

PKCS7Signer.java 创建您的签名文件。

import java.io.FileInputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Security;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaCertStore;
import org.bouncycastle.cms.CMSProcessableByteArray;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.CMSSignedDataGenerator;
import org.bouncycastle.cms.CMSTypedData;
import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import org.bouncycastle.util.Store;

public final class PKCS7Signer {

    static {
        try{
            Security.addProvider(new BouncyCastleProvider());
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    private KeyStore getKeystore(String storeLocation, String storePasswd) throws Exception {
        if (storeLocation == null) {
            System.out.println("Could not find store file (.p12)");
            return null;
        }
        // First load the keystore object by providing the p12 file path
        KeyStore clientStore = KeyStore.getInstance("PKCS12");
        // replace testPass with the p12 password/pin
        clientStore.load(new FileInputStream(storeLocation), storePasswd.toCharArray());
        return clientStore;
    }

    private X509CertificateHolder getCert(KeyStore keystore, String alias) throws Exception {
        java.security.cert.Certificate c = keystore.getCertificate(alias);
        return new X509CertificateHolder(c.getEncoded());
    }

    private PrivateKey getPrivateKey(KeyStore keystore, String alias, String storePasswd) throws Exception {
        return (PrivateKey) keystore.getKey(alias, storePasswd.toCharArray());
    }

    public byte[] sign(String storeLocation, String storePasswd, byte[] dataToSign) throws Exception {
        KeyStore clientStore = getKeystore(storeLocation, storePasswd);

        if (clientStore == null) {
            return null;
        }
        Enumeration aliases = clientStore.aliases();
        String alias = "";
        while (aliases.hasMoreElements()) {
            alias = (String) aliases.nextElement();
            if (clientStore.isKeyEntry(alias)) {
                break;
            }
        }

        CMSTypedData msg = new CMSProcessableByteArray(dataToSign); // Data to sign

        X509CertificateHolder x509Certificate = getCert(clientStore, alias);
        List certList = new ArrayList();
        certList.add(x509Certificate); // Adding the X509 Certificate

        Store certs = new JcaCertStore(certList);

        CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
        // Initializing the the BC's Signer
        ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA").setProvider("BC").build(
                getPrivateKey(clientStore, alias, storePasswd));

        gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder()
                .setProvider("BC").build()).build(sha1Signer, x509Certificate));
        // adding the certificate
        gen.addCertificates(certs);
        // Getting the signed data
        CMSSignedData sigData = gen.generate(msg, false);
        return sigData.getEncoded();
    }
}

请注意使用最新的Bouncy Castle Jars来创建签名文件 bcprov-jdk15on-157.jar bcpkix-jdk15on-157.jar 4. 在客户端页面上编写您的JavaScript。它包含简单的JavaScript代码以获取权限。
///
// 适用于Safari var domain="YOUR WEB PUSH ID";
function safariIniti() {

    var pResult = window.safari.pushNotification.permission(domain);

    if(pResult.permission === 'default') {
        //request permission
        requestPermissions();
    } else if (pResult.permission === 'granted') {
        console.log("Permission for " + domain + " is " + pResult.permission);
        var token = pResult.deviceToken;
        // Show subscription for debug
        console.log('Subscription details:'+token);
    } else if(pResult.permission === 'denied') {
        console.log("Permission for " + domain + " is " + pResult.permission);
    }
}

function getToken(){

    // always start with a letter (for DOM friendlyness)
    var idstr=String.fromCharCode(Math.floor((Math.random()*25)+65));
    do {                
        // between numbers and characters (48 is 0 and 90 is Z (42-48 = 90)
        var ascicode=Math.floor((Math.random()*42)+48);
        if (ascicode<58 || ascicode>64){
            // exclude all chars between : (58) and @ (64)
            idstr+=String.fromCharCode(ascicode);    
        }                
    } while (idstr.length<32);

    return (idstr);
}


function requestPermissions() {

    var tokenVal = getToken();
    window.safari.pushNotification.requestPermission('WEb service url without end points',domain,{token:tokenVal},
function(subscription) {


        console.log(subscription.permission);
        console.log("PERMISSION ====>> "+subscription.permission);
        if(subscription.permission === 'granted') {
            //TODO
        }
        else if(subscription.permission === 'denied') {
            // TODO:
        }
    });

}

2
苹果公司提供了一个php文件,您可以使用它来创建包括签名在内的推送包。 https://developer.apple.com/library/mac/documentation/NetworkingInternet/Conceptual/NotificationProgrammingGuideForWebsites/CompanionFile.zip 另外,您也可以使用push_package gem,https://github.com/SymmetricInfinity/push_package,我们在为zeropush.com实现safari推送通知时开发了这个gem。更多详细信息请参见https://zeropush.com/blog/implementing-safari-push-notifications-in-osx-mavericks

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接