A user login system for UDK

来源:互联网 发布:尼古拉斯网络用语 编辑:程序博客网 时间:2024/05/11 17:49

This is the placeholder tutorial for all of you who are patiently awaiting the Kiosk final touches, I have had to rework a large portion of the kiosks in light of some issues with forwarding input around, but hopefully this evening ill have v2 working and ill be able to get back to work on the tutorial.

In the down time though, I have found a number of people are asking about user login systems and how to pull them off. The truth is, its not that difficult.People are just very good at being afraid to do things they have not already done. For anyone who has written a web app you likely already took many of the steps, but here we go with a very simple one.

The concepts in this tutorial can easily be ported around to more involved things like stats tracking, achievements as well as providing a bit of persistence across servers if you are so inclined.

As a final word of caution, it is important that you take this tutorial further than the words I share. It is intermediate, meaning you will be putting together some advanced UnrealScript elements, such as delegates, exec functions and TCPLink, as well as using PHP on your web server to do all of the dirty work. This is not an optimal setup, however.

  1. If you are doing a networked game you will likely want to make this server side, and let UDK handle the networking to any important actors.
  2. All validation and commenting will be omitted for brevity

Down to business

We have to start out with the TCPLink class, because the work we do here will absolutely make or break our system. This class is going to encapsulate all of the complexities of HTTP Requests, and do some preliminary response cleanup for us to lighten the load when we need to actually work with it.

/** * @author      Bob Chatman bob@gneu.org * @version     2011-08-10 * @since       July 2011 Release */class HTTPLink extends TCPLink    Config(BetterTech);var config string Host;var config string Page;var config int HostPort;var bool RequestInProgress;var bool ShouldPOST;var string Action;var array Data;var string LastResponse;

Here is the header for our class. We define a few interesting elements:

  1. RequestInProgress (RIP) is a boolean we test to see if there is a request in progress, since it doesn’t really make sense to modify a request when its being processed.
  2. ShouldPost is a flag to toggle between GET and POST requests. I tend to keep non trivial submissions as POST because I am a zealot for taking steps to protect websites when possible. This is an example of “security through obscurity,” which is not security at all, but it will help you cut down on submissions, as well as a couple other things I will show you later.
  3. Action is the name of the Action to post. I moved it to a variable primarily in earlier tests to ensure it is set.
  4. The Data array is used to consolidate the Key=Value pairs that you will find in HTTPRequests, but I will go into this more in just a few minutes.

Note the three config variables. This is done to allow us to move the values of these variables into the config files. You will likely have a few of these classes hiding around and you can stick entries as the following into your config file:

[BetterTech.HTTPLink]Host=bettertech.gneu.orgHostPort=80Page=superTest.php

If you would like more information on config variables please follow these links:

UnrealScript Language Reference | Configuration Files

Request Execution, Response Delegation

Next up is a delegate, which we are defining now to provide a route for the successful request to be processed by any function in any class. Another method could be to do something similar to how exceptions are written in Java – new class for anything different and pass a class reference for success/fail which will encapsulate the functionality and build a response tree, but I find that if I can consolidate them it makes modifying the code a bit easier. And truthfully, most people don’t even know that delegates exist, so enjoy your function pointers =)

delegate OnResponse( string act, array res );

This is actually pretty important. act is the Action that was previously set, allowing you to test for it if you choose, and res is an array of the response values. One thing that is important to define early on is a set of standards for your Requests and Responses. For the purposes of this tutorial Requests will follow HTTP standards, because it is easy for PHP to work with them, and HTTP Responses will be comma separated tokens provided in an expected order. You will quickly see the benefit to following these standards as we progress.

TCPLink begins with a call to Resolve, but, we are trying to wrap up a couple other bits of functionality at the same time. Instead of expecting the user to have to set the RIP flag and delegate reference up we are going to create a new function:

function bool Execute(delegate ResponseDelegate){    `Log("Attempting to Resolve " $ Host);    if (RequestInProgress || Action == "")        return false;    super.Resolve(Host);    OnResponse = ResponseDelegate;    RequestInProgress = true;    return true;}

Although it has been simplified a bit, note the tests. If there is a request in progress or we have no action, we fail out, and all of our assignments are done after that. This is a primary task, and it helps to avoid modification mid request. Once the Resolve function has been triggered all hell breaks loose and the class is on cruise control… Resolved or ResolveFailed gets called, neither of which are really very important for this but ill include them for completeness:

event Resolved( IpAddr Addr ){    `Log("Resolved to " $ IpAddrToString(Addr));    Addr.Port = HostPort;    if (!Open(Addr))        `Log("Open failed");}event ResolveFailed(){    Cleanup(false);    `Log("Unable to resolve " $ Host);}

Their names speak for themselves – Resolved occurs when the location is resolved and ResolveFailed is called when its not. I have included a couple log messages for your debugging use. Cleanup is actually a function i use to clear out the relevant variables and flags to allow for a new query to be made:

function Cleanup(bool clearData){    if (clearData)    {        Data.length = 0;        Action = default.Action;    }    ShouldPOST = default.ShouldPOST;    RequestInProgress = default.RequestInProgress;}

We swap in the default values for the elements and clear out our data array. You will note that when resolving fails I don’t clear out the data, this is to allow for resubmission if needed.

Sending the request

We have already probably done more than most people are comfortable doing, but things are just getting fun. Our next function is Opened which is responsible for making successive calls to SendText to send the related message across the wire to your webserver. In order for this function to be called all of the above must have already been done, and now things get a little hairy.

HTTPRequests have a very specific format, and deviation from it is actually a bit difficult to debug – all you get is a failed response. I have tested the following out at length and it has worked for anything I have thrown at it.

event Opened(){    local string text;    if (ShouldPOST)        SendText("POST /" $ Page $ " HTTP/1.0");    else        SendText("GET /" $ Page $ " HTTP/1.0");    SendText("Host: " $ Host);    SendText("User-Agent: BetterTechClient/1.0");    Data.AddItem("Action=" $ Action);    if (Data.length > 0)    {        JoinArray(Data, text, "&", true);        `Log(text);        SendText(            "Content-Type: application" $            "/x-www-form-urlencoded");        SendText(            "Content-Length: " $ len(text) $            chr(13) $ chr(10));        SendText(text);    }    SendText("Connection: Close" $ chr(13) $ chr(10));    `Log("Query Sent.");}

As you can see this is a bit more involved, but our work above is already helping loads. We will add a couple convenience functions in a moment to improve developer usability.

I wont really go into how things are setup here, or why, but you are free to look up the HTTP request headers and formatting in and read about them if you choose. The two things I will note for you is my use of the User-Agent header, which is another security through obscurity layer that we will utilize later, and JoinArray which converts our data into a string, glued together with & characters.

Handling the response

The class is nearly finished now, all we need to do is put that delegate to use, clean up and a couple convenience functions.

If you compile the code as is, your requests will fail, outright. This is primarily because each of those SendText calls is incomplete. Each time that SendText is called above you are actually supposed to append \r\n to the end of it. Fortunately enough for you, we have function overloading and can abstract that away.

function int SendText(string s){    return super.SendText(s $ chr(13) $ chr(10));}

Adding data to the array is should also be a simple process, as should setting the action:

function bool appendData(string s){    if (RequestInProgress)        return false;    Data.AddItem(s);    return true;}function bool setAction(string s){    if (RequestInProgress)        return false;    Action = s;    return true;}

Do note the pattern here, these functions will fail if a request is in progress.

We are likely to want to wipe all of our request data once we close our connection. I have added a log message as well.

event Closed(){    Cleanup(true);    `Log("Closing Link.");}

Our final function is ReceivedText, which is triggered when we get a response from the server. It comes back in the form of an HTTP Response, which is similar in format to our HTTP Request mentioned earlier.

event ReceivedText( string Text ){    LastResponse = Text;    // `log(Text);    Text = Split(Text,        chr(13) $ chr(10) $ chr(13) $ chr(10),        true);    if (OnResponse != none)        OnResponse(Action, SplitString(Text, ",", true));}

There is some not so complicated string parsing elements in here but please feel free to read up on them if you are not already in line. Because of the complexity of this class I have taken the liberty of attaching it at the very end.

Triggering the request

So, we have the framework in place, there are now only a couple of steps between us and being able to log in. Because the scaleform end of things is quite insignificant I am going to move to a PlayerController Exec function, and the paired delegate so i can explain them at the same time:

exec function Authenticate(string user, string password){    if (MyLink == None)        MyLink = Spawn(class'HTTPLink');    MyLink.setAction("Authenticate");    MyLink.appendData("User=" $ user);    MyLink.appendData("Password=" $ password);    MyLink.Execute(onAuthenticate);}function onAuthenticate(string act, array res){    if (res[0] == "Success")    {        `Log("[REQUEST] Authentication has succeeded");        if (PlayerReplicationInfo != none)        {            BTPRI(PlayerReplicationInfo).SetPlayerName(res[1]);            SetTimer(AutoUpdatePRIInterval, true, 'UpdatePRI');        }        // else        // DisplayNotice("Player has logged in successfully!");        UserToken = res[2];        UserName = res[1];        SaveConfig();    }    else        `LogMessage("Authentication has failed");}

All n all, this is pretty straight forward. You will see above that the Authenticate function takes two strings, passes them in to the link and executes it. Our abstraction earlier really simplifies our interactions. Just for illustration purposes, ill show you a second request execution:

exec function SavePRI(){    if (MyLink == None)        MyLink = Spawn(class'HTTPLink');    if (!BTPRI(PlayerReplicationInfo).shouldSave)        return;    MyLink.setAction("SavePRI");    MyLink.appendData("User=" $ UserName);    MyLink.appendData("Token=" $ UserToken);    MyLink.appendData("Fields[]=cash|" $        BTPRI(PlayerReplicationInfo).cash);    MyLink.appendData("Fields[]=savings|" $        BTPRI(PlayerReplicationInfo).savings);    MyLink.Execute(onSavePRI);}exec function onSavePRI(string act, array res){    `LogMessage(act);    `LogMessage(res[0]);    if (res[0] == "Success")        `Log("[REQUEST] SavePRI has succeeded");    else        `LogMessage("SavePRI request has failed");}

Both of these requests illustrate the flexibility of the link class, as well as the benefit of using delegates in this situation. We also gain some flexibility by making these exec functions, because all that you need to do is run a console command and you can do that from practically anywhere – hence why dealing with Scaleform is not important.

You can see here that I am storing my data in the PRI, but you can do anything you want with it.

This is really the end of the UnrealScript. Now we can move over to the PHP end of things, where things get easier.

On the Web Server

We are nearly done, thankfully. It may seem like a lot of work, but its really just some abstraction to clean up the interaction and a couple of function calls. On the web server side we can break things into a few aspects

  1. Switch based on the action
  2. Sanitize and validate your input
  3. Do what you need to do with it
  4. Respond

You may find them similar to steps you had seen in web development in the past, and for those of you who picked up on that, congratulations. All of your PHP development is only as difficult as anything going on in web development. You can find tutorials on how to connect to Databases, file IO and so forth with a little creative googling.

<?php switch($_POST['Action']){case 'Authenticate':// ... Sanitize and validateif ($_POST['User'] == 'Bob_Gneu' &&    sha1($_POST['Password']) == '...'){    print "Success,Bob_Gneu,BT_42";}else{    print "Failure";}break;?>

And with that you should have a working user login system. I recommend you read a couple of my other posts as well, just to make sure you are up to speed on web development expectations.

  • http://blog.gneu.org/2007/11/developer-responsibility/
  • http://blog.gneu.org/2009/12/grinding-my-gears-stored-passwords/
  • http://blog.gneu.org/writings-ramblings/developing-user-systems/

If you are a copy and paste developer I hope you get ill, but I’ve attached the full HTTPLink example file. If you have questions or comments you know where they go =D

Download

Enjoy

0 0