1. “游戏客户端”调用“SDK客户端”的登录功能向“SDK服务端”进行身份认证

2. 验证通过后,“游戏客户端”可得到用户信息,根据游戏逻辑可将用户信息传给“游戏服务器”进行验证

3. “游戏服务器”通过客户端传来的用户信息,一般还需要向“SDK服务器”请求验证用户信息

4. 验证通过后,“游戏服务器”从数据库中查询用户信息,不存在的话直接插入新的用户信息,然后将验证结果和用户信息返回给“游戏客户端”

5. 如果使用了登录会话管理,用户登录后会生成一个新的会话token字符串,把这个token传给“游戏客户端”即可,使用这个token即可查出对应的用户信息

6. 在使用第三方账号登录时,需要为每个第三方账号ID建立一个新的玩家ID,使这两个ID产生关联即可

上图是一个典型的登录过程,关键点是游戏服务端如何做第三方玩家账号与游戏中玩家账号的数据库映射。

客户端示例代码(客户端代码比较简单,可以点此下载完整客户端登录代码):

    public void authenticate(final AuthenticationInterface authenticationInterface, final AuthenticationRequest request)
    {
        // save the request
        m_authenticationInterface = authenticationInterface;
        m_request = request;

        // call third party login method
        OppoClientHelper m_oppoSdk = OppoClientHelper.getInstance();
        m_oppoSdk.setLoginCallback(new OppoLoginCallback()
        {
            @Override
            public void onSuccess(String userId, String accessToken) {
                doAuthenticate(userId, accessToken);
            }

            @Override
            public void onFailure() {
                m_authenticationInterface.onAuthenticationCancel();
            }
        });
        m_oppoSdk.sdkLogin();
    }

登录服务端采用Java的Servlet技术实现,下面是doPost部分代码。这里主要省略了从数据库中获取用户信息和从第三方SDK获取用户信息的代码。

public class Authenticate extends HttpServlet
{
    public static class RequiredParameters{
        public String gameId;
        public String deviceId;
        public String userId;
        public String token;
    }

    public static class OppoUserInfo
    {
        @JsonIgnoreProperties({ "sex", "profilePictureUrl", "emailStatus", "email" })
        public static class BriefUser
        {
            public String getId() {
                return id;
            }
            public void setId(String id) {
                this.id = id;
            }
            public String getName() {
                return name;
            }
            public void setName(String name) {
                this.name = name;
            }

            private String id;
            private String name;
        }

        @JsonProperty("BriefUser")
        public BriefUser getBriefUser() {
            return briefUser;
        }
        public void setBriefUser(BriefUser briefUser) {
            this.briefUser = briefUser;
        }

        private BriefUser briefUser;
    }

    private static final long serialVersionUID = 1L;
    private static final String authenticateTypeName = "oppo";

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
    {
        // check request parameters
        RequiredParameters parameters = new RequiredParameters();
        Map<String, String[]> parameterMap = request.getParameterMap();
        if (parameterMap.get("userId") == null || parameterMap.get("token") == null){
            AuthenticationUtilities.sendError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "internal error");
            AuthenticationUtilities.logWarning(authenticateTypeName, "wrong parameters:" + parameterMap.toString());
            return;
        }
        else{
            parameters.userId = parameterMap.get("userId").toString();
            parameters.token = parameterMap.get("token").toString();
        }

        try {
            // query userInfo from third party SDK
            String gcUserInfo = "{{jsonstring}}";

            // check returned JSON string from SDK
            ObjectMapper mapper = new ObjectMapper();
            OppoUserInfo userInfo = mapper.readValue(gcUserInfo, OppoUserInfo.class);
            if (!parameters.userId.equals(userInfo.getBriefUser().getId())){
                AuthenticationUtilities.sendError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "internal error");
                AuthenticationUtilities.logWarning(authenticateTypeName, "verify userId failed:" + parameters.userId.toString());
                return;
            }

            try{
                // do with database and get a returned token
                String authenticationToken = "";
                boolean databaseResult = false;
                if (!databaseResult){
                    AuthenticationUtilities.sendError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "internal_error");
                    AuthenticationUtilities.logSevere(authenticateTypeName, "database returned R_failure_internal_error.");
                    return;
                }

                // if success, send response to client
                response.getWriter().print("authenticationToken=" + authenticationToken);
            }
            catch (final Exception exception){
                AuthenticationUtilities.sendError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "internal_error");
                AuthenticationUtilities.logSevere(authenticateTypeName, exception.toString());
                return;
            }
        }
        catch (Exception e) {
            AuthenticationUtilities.sendError(response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "internal error");
            AuthenticationUtilities.logSevere(authenticateTypeName, e.getMessage());
            return;
        }
    }
}

下面是使用到的日志功能代码:

public class AuthenticationUtilities
{
    private static final Logger ms_logger = Logger.getLogger("spacetime-authentication");

    public static void sendError(final HttpServletResponse response, final int responseCode, final String errorMessage) throws IOException{
        response.setStatus(responseCode);
        response.getWriter().print(errorMessage);
    }

    public static void logInfo(final String authenticationTypeName, final String message){
        ms_logger.info(authenticationTypeName + ":" + message);
    }

    public static void logWarning(final String authenticationTypeName, final String message){
        ms_logger.warning(authenticationTypeName + ":" + message);
    }

    public static void logSevere(final String authenticationTypeName, final String message){
        ms_logger.severe(authenticationTypeName + ":" + message);
    }
}

这里对数据库的操作也很重要,下面是使用postgresql中用到的部分Sql代码。

如果用户第一次登录,还需要在数据库中建立新用户,然后产生会话token。如果可以查到用户信息,直接产生token并返回结果。

CREATE TABLE authentication_token
(
    token UUID NOT NULL,
    account_id UUID NOT NULL,
    authentication_time TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT now(),
    PRIMARY KEY (token)
);

CREATE INDEX authentication_token_index_account_id ON authentication_token (account_id);
CREATE INDEX authentication_token_index_authentication_time ON authentication_token (authentication_time);

-- --------------------------------------------------------------------

CREATE TABLE auth_account
(
    account_id UUID NOT NULL,
    creation_time TIMESTAMP DEFAULT now() NOT NULL,
    PRIMARY KEY (account_id)
);

ALTER TABLE authentication_token ADD CONSTRAINT authentication_token_account_id_fkey FOREIGN KEY (account_id) REFERENCES auth_account (account_id) ON UPDATE CASCADE ON DELETE CASCADE;

-- ----------------------------------------------------------------------

CREATE TABLE spacetime_account_oppo
(
    oppo_account_id ) NOT NULL,
    account_id UUID NOT NULL,
    creation_time TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT now(),
    PRIMARY KEY(oppo_account_id)
);

ALTER TABLE spacetime_account_oppo ADD CONSTRAINT spacetime_account_oppo_account_id_fkey FOREIGN KEY (account_id) REFERENCES auth_account (account_id) ON UPDATE CASCADE ON DELETE NO ACTION;
ALTER TABLE spacetime_account_oppo ADD CONSTRAINT spacetime_account_oppo_account_id_unique UNIQUE(account_id);

-- --------------------------------------------------------------------

CREATE OR REPLACE FUNCTION get_new_auth_account_id_0007
(
    v_account_id OUT UUID
)
RETURNS UUID
AS $$
BEGIN
    <<retry>>
    LOOP
        v_account_id := public.uuid_generate_v4();
        BEGIN
            INSERT INTO auth_account (account_id) VALUES (v_account_id);
            EXIT retry;
        EXCEPTION
            WHEN UNIQUE_VIOLATION THEN
        END;
    END LOOP;
END;
$$
LANGUAGE plpgsql; 

-- ----------------------------------------------------------------------

CREATE FUNCTION cleanup_expired_authentication_tokens_0001
(
)
RETURNS VOID
AS $$
BEGIN

    DELETE FROM authentication_token WHERE authentication_time < now() - interval '10 minutes';

END;
$$
LANGUAGE plpgsql;

-- --------------------------------------------------------------------

CREATE FUNCTION create_authentication_token_0001
(
    v_account_id IN UUID,
    v_token OUT UUID
)
RETURNS UUID
AS $$
BEGIN
    PERFORM cleanup_expired_authentication_tokens_0001();
    v_token := public.uuid_generate_v4();
    UPDATE
        authentication_token
    SET
        token = v_token,
        authentication_time = now()
    WHERE
        account_id = v_account_id;
    IF NOT FOUND THEN
        BEGIN
            INSERT INTO authentication_token
                (token, account_id)
            VALUES
                (v_token, v_account_id);
        EXCEPTION
            WHEN UNIQUE_VIOLATION THEN
                UPDATE
                    authentication_token
                SET
                    token = v_token,
                    authentication_time = now()
                WHERE
                    account_id = v_account_id;
        END;
    END IF;

END;
$$
LANGUAGE plpgsql;

-- ----------------------------------------------------------------------

CREATE OR REPLACE FUNCTION get_authentication_token_oppo_0008
(
    v_oppoId IN VARCHAR,
    v_result OUT INTEGER,
    v_token OUT UUID
)
RETURNS RECORD
AS $$
DECLARE
    t_account_id UUID;
BEGIN

    SELECT account_id INTO t_account_id FROM spacetime_account_oppo WHERE oppo_account_id = v_oppoId;
    IF NOT FOUND THEN
        t_account_id := get_new_auth_account_id_0007();
        BEGIN
            INSERT INTO spacetime_account_oppo
                (oppo_account_id, account_id)
            VALUES
                (v_oppoId, t_account_id);
        EXCEPTION
            WHEN UNIQUE_VIOLATION THEN
                BEGIN
                    RAISE EXCEPTION 'spacetime_account_oppo: oppo_account_id [%] or account_id [%] already exist', v_oppoId, t_account_id;
                END;
        END;
    END IF;

    v_result :; -- R_success
    v_token := (SELECT f.v_token FROM create_authentication_token_0001(t_account_id) f);

END;
$$
LANGUAGE plpgsql;

-- --------------------------------------------------------------------

为Android游戏接入第三方登录功能的更多相关文章

  1. Android 实现QQ第三方登录

    Android 实现QQ第三方登录 在项目中需要实现QQ第三方登录,经过一番努力算是写出来了,现在总结以下,以防以后遗忘,能帮到其他童鞋就更好了. 首先肯定是去下载SDK和DEMO http://wi ...

  2. laravel5实现微信第三方登录功能

    背景 最近手头一个项目需要实现用户在网站的第三方登录(微信和微博),后端框架laravel5.4. 实现过程以微信网页版第三方登录,其他于此类似,在此不做重复. 准备工作 网站应用微信登录是基于OAu ...

  3. Android Studio 通过一个登录功能介绍SQLite数据库的使用

    前言: SQLite简介:是一款轻型的数据库,是遵守ACID的关系型数据库管理系统,它包含在一个相对小的C库中.它是D.RichardHipp建立的公有领域项目.它的设计目标是嵌入式的,而且目前已经在 ...

  4. 网站如何接入第三方登录,微信登录和QQ登录:注册认证篇

    第三方登录平台接入 (QQ\微信登录) QQ登录接入 第一步成为QQ应用开发者,审核期限七天 一.所需材料 1.公司注册相关信息 2.营业执照扫描件 微信登录接入 第一步成为微信开发平台开发者,认证费 ...

  5. Android应用接入第三方登陆之新浪微博

    众所周知,移动互联网在这几年经历了蓬勃发展,到目前为止,移动互联网发展仍然很强劲.其中移动设备系统以android占据主导地位,之前是加拿大的黑莓系统占据主导,但后来随着android系统的问世,黑莓 ...

  6. Android学习笔记_65_登录功能本身没有任何特别

    对于登录功能本身没有任何特别,使用httpclient向服务器post用户名密码即可.但是为了保持登录的状态(在各个Activity之间切换时要让网站知道用户一直是处于登录的状态)就需要进行cooki ...

  7. .netcore 急速接入第三方登录,不看后悔

    新年新气象,趁着新年的喜庆,肝了十来天,终于发了第一版,希望大家喜欢. 如果有不喜欢看文字的童鞋,可以直接看下面的地址体验一下: https://oauthlogin.net/ 前言 此次带来得这个小 ...

  8. Android 集成支付宝第三方登录

    前言: 在集成支付宝支付的时候遇到一点小麻烦,先在此记录供大家参考 1.授权 支付宝第三方登录需要在后台进行授权,在查看授权的时候我们一定要看清楚时候真的已经获得了权限(我在没有获取权限的情况下集成的 ...

  9. Owin+ASP.NET Identity浅析系列(五)接入第三方登录

    在今天,读书有时是件“麻烦”事.它需要你付出时间,付出精力,还要付出一份心境.--仅以<Owin+ASP.NET Identity浅析系列>来祭奠那逝去的…… OK,用户角色实现后,我们回 ...

随机推荐

  1. rhel-server-7.2-x86_64无法联网(VMware环境)

    vim /etc/sysconfig/network-script vim ifcfg-eno16777736 (编辑网卡名) 将ONBOOT=NO更改为YES (启动network服务时是否启用该网 ...

  2. win7进入不了系统故障修复

    问题: 由于电脑关机比较慢,等得不耐烦了,就强制关机了,以前都没事,直到昨晚打开电脑,提示windows错误恢复,试了好久,提示windows无法修复此计算机,看来是没办法了.后来进入系统还原后,总算 ...

  3. 从输入 URL 到页面加载完的过程中都发生了什么---优化

    这篇文章是转载自:安度博客,http://www.itbbu.com/1490.html 在很多地方看到,感觉不错,理清了自己之前的一些思路,特转过来留作记录. 一个HTTP请求的过程 为了简化我们先 ...

  4. linux常用头文件

    http://blog.csdn.net/kokodudu/article/details/17361161 aio.h 异步I/Oassert.h 验证程序断言 complex 复数类complex ...

  5. [转载]浅析Java中的final关键字

    浅析Java中的final关键字 谈到final关键字,想必很多人都不陌生,在使用匿名内部类的时候可能会经常用到final关键字.另外,Java中的String类就是一个final类,那么今天我们就来 ...

  6. Drawable(4)LevelListDrawable

    Levellist 显示时不像scale list 或 layout list那样要用代码设置一下,可以直接显示.改变level时才用代码. res:level_list.xml <?xml v ...

  7. n人比赛,可轮空,比赛轮数和场数

    #include<stdio.h> int chang(int x,int s){ ) return s; ) ; !=){ s+=(x-)/; )/,s); } else{ s+=x/; ...

  8. JVM参数配置

    JVM参数配置 设置堆大小 -Xms 初始堆大小 -Xmx 最大堆大小 -Xmn 设置年轻代大小 设置每个线程堆栈大小 -Xss 设置每个线程的堆栈大小 设置年轻代大小 -XX:NewSize= -X ...

  9. MongoDB入门分享-笔记整理精选

    最近在学习MongoDB,怕以后忘记,自己做了一个整理,给不知道的小伙伴一起分享学习一下. 第一步> 首先到官网下载,安装MongoDB.(注意MongoDB还有一个可视化管理工具叫: Mong ...

  10. 视频捕捉全教程(vc+vfw)

    目 录 一. 视频捕获快速入门 二.基本的捕获设置 1.设置捕获速度: 2.设置终止捕获 3.捕获的时间限制 三.关于捕获窗口 1.创建一个AVICAP捕获窗口 2.将一个捕获窗口连接至捕获设备 3. ...