赞
踩
背景:工作需要,领导让去研究阿里云视频点播,毕竟害怕付费视频被二次转发,导致视频的不安全。
前期准备:
1)开启视频点播控制台。
2)设置转码模板组,因为看文档说加密有标准HLS加密和阿里私密加密和DRM加密(商业一点,贵贵),同时阿里私密加密有个不足就是IOS网页不能播放,所以这里使用HLS加密了,在这边也需要做点操作。
具体某个画质里面,设置封装格式为HLS,高级参数那边设置私密加密。
3)域名管理
只有添加分发加速的域名才能使用HLS加密,同时也要做HTTPS证书添加,不然也会报错。
具体域名怎么配置可以看文档。
3)开启写代码了,做好依赖注入。
- <dependency>
- <groupId>com.aliyun</groupId>
- <artifactId>aliyun-java-sdk-core</artifactId>
- <version>4.5.1</version>
- </dependency>
- <dependency>
- <groupId>com.aliyun</groupId>
- <artifactId>aliyun-java-sdk-vod</artifactId>
- <version>2.15.11</version>
- </dependency>
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>fastjson</artifactId>
- <version>1.2.62</version>
- </dependency>
- <dependency>
- <groupId>com.aliyun</groupId>
- <artifactId>aliyun-java-sdk-kms</artifactId>
- <version>2.10.1</version>
- </dependency>
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
4)获得上传凭证和重新获得上传凭证接口。这边采用前端上传视频,前端在一开始调用上传凭证是需要给fileName和title。重新获得凭证是在视频上传超时之后重新调用获得凭证,这是只需要一个videoID;
- /**
- * 获取视频上传地址和凭证
- * @return CreateUploadVideoResponse 获取视频上传地址和凭证响应数据
- */
- public static BaseVideo createUploadVideo(BaseUpload baseUpload){
- DefaultAcsClient client = initVodClient();
- CreateUploadVideoRequest request = new CreateUploadVideoRequest();
- request.setTitle(baseUpload.getTitle());
- request.setFileName(baseUpload.getFileName());
- BaseVideo baseVideo = new BaseVideo();
- CreateUploadVideoResponse response = new CreateUploadVideoResponse();
- try {
- response=client.getAcsResponse(request);
- baseVideo.setUploadAddress(response.getUploadAddress());
- baseVideo.setVideoId(response.getVideoId());
- baseVideo.setUploadAuth(response.getUploadAuth());
- }catch (Exception e){
- baseVideo.setErrorMessage(e.getLocalizedMessage());
- }finally {
- baseVideo.setRequestId(response.getRequestId());
- }
- return baseVideo;
- }
-
-
- /**
- * 刷新视频上传凭证
- * @return RefreshUploadVideoResponse 刷新视频上传凭证响应数据
- */
- public static BaseVideo refreshUploadVideo(String VideoId ){
- DefaultAcsClient client = initVodClient();
- RefreshUploadVideoRequest request = new RefreshUploadVideoRequest();
- request.setVideoId(VideoId);
- BaseVideo baseVideo = new BaseVideo();
- RefreshUploadVideoResponse response = new RefreshUploadVideoResponse();
- try {
- response=client.getAcsResponse(request);
- baseVideo.setUploadAddress(response.getUploadAddress());
- baseVideo.setVideoId(VideoId);
- baseVideo.setUploadAuth(response.getUploadAuth());
- }catch (Exception e){
- baseVideo.setErrorMessage(e.getLocalizedMessage());
- }finally {
- baseVideo.setRequestId(response.getRequestId());
- }
- return baseVideo;
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
5)上传成功后得到成功上传的回调信息,进行HLS加密。
先设置那些回调信息可以通过接口回调出来。
当然回调要是有人恶意多次请求该接口,会出现很多问题,所以需要进行一个回调鉴权。
- /**
- * 回调比较是否合法
- */
- public static Integer compareSignature(String url,String time,String key,String signature){
- Digester digester = new Digester(DigestAlgorithm.MD5);
- String digestHex = digester.digestHex(url+"|"+time+"|"+key);
- long localtime = System.currentTimeMillis() / 1000;
- long oldtime=Long.parseLong(time);
- if (localtime-oldtime>300000){
- return 2;
- }
- System.out.println(digestHex);
- System.out.println(signature);
- if (digestHex.equals(signature)){
- return 0;
- }else {
- return 1;
- }
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
成功回调之后,就可以进行转码作业了
- /**
- * 提交媒体处理作业
- */
- public static BaseCommit submitTranscodeJobs(String VideoId){
-
- try {
- DefaultAcsClient client = initVodClient();
- SubmitTranscodeJobsRequest request = new SubmitTranscodeJobsRequest();
- request.setVideoId(VideoId);
- request.setTemplateGroupId("44b01537a7bb10990e101f812d659478");
- JSONObject encryptConfig = buildEncryptConfig();
- //HLS标准加密配置(只有标准加密才需要传递)
- request.setEncryptConfig(encryptConfig.toJSONString());
- SubmitTranscodeJobsResponse acsResponse;
- acsResponse = client.getAcsResponse(request);
- BaseCommit baseCommit = new BaseCommit();
- baseCommit.setCiphertext(encryptConfig.get("CipherText").toString());
- baseCommit.setMtsHlsUriToken(encryptConfig.getString("MtsHlsUriToken"));
- baseCommit.setJobId(acsResponse.getTranscodeJobs().get(0).getJobId());
- return baseCommit;
- }catch (Exception e){
- e.printStackTrace();
- return null;
- }
- }
- /**
- * 构建HLS标准加密的配置信息
- * @return
- * @throws ClientException
- */
- public static JSONObject buildEncryptConfig() throws ClientException {
- DefaultAcsClient client = initVodClient();
- GenerateDataKeyResponse response = generateDataKey(client, serviceKey);
- JSONObject encryptConfig = new JSONObject();
- PlayToken playToken = new PlayToken();
- try {
- // String token = playToken.generateToken("sh12345678912345");
- encryptConfig.put("DecryptKeyUri", "http://IP:10089/decrypt?CipherText=" + response.getCiphertextBlob()+"&MtsHlsUriToken="+"HiZZg7kx0lUFWcByN9mGMG8V2SvprV07psRPFdM/f50=");
- encryptConfig.put("KeyServiceType", "KMS");
- encryptConfig.put("CipherText", response.getCiphertextBlob());
- encryptConfig.put("MtsHlsUriToken","HiZZg7kx0lUFWcByN9mGMG8V2SvprV07psRPFdM/f50=");
- return encryptConfig;
- } catch (Exception e) {
- e.printStackTrace();
- }
- return null;
-
- }
-
-
- /**
- * 生成加密需要的密钥,response中包含密文密钥和明文密钥,用户只需要将密文密钥传递给点播即可
- * 注意:KeySpec 必须传递AES_128,且不能设置NumberOfBytes
- * @param client KMS-SDK客户端
- * @param serviceKey 点播提供生成密钥的service key,在用户的密钥管理服务中可看到描述为vod的加密key
- * @return
- * @throws ClientException
- */
- public static GenerateDataKeyResponse generateDataKey(DefaultAcsClient client, String serviceKey) throws ClientException {
- GenerateDataKeyRequest request = new GenerateDataKeyRequest();
- request.setKeyId(serviceKey);
- request.setKeySpec("AES_128");
- return client.getAcsResponse(request);
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
看控制台的视频地址,要是有一个画面格式的mp4和别的进行转码成功的m3u8并带有标准加密,就意味着加密成功。
最后就是解密去看视频了。
在加密转码接口之中有一个小细节。
看了官网有个解密的服务。
直接可以用,但是推荐把token放入数据库,我还没做好。这个具体按照业务来嘛。
- //加密服务
- public class PlayToken {
- //非AES生成方式,无需以下参数
- private static String ENCRYPT_KEY = "1234561112345678"; //加密字符串,用户自行定义
- private static String INIT_VECTOR = "123456789123456g"; //长度为16的自定义字符串,不能有特殊字符。
- public static void main(String[] args) throws Exception {
- PlayToken playToken = new PlayToken();
- playToken.generateToken("sh12345678912349");
- }
- /**
- * 根据传递的参数生成令牌
- * 说明:
- * 1、参数可以是业务方的用户ID、播放终端类型等信息
- * 2、调用令牌接口时生成令牌Token
- * @param args
- * @return
- */
- public String generateToken(String... args) throws Exception {
- if (null == args || args.length <= 0) {
- return null;
- }
- String base = StringUtils.join(Arrays.asList(args), "_");
- //设置30S后,该token过期,过期时间可以自行调整
- long expire = System.currentTimeMillis() + 30000L;
- base += "_" + expire; //base最终的字符串长度和时间戳一起要保证是16位(其中时间戳13位),用户可以自行更改。
- //生成token
- String token = encrypt(base, ENCRYPT_KEY);
- System.out.println(token);
- //保存token,用于解密时校验token的有效性,例如:过期时间、token的使用次数
- saveToken(token);
- return token;
- }
- /**
- * 验证token的有效性
- * 说明:
- * 1、解密接口在返回播放密钥前,需要先校验Token的合法性和有效性
- * 2、强烈建议同时校验Token的过期时间以及Token的有效使用次数
- * @param token
- * @return
- * @throws Exception
- */
- public boolean validateToken(String token) throws Exception {
- if (null == token || "".equals(token)) {
- return false;
- }
- String base = decrypt(token, ENCRYPT_KEY);
- //先校验token的有效时间
- Long expireTime = Long.valueOf(base.substring(base.lastIndexOf("_") + 1));
- if (System.currentTimeMillis() > expireTime) {
- return false;
- }
- //从DB获取token信息,判断token的有效性,业务方可自行实现
- Token dbToken = getToken(token);
- //判断是否已经使用过该token
- if (dbToken == null || dbToken.useCount > 0) {
- return false;
- }
- //获取到业务属性信息,用于校验
- String businessInfo = base.substring(0, base.lastIndexOf("_"));
- String[] items = businessInfo.split("_");
- //校验业务信息的合法性,业务方实现
- return validateInfo(items);
- }
- /**
- * 保存Token到DB
- * 业务方自行实现
- *
- * @param token
- */
- public void saveToken(String token) {
- System.out.println(token);
-
- //TODO 存储Token
- }
- /**
- * 查询Token
- * 业务方自行实现
- *
- * @param token
- */
- public Token getToken(String token) {
- //TODO 从DB 获取Token信息,用于校验有效性和合法性
- return null;
- }
- /**
- * 校验业务信息的有效性,业务方可自行实现
- *
- * @param infos
- * @return
- */
- public boolean validateInfo(String... infos) {
- //TODO 校验信息的有效性,例如UID是否有效等
- return true;
- }
- /**
- * AES加密生成Token
- *
- * @param key
- * @param value
- * @return
- * @throws Exception
- */
- public String encrypt(String value, String key) throws Exception {
- IvParameterSpec e = new IvParameterSpec(INIT_VECTOR.getBytes("UTF-8"));
- SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
- Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
- cipher.init(Cipher.ENCRYPT_MODE, skeySpec, e);
- byte[] encrypted = cipher.doFinal(value.getBytes());
- return Base64.encodeBase64String(encrypted);
- }
- /**
- * AES解密token
- *
- * @param key
- * @param encrypted
- * @return
- * @throws Exception
- */
- public String decrypt(String encrypted, String key) throws Exception {
- IvParameterSpec e = new IvParameterSpec(INIT_VECTOR.getBytes("UTF-8"));
- SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
- Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
- cipher.init(Cipher.DECRYPT_MODE, skeySpec, e);
- byte[] original = cipher.doFinal(Base64.decodeBase64(encrypted));
- return new String(original);
- }
- /**
- * Token信息,业务方可提供更多信息,这里仅仅给出示例
- */
- class Token {
- //Token的有效使用次数,分布式环境需要注意同步修改问题
- int useCount;
- //token内容
- String token;
- }}
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
- //解密服务
- public class HlsDecryptServer {
- private static DefaultAcsClient client;
- static {
- //KMS的区域,必须与视频对应区域
- String region = "";
- //访问KMS的授权AccessKey信息
- String accessKeyId="";
- String accessKeySecret="";
- client = new DefaultAcsClient(DefaultProfile.getProfile(region, accessKeyId, accessKeySecret));
- }
- /**
- * 说明:
- * 1、接收解密请求,获取密文密钥和令牌Token
- * 2、调用KMS decrypt接口获取明文密钥
- * 3、将明文密钥base64decode返回
- */
- public class HlsDecryptHandler implements HttpHandler {
- /**
- * 处理解密请求
- * @param httpExchange
- * @throws IOException
- */
- public void handle(HttpExchange httpExchange) throws IOException {
- String requestMethod = httpExchange.getRequestMethod();
- if ("GET".equalsIgnoreCase(requestMethod)) {
- //校验token的有效性
- String token = getMtsHlsUriToken(httpExchange);
- System.out.println("hh"+token);
- boolean validRe = validateToken(token);
- if (!validRe) {
- return;
- }
- //从URL中取得密文密钥
- String ciphertext = getCiphertext(httpExchange);
- if (null == ciphertext)
- return;
- //从KMS中解密出来,并Base64 decode
- byte[] key = decrypt(ciphertext);
- //设置header
- setHeader(httpExchange, key);
- //返回base64decode之后的密钥
- OutputStream responseBody = httpExchange.getResponseBody();
- responseBody.write(key);
- responseBody.close();
- }
- }
- private void setHeader(HttpExchange httpExchange, byte[] key) throws IOException {
- Headers responseHeaders = httpExchange.getResponseHeaders();
- responseHeaders.set("Access-Control-Allow-Origin", "*");
- httpExchange.sendResponseHeaders(HttpURLConnection.HTTP_OK, key.length);
- }
- /**
- * 调用KMS decrypt接口解密,并将明文base64decode
- * @param ciphertext
- * @return
- */
- private byte[] decrypt(String ciphertext) {
- DecryptRequest request = new DecryptRequest();
- request.setCiphertextBlob(ciphertext);
- request.setProtocol(ProtocolType.HTTPS);
- try {
- DecryptResponse response = client.getAcsResponse(request);
- String plaintext = response.getPlaintext();
- //注意:需要base64 decode
- return Base64.decodeBase64(plaintext);
- } catch (ClientException e) {
- e.printStackTrace();
- return null;
- }
- }
- /**
- * 校验令牌有效性
- * @param token
- * @return
- */
- private boolean validateToken(String token) {
- if (null == token || "".equals(token)) {
- return false;
- }
- //TODO 业务方实现令牌有效性校验
- return true;
- }
- /**
- * 从URL中获取密文密钥参数
- * @param httpExchange
- * @return
- */
- private String getCiphertext(HttpExchange httpExchange) {
- URI uri = httpExchange.getRequestURI();
- String queryString = uri.getQuery();
- String pattern = "CipherText=(\\w*)";
- Pattern r = Pattern.compile(pattern);
- Matcher m = r.matcher(queryString);
- if (m.find())
- return m.group(1);
- else {
- System.out.println("Not Found CipherText Param");
- return null;
- }
- }
- /**
- * 获取Token参数
- *
- * @param httpExchange
- * @return
- */
- private String getMtsHlsUriToken(HttpExchange httpExchange) {
- URI uri = httpExchange.getRequestURI();
- String queryString = uri.getQuery();
- String pattern = "MtsHlsUriToken=(\\w*)";
- Pattern r = Pattern.compile(pattern);
- Matcher m = r.matcher(queryString);
- if (m.find())
- return m.group(1);
- else {
- System.out.println("Not Found MtsHlsUriToken Param");
- return null;
- }
- }
- }
- /**
- * 服务启动
- *
- * @throws IOException
- */
- public void serviceBootStrap() throws IOException {
- HttpServerProvider provider = HttpServerProvider.provider();
- //监听端口可以自定义,能同时接受最多30个请求
- HttpServer httpserver = provider.createHttpServer(new InetSocketAddress(10089), 30);
- httpserver.createContext("/", new HlsDecryptHandler());
- httpserver.start();
- System.out.println("hls decrypt server started");
- }
-
-
- public static void main(String[] args) throws IOException {
- HlsDecryptServer server = new HlsDecryptServer();
- server.serviceBootStrap();
- }}
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
尤其让我困惑好久的是这边解密的端口号是和上述uri一样的端口号,我这个研究了一天,我好像一个憨批。
最后把解密服务当做一个bean,当系统运行的时候,服务也就开着了。
大致说下感受:加密还是蛮简单的,解密的话就是我后端从阿里云得到视频的地址(加密m3u8格式),播放器知道这个是加密视频,就会通过解密接口来进行解密,最后就是解密之后的播放地址。其实也还行,就是文档有点杂,要东拼西凑的看东西。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。