用 Java 构建声网令牌服务器

视频聊天应用程序的安全至关重要。声网平台的安全防护之一是令牌(Token)认证。本指南的主要内容是使用 Java 和 Jersey 构建简单的微服务以生成声网 RTE 令牌。


前期准备

  1. Apache Tomcat、IntelliJ IDEA IDE、Jersey 框架和 Docker;
  2. 对 REST 框架工作原理有基本了解;
  3. 对 Java 类有基本了解;
  4. 声网开发者帐户(详细步骤可参考这篇文章)。


主要事项

  1. 构建一个 Jersey 项目;
  2. 创建一个 Hello World API;
  3. 生成声网令牌;
  4. 生成 Dockerize 应用程序并将其部署在 Ubuntu VM 上。


创建项目

Archetype:Archetype 是一个 Maven 项目模板工具包。

ArtifactArtifact 是一个文件,通常是一个部署在 Maven 资源库的 JAR 文件。构建好的 Maven 能产生一个或多个 artifacts,比如,已编译的 JAR 文件和 JAR 源文件。

jersey-quickstart-webapp 提供基本的项目模板,对初学者非常有用。

打开 IntelliJ IDE ,然后单击“开始新项目”,我们用 Maven 创建新项目。单击 Archetype 中的创建,添加以下配置的 Archetype

Group Id: org.glassfish.jersey.archetypes
Artifact Id: jersey-quickstart-webapp
Version: 2.31

项目的最终配置如下:

项目创建完成后,添加 Tomcat 服务器,大家可以从 Tomcat 官方网站获取 Tomcat。Tomcat 安装完成后,单击创建图标旁边的添加配置。

在模板中选择本地 Tomcat 服务器,最终配置如下:


完成后应用该配置并关闭配置窗口。现在,我们已经完成了 Jersey 项目的创建并配置了 Tomcat 服务器。


Hello World API

本节我们将用 JSON 和 XML 的输入和输出创建 Hello World API。

找到 pom.xml(它有所有 Maven 依赖项),取消注释 JSON 依赖项,然后添加 JAVAX 依赖项。你的依赖项应如下:

    <dependencies>
        <dependency>
            <groupId>org.glassfish.jersey.containers</groupId>
            <artifactId>jersey-container-servlet-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jersey.inject</groupId>
            <artifactId>jersey-hk2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.glassfish.jersey.media</groupId>
            <artifactId>jersey-media-json-binding</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.ws.rs</groupId>
            <artifactId>javax.ws.rs-api</artifactId>
            <version>2.1.1</version>
        </dependency>
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.6</version>
        </dependency>
        <dependency>
            <groupId>com.googlecode.json-simple</groupId>
            <artifactId>json-simple</artifactId>
            <version>1.1.1</version>
        </dependency>

    </dependencies>

编辑 src/main/webapp/index.JSP 并将下列代码添加到文件中:

<html>
<body>
    <h2>Agora Token Server</h2>
    <p><a href="webapi/myresource">Agora resource</a>
    <p>Visit <a href="https://agora.io/">Agora.io website</a>
    for more information on Agora!
</body>
</html>

接下来,构建项目。构建完成后,用 IntelliJ 打开浏览器。默认情况下,index.JSP 文件显示在浏览器中。浏览器应显示以下内容:

build-an-agora-token-server-using-java-5

单击 jersey 资源,跳转到新页面:

build-an-agora-token-server-using-java-6

请注意 URL,默认情况下,ServerletMapping 将所有内容映射到 */webapi/ 上,你要将其改为/*。

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
    <servlet>
        <servlet-name>Agora Token Server</servlet-name>
        <servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
        <init-param>
            <param-name>jersey.config.server.provider.packages</param-name>
            <param-value>com.sandeep.agoratoken</param-value> <!-- Remember to replace this -->
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>Agora Token Server</servlet-name>
        <url-pattern>/webapi/*</url-pattern>
    </servlet-mapping>
</web-app>

删除 webapi 后,重新启动服务器。

怎么让“Got it”显示在网站上?转到 MyResource Java 类:

build-an-agora-token-server-using-java-7

这里我们添加了路径注释(@PATH),用于为 MyResource 类传入的 HTTP 请求定义一个 URI 匹配模式。

因为 Path annotation ,我们从 URL 中获得“myresource”,即@ PATH(“myresource”)。我们为 getIt() 方法定义了 @ GET (第 12 行),getIt() 方法指定使用 GET HTTP 方法@ Produces annotation(第 13 行) 定义返回响应格式。

现在,将声网添加到项目中。

访问声网 GitHub 仓库,克隆整个仓库,复制并粘贴 Media 和 RTM 文件夹,文件结构应如下:

build-an-agora-token-server-using-java-9

注意:找到每个文件并更正软件包名称,避免出现错误。本项目的软件包名称应该是 com.sandeep.agoratoken,不是 io.agora。通常 IntelliJ 会执行此操作。

分别在 Agora、AgoraRepository 和 AgoraRTMRepository 这三个 java 文件夹上单击右键,删除 MyResource 文件后,3个新的 Java 类就创建完成了。

将以下代码粘贴到 AgoraRepository 中:

package com.sandeep.agoratoken; //make sure you change this

public class AgoraRepository {
    static String appId = "APP_ID";
    static String appCertificate ="APP_CERIFICATE";
    private String channelName;
    private int uid = 0; // By default 0
    private int expirationTimeInSeconds = 3600; // By default 3600
    private int role = 2; // By default subscriber
}

将以下代码粘贴到 AgoraRTMRepository 中:

package com.sandeep.agoratoken;

public class AgoraRTMRepository {
    private static String appId = "APP_ID";
    private static String appCertificate = "APP_CERT";
    private String userId;
    private int expireTimestamp = 0;
}

现在,你需要获取器和设置器。

转到 AgoraRepository 类,在操作系统输入下列命令:

* Ctrl + N for Linux and Windows users

* Cmd + N for Mac users

build-an-agora-token-server-using-java-10

上述命令的替换

你的文件应如下:

package com.sandeep.agoratoken;

public class AgoraRepository {
    static String appId = "APP_ID"; //replace app id
    static String appCertificate = "APP_CERT"; //replace app cert
    private String channelName;
    private int uid = 0;
    private int expirationTimeInSeconds = 3600;
    private int role = 2; // By default subscriber

    public static String getAppId() {
        return appId;
    }

    public static void setAppId(String appId) {
        AgoraRepository.appId = appId;
    }

    public static String getAppCertificate() {
        return appCertificate;
    }

    public static void setAppCertificate(String appCertificate) {
        AgoraRepository.appCertificate = appCertificate;
    }

    public String getChannelName() {
        return channelName;
    }

    public void setChannelName(String channelName) {
        this.channelName = channelName;
    }

    public int getUid() {
        return uid;
    }

    public void setUid(int uid) {
        this.uid = uid;
    }

    public int getExpirationTimeInSeconds() {
        return expirationTimeInSeconds;
    }

    public void setExpirationTimeInSeconds(int expirationTimeInSeconds) {
        this.expirationTimeInSeconds = expirationTimeInSeconds;
    }

    public int getRole() {
        return role;
    }

    public void setRole(int role) {
        this.role = role;
    }


}

在 AgoraRTMRepository 中重复此步骤,你的文件应如下:

package com.sandeep.agoratoken;

public class AgoraRTMRepository {
    private static String appId = "APP_ID";
    private static String appCertificate = "APP_CERT";
    private String userId = "USER_ID";
    private int expireTimestamp = 0;

    public static String getAppId() {
        return appId;
    }

    public static String getAppCertificate() {
        return appCertificate;
    }

    public static void setAppCertificate(String appCertificate) {
        AgoraRTMRepository.appCertificate = appCertificate;
    }

    public String getUserId() {
        return userId;
    }


    public int getExpireTimestamp() {
        return expireTimestamp;
    }

    public static void setAppId(String appId) {
        AgoraRTMRepository.appId = appId;
    }
}

看到类似内容后,单击“获取器和设置器”,然后全选。至此,声网 Token Server 大致构建完成。

输入内容并定义 Token Server RTC POST Request:

package com.sandeep.agoratoken;

import com.sandeep.agoratoken.media.RtcTokenBuilder;

import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.json.simple.JSONObject;

@Path("api")
public class Agora{

    @POST
    @Path("rtc")
    @Produces(MediaType.APPLICATION_JSON)
    public Object getRTCToken(AgoraRepository resource) {
    
    }
 
}

初始化所有参数:

package com.sandeep.agoratoken;

import com.sandeep.agoratoken.media.RtcTokenBuilder;

import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.json.simple.JSONObject;

@Path("api")
public class Agora{

    @POST
    @Path("rtc")
    @Produces(MediaType.APPLICATION_JSON)
    public Object getRTCToken(AgoraRepository resource) {

        RtcTokenBuilder token = new RtcTokenBuilder();
        String channelName = resource.getChannelName();
        int expireTime = resource.getExpirationTimeInSeconds();
        RtcTokenBuilder.Role role = RtcTokenBuilder.Role.Role_Subscriber;
        int uid = resource.getUid();

    }

}

创建 Token 之前先检查一下,确保万无一失:

package com.sandeep.agoratoken;

import com.sandeep.agoratoken.media.RtcTokenBuilder;

import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.json.simple.JSONObject;

@Path("api")
public class Agora{

    @POST
    @Path("rtc")
    @Produces(MediaType.APPLICATION_JSON)
    public Object getRTCToken(AgoraRepository resource) {

        RtcTokenBuilder token = new RtcTokenBuilder();
        String channelName = resource.getChannelName();
        int expireTime = resource.getExpirationTimeInSeconds();
        RtcTokenBuilder.Role role = RtcTokenBuilder.Role.Role_Subscriber;
        int uid = resource.getUid();
        
        // check for null channelName
        if (channelName==null){
            JSONObject error=new JSONObject();
            error.put("error","Channel ID cannot be blank");
            return Response.status(Response.Status.BAD_REQUEST).entity(error).build();
        }

        if(expireTime==0){
            expireTime = 3600;
        }

        if(resource.getRole()==1){
            role = RtcTokenBuilder.Role.Role_Publisher;
        }else if(resource.getRole()==0){
            role = RtcTokenBuilder.Role.Role_Attendee;
        }


    }

}

创建一个时间戳并生成令牌:

package com.sandeep.agoratoken;

import com.sandeep.agoratoken.media.RtcTokenBuilder;

import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.json.simple.JSONObject;

@Path("api")
public class Agora{

    @POST
    @Path("rtc")
    @Produces(MediaType.APPLICATION_JSON)
    public Object getRTCToken(AgoraRepository resource) {

        RtcTokenBuilder token = new RtcTokenBuilder();
        String channelName = resource.getChannelName();
        int expireTime = resource.getExpirationTimeInSeconds();
        RtcTokenBuilder.Role role = RtcTokenBuilder.Role.Role_Subscriber;
        int uid = resource.getUid();
        
        // check for null channelName
        if (channelName==null){
            JSONObject error=new JSONObject();
            error.put("error","Channel ID cannot be blank");
            return Response.status(Response.Status.BAD_REQUEST).entity(error).build();
        }

        if(expireTime==0){
            expireTime = 3600;
        }

        if(resource.getRole()==1){
            role = RtcTokenBuilder.Role.Role_Publisher;
        }else if(resource.getRole()==0){
            role = RtcTokenBuilder.Role.Role_Attendee;
        }
      
        int timestamp = (int)(System.currentTimeMillis() / 1000 + expireTime);


        String result = token.buildTokenWithUid(resource.appId, resource.appCertificate,
                channelName, uid, role, timestamp);
        System.out.print(result);
        JSONObject jsondict = new JSONObject();
        jsondict.put("message",result);
        return jsondict;


    }

}

好啦!声网 Java RTC 令牌服务器创建完成!是时候启动声网 RTM 令牌服务器了!

将以下代码添加到声网类中:

@POST
@Path("rtm")
@Produces(MediaType.APPLICATION_JSON)
public Object getRTMToken(AgoraRTMRepository resource) throws Exception {

    String userId = resource.getUserId();

    if (userId==null){
        JSONObject error=new JSONObject();
        error.put("error","User ID cannot be blank");
        return Response.status(Response.Status.BAD_REQUEST).entity(error).build();
    }

    RtmTokenBuilder token = new RtmTokenBuilder();
    String result = token.buildToken(resource.getAppId(), resource.getAppCertificate(), userId, Role.Rtm_User, resource.getExpireTimestamp());
    System.out.println(result);
    JSONObject jsondict = new JSONObject();
    jsondict.put("message",result);
    return jsondict;
}

最终代码应如下:

package com.sandeep.agoratoken;

import com.sandeep.agoratoken.media.RtcTokenBuilder;
import com.sandeep.agoratoken.rtm.RtmTokenBuilder;
import com.sandeep.agoratoken.rtm.RtmTokenBuilder.Role;

import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.json.simple.JSONObject;

@Path("api")
public class Agora{

    @POST
    @Path("rtc")
    @Produces(MediaType.APPLICATION_JSON)
    public Object getRTCToken(AgoraRepository resource) {

        RtcTokenBuilder token = new RtcTokenBuilder();
        String channelName = resource.getChannelName();
        int expireTime = resource.getExpirationTimeInSeconds();
        RtcTokenBuilder.Role role = RtcTokenBuilder.Role.Role_Subscriber;
        int uid = resource.getUid();

        // check for null channelName
        if (channelName==null){
            JSONObject error=new JSONObject();
            error.put("error","Channel ID cannot be blank");
            return Response.status(Response.Status.BAD_REQUEST).entity(error).build();
        }

        if(expireTime==0){
            expireTime = 3600;
        }

        if(resource.getRole()==1){
            role = RtcTokenBuilder.Role.Role_Publisher;
        }else if(resource.getRole()==0){
            role = RtcTokenBuilder.Role.Role_Attendee;
        }

        int timestamp = (int)(System.currentTimeMillis() / 1000 + expireTime);


        String result = token.buildTokenWithUid(resource.appId, resource.appCertificate,
                channelName, uid, role, timestamp);
        System.out.print(result);
        JSONObject jsondict = new JSONObject();
        jsondict.put("message",result);
        return jsondict;


    }

    @POST
    @Path("rtm")
    @Produces(MediaType.APPLICATION_JSON)
    public Object getRTMToken(AgoraRTMRepository resource) throws Exception {

        String userId = resource.getUserId();

        if (userId==null){
            JSONObject error=new JSONObject();
            error.put("error","User ID cannot be blank");
            return Response.status(Response.Status.BAD_REQUEST).entity(error).build();
        }

        RtmTokenBuilder token = new RtmTokenBuilder();
        String result = token.buildToken(resource.getAppId(), resource.getAppCertificate(), userId, Role.Rtm_User, resource.getExpireTimestamp());
        System.out.println(result);
        JSONObject jsondict = new JSONObject();
        jsondict.put("message",result);
        return jsondict;
    }

}

接下里是最后一步——部署!


部署

首先,创建一个 WAR 文件。从右侧切换栏打开 Maven,单击 Maven 图标执行命令:

运行 mvn clean package

只需一两分钟就能生成一个 WAR 文件,大家可以看到 WAR 文件的位置。

在资源库中创建一个 Dockerfile,并将你的代码推送到 GitHub。

FROM tomcat:9.0.37
MAINTAINER SaiSandeep

COPY target/agoratoken.war /usr/local/tomcat/webapps/ROOT.war

EXPOSE 8080

CMD ["catalina.sh", "run"]

创建一个 Ubuntu VM。

运行以下命令,在 Linux 上安装 Docker:

sudo apt-get update

sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg-agent \
    software-properties-common

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"

sudo apt-get update

sudo apt-get install docker-ce docker-ce-cli containerd.io

Mac 和 Windows 用户可以从 Docker 官方网站中获取可执行文件。

运行此命令,检查你的设备是否已安装 Docker:

-> docker -v

将你的资源库克隆到 VM。并运行下列命令:

nano src/main/java/com/sandeep/agoratoken/MyResourceRepository.java

填写你的 APP_ID 和 APP_CERT(可以从声网控制台获取 APP_ID 和 APP_CERT)。

build-an-agora-token-server-using-java-13

运行以下命令来构建 Docker 映像:

docker build -t agoratoken ./

OK,运行!

docker run -p 80:8080 agoratoken

检查 API。



更多资源

想了解声网应用程序令牌的更多信息,请参阅《设置身份验证指南》《声网高级指南:如何构建令牌(Go)》

Github 资源库:声网 Token 参考代码


原文作者:声网 SuperStar
原文链接:https://www.agora.io/en/blog/building-an-agora-token-server-using-java/
推荐阅读
相关专栏
SDK 教程
164 文章
本专栏仅用于分享音视频相关的技术文章,与其他开发者和声网 研发团队交流、分享行业前沿技术、资讯。发帖前,请参考「社区发帖指南」,方便您更好的展示所发表的文章和内容。