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 VARCHAR(64) 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 := 0; -- R_success v_token := (SELECT f.v_token FROM create_authentication_token_0001(t_account_id) f); END; $$ LANGUAGE plpgsql; -- --------------------------------------------------------------------
时间: 2024-11-10 11:02:56