Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Professional ASP.NET Security - Jeff Ferguson

.pdf
Скачиваний:
31
Добавлен:
24.05.2014
Размер:
13.26 Mб
Скачать

Custom Authentication

public bool IsAuthenticated { get

{

return true ;

Additionally, we've returned a true value for the IsAuthenticated property. This property, frequently used by applications that require authenticated identity objects representing active users, contains a Boolean value indicating whether the active account has been authenticated according to some scheme (in this case, the WROXTBAUTH authentication scheme).

Notice especially the local field usr, which we've provided near the top of the class. We'll use this string field during object construction to make it a little easier to set the final required property implementation - the user's name.

The Name Property - The "Who"

All classes that implement I Identity must implement the Name property. This string value represents the account name the authenticated user provided during the login process. Our class contains a global member, usr. The default constructor of our custom identity object requires one parameter - usrName - the value of which is assigned to the class's usr variable when the custom identity object is instantiated in memory. In this way, we'll require that every instantiation of our identity object requires one input parameter that then assigns the user's name to the identity object's Name property.

To implement the Name property within our custom identity object, we'll simply return the value of the strUsr variable that will get set when the object is constructed. The code below demonstrates this simple requirement.

public Myldentity (string usrName) { usr = usrName;

public string Name { get

{

return usr;

This makes it easier for applications to use our authentication method. To create an instance of this type of Identity object, the code simply needs to create a new instance of the object while supplying the desired username as a string parameter to the object construction directive.

Now that the custom Identity object has been created we'll move a little further and create a similar object - a custom Principal object - that can be used by the IPAuthenticator HTTP module to bind an HTTP process to our custom authentication process. We'll create the principal object, MyPrincipal, in the next section, and then investigate how to attach an HTTP application's context to it.

251

Creating Custom Principals (Roles)

Now that we've created a class that serves as a valid account identity, we can develop a custom Principal object as well. Principals are to Identity objects what Windows user groups are to a user: principals can have rights and privileges just as identities do, and these rights and privileges extend to each identity contained within the given principal.

In terms of ASP.NET applications, the Principal object serves an additional function. Every HTTP application contains a reference to all aspects of that particular HTTP application, known as the HttpContext. This reference, as we demonstrated in our global . asax and IPAuthenticator examples earlier, contains pointers to the Request and Response object's contexts, rendering them available to our authentication provider. One additional property exposed via the HttpContext object is the User property. This property exposes an instance of the System. Security. Principal namespace's IPrincipal interface, which can be set at run time.

By setting the current HttpContext's User property to an instance of a custom Principal object, the HTTP process is successfully attached to the Principal object and is thus bound to the underlying Identity class's account. This step is fundamental during custom authentication - without it, the authentication data is never associated with the HTTP process.

This can be confusing, but it is really quite straightforward. In essence, principal objects are - in addition to being any class which implements the interface

System. Security. Principal . IPrincipal - a collection of properties all identity objects will adopt, should they be associated with a given principal. For example, all users who are added to the Domain Administrators group maintain the rights and privileges of a Domain Administrator. Should an account object exist as a member of the Domain Administrators and the HR Managers user groups, it will maintain all the privileges provided to either the Domain Administrator or HR Managers groups.

Our custom Principal object will accept an instance of the Myldentity object we've already created as the single construction parameter. In this way, any instance of our MyPrincipal object will be associated with an active account user. In the following code, from MyPrincipal . cs, note the presence of a single local field, ident. This field, set at the time that the object is constructed, is used to return an instance of our Myldentity class in the code that implements the Identity property of the IPrincipal interface.

using System;

using System. Security .Principal;

namespace CustomAuthenticationC {

public class MyPrincipal : IPrincipal

{

private Myldentity ident;

public MyPrincipal (Myldentity m) { ident = m;

}

public Ildentity Identity

252

Custom Authentication

get

{

return ident;

Next we'll accommodate the IPrincipal interface's IsInRole ( ) method. This method accepts a string input parameter representing the role the client wishes to test this principal against. Since this authentication scheme is specialized to a specific business need, we'll assume that all users of this type will exist within one specified role.

Within the code that implements this property we could add complex routines that test the user's login credentials against our group affiliation rules. For the purposes of this simple demonstration, however, we'll simply state that all members of this principal are only members of the users group.

public bool IsInRole (string role) { if (role == "users") {

return true ; } return false;

The final step in explaining this simple custom authentication example involves attaching it to the HTTP context during run time. To facilitate this process, we'll add some additional code to the IPAuthenticator HTTP module that uses variables passed within the URL query string to create instances of our custom Identity and Principal objects.

Attaching to a Custom Principal Object at Run Time

Now that we've created a custom Identity object and a Principal object, which we'll use to bind the Identity object to the executing HTTP application during run time for a user, we'll augment the IPAuthenticator class so that it uses a query string value as a login mechanism. This type of solution works well in the case of Web Services or client interfaces that don't provide for HTML form postings, but care must be taken to encrypt the HTTP connection so that the variables aren't 'hijacked1.

Nevertheless, query string variables and SOAP Headers, among other methods, can be useful for HTTP modules enabling such custom authentication functionality.

To begin with, we'll add some code to our IPAuthenticator class to interrogate the query string content that was issued with any given HTTP request. We'll look for a particular variable, usr, that will be used in an additional SQL statement, designed to find users within a database that have been associated with a particular IP address. The code from IPAuthenticator . cs below shows some additional code added to the Application_OnAuthenticate event handler, which performs this very function. If this code executes in the absence of the usr query string variable it will generate an exception, so we'll wrap the code in some error-checking code that returns an HTTP 401.2 Access Denied error should the exception occur.

253

public void Application_OnAuthenticateRequest (object sender,

System. EventArgs e) {

HttpApplication app = (HttpApplication) sender; HttpContext ctx = app . Context ;

string ip = ctx. Request .UserHostAddress .ToString (); string sConn =

"SERVER=sql4.WROX.com;UID=dbUser;PWD=4323uih; " "Initial Catalog=CAC" ;

string sqlCheck = "SELECT COUNT (*) FROM client WHERE " + "client_ip_address = '"+ ip + ..... ;

SqlCommand cmd = new SqlCommandf sqlCheck, new

SqlConnection(sConn) ) ;

if (cmd. Connection. State != ConnectionState.Open) { cmd. Connection. Open () ;

int cnt = (int) cmd.ExecuteScalar ( ) ;

// make sure that this client exists if (cnt != 1)

{

ctx. Response. StatusCode = 401; } else

{

string usrQS = " " ; string usrName = " " ;

try {

usrQS = ctx. Request .QueryStringf "usr" ]; } catch

{

ctx. Response. StatusCode = 401; }

In this way, we'll capture the value of the usr querystring variable and save it into a local variable named usrQS. This local variable will then be used as a parameter in a SQL statement.

The next step will be to verify the user's existence in our database. To do this, we'll find a count of all the users with a matching username and then perform a second query to ensure that the username is associated with the IP address we've already obtained. In this way we'll extract that username we've stored in the database as a string value.

// client exists, see if the user exists string sqlUsrCnt = "SELECT COUNT (user_id) " +

"FROM [user] WHERE username = '" + usrQS + " ' " ;

254

Custom Authentication

cmd . CorranandText = sqlUsrCnt;

int usrCnt = (int) cmd.ExecuteScalar ();

if (usrCnt == I)

{

string sqlUsr = "SELECT [user] .username " + "FROM client INNER JOIN " +

"[user] ON client .client_id = [user] . client_id " + "WHERE

(client . client_ip_address = ' " + ip + " ' ) " + "AND ( [user] .username = ' " + usrQS + " ' ) " ;

cmd.CommandText = sqlUsr;

usrName = (string) cmd.ExecuteScalar ();

If the database returns a string value, we can be sure we have found a valid user. Using the string the database query provides, we'll create a new instance of our Myldentity class that gets passed as the single parameter in the creation of an instance of our MyPrincipal custom Principal class:

MyPrincipal mp = new MyPrincipal (new Myldentity (usrName) ) ;

Once the Principal object is created it can be used to set the User property of the HttpContext. By performing this final step, the HTTP application attaches it's underlying User context to our custom Principal object, thus binding the user who has authenticated against our application to the run-time execution:

ctx.User = mp;

ctx . Response .Write (ctx . Request . IsAuthenticated) ; ctx. Response. End ( ) ; }

else

{

ctx. Response. StatusCode = 401;

If, for any reason, an exception is generated, we know that either a valid user was not obtained from the database or the application was unable to set the User context, so we'll respond with the 401.2 HTTP Access Denied Error.

In this way, we've attached the HTTP process to our custom Principal object and associated the context with our custom authentication data. By creating custom Identity and Principal objects and then attaching an HTTP process to those objects, the HTTP process has been authenticated against our underlying account base. We can now rest assured that any user entering our application has been associated with their parent client's IP address as it was registered with our service.

Now that you've been introduced to the complexities of developing a custom authentication provider, we'll build on our knowledge and develop a more complex solution.

955

The Telephone Banking Case Study

Consider for a moment the difficulty surrounding a distributed, Internet-accessible, telephone-interfacing application. All sorts of security questions arise - there's no web browser, so traditional authentication options won't work. There's no login dialog or dependable TCP packets to encrypt. Wouldn't it be great if we could couple the resources ASP.NET provides us with the most recognized form of communication -the telephone - to produce a system that would somehow recognize the user automatically?

A technology frequently employed for this very end - Caller Identification - will be used in conjunction with Voice XML and .NET XML functionality in this example, to demonstrate the construction of such an interface - one that uses sound network standards and widespread user acceptance - to augment its underlying authentication architecture.

Using Voice XML and the Be Vocal Cafe online development environment, we'll develop a phone-based interface to a banking application. Users who subscribe to our fictional WROX TeleBank service will call a specified number to access their bank account balances. User accounts will be stored within a back-end SQL Server database, and will be associated with at least one telephone number. Provided users call the system from a number associated with their account, they will be granted access to their account balance information. When users call the application, it will automatically determine the phone number of the telephone they used to call the service using caller identification (Caller ID) technology. This number will then be transmitted as a URL query string value to our authentication components. This query string - the user's phone number - will be used to access any and all pertinent information pertaining to the user's associated accounts.

One thing is important to note about such an architecture before we dive into the code. Anyone can "fake" a query string value in a URL, and as a result gain access to our system. To ensure such an intrusion does not take place, it would be a good idea to use encryption to encode the HTTP transmission. Likewise, our application could perform additional checks to make sure that each request originates from an expected IP address (and we've already demonstrated how to do this!). Such issues abound when developing custom security paradigms, and provide a rationale for investigating all the other options before endeavoring to create your own custom solution. For now, we're interested in demonstrating a custom method of authenticating users who access our system, which could be used in many different types of contexts, most of which would not require the high level of security required by a banking application.

Our example will utilize the authentication methods we've already discussed to perform authentication for clients accessing our application. As an account and data store, SQL Server 2000 will be used, with ADO.NET as the technology of choice to access this. Via the System. Data. SqlClient namespace, we'll develop a few simple components that make calls to stored procedures in the database. The results of these queries will be formatted using XSL and returned as Voice XML markup, which will be rendered into spoken messages by the BeVocal Cafe Voice XML parser.

Before looking into how our authentication solution works, we will spend a short while investigating Voice XML and the BeVocal Cafe Voice XML parser.

A Pragmatic Introduction to Voice XML

Though an exhaustive discussion of Voice XML is somewhat beyond the scope of this chapter, it's important to understand a few basic elements of the language, and the resources the BeVocal Cafe provides as an application hosting solution. Just as ASP and any other development paradigm provides certain types of tools, the BeVocal Cafe offers up some Voice XML extensions that we'll exploit in a fe\* areas of our example.

256

Custom Authentication

More information about the language and syntax of Voice XML may be found in Early Adopter Voice XML, ISBN 1-86100-562-8, also from Wrox.

To begin with, let's take a look at a few of the Voice XML tags that our example Voice XML file will contain. Most Voice XML tags are relatively self-explanatory, such as the <prompt> tag, which simply instructs the parser to say the content contained within the tag. If we were to create a Voice XML document containing the following markup:

<prompt>Hello World!</prompt>

the Voice XML parser would read the phrase "Hello World!" aloud. To begin our introduction to Voice XML, take a look at the table below to get a quick guide to the tags we'll be using in this example.

Tag Name

<prompt>

<form> or <block>

<value>

<say-as>

<goto>

Function

Instructs the Voice XML parser to read the text contained within the open and close tags.

Creates a variable.

A section of Voice XML that contains prompt tags, variables, or processes that are logically grouped together in one place.

Instructs the parser to write the value of a particular element or variable.

Contains grammatical instructions on how to "format" a response.

The next section of Voice XML (be it a <f orm> or <block> element) to be executed. Can also contain a pointer to a URL that the client is redirected to.

For starters, we'll examine a particular BeVocal Cafe extension to Voice XML, the static variable session. telephone. ani. This extension can be though of in terms of its similarity to the server variable named remote_addr, which represents the requesting client's Internet Protocol (IP) address. The session. telephone . ani variable actually reflects the phone number of the telephone being used to access the system. BeVocal Cafe records the Caller Identification number for each incoming call, and passes it into the application as a system variable.

Using Voice XML markup, this variable can be accessed rather simply. The following code demonstrates how Voice XML code can access this variable and play it back to a user accessing the system.

<?xml version="l.0" ?>

<!DOCTYPE vxml PUBLIC "-//BeVocal Inc//VoiceXML 2.0//EN" "http://cafe.bevocal.com/libraries/dtd/vxml2-0-bevocal.dtd"> <vxml version="2.0">

<var name="clientTelco" expr="session.telephone.ani"/> <form id="frmData"> <block>

<prompt>You are calling from

•cvalue expr="clientTelco"/> </prompt>

</block> </form> </vxml>

Calling a Be Vocal application that had this script marked as its activated script would result in a female voice stating something like " You are calling from eight-billion-two-hundred-and-seventy-five-million-five-hundred-and-twenty-one-thousand-two-hundred-seventy-on e."

Since most users wouldn't expect to have their phone number presented in this way, Voice XML markup allows built-in functionality useful for formatting the style of language the speaker will choose. Using the <say-as> element and it's associated type attribute, the Voice XML code can be changed slightly to produce a more realistic, familiar speech pattern.

<?xml version="l.0"?>

<!DOCTYPE vxml PUBLIC "-//BeVocal Inc//VoiceXML 2.0//EN" "http://cafe.bevocal.com/libraries/dtd/vxml2-0-bevocal.dtd"> <vxml version="2.0">

<var name="clientTelco" expr="session.telephone.ani"/> <form id="frmData"> <block>

<prompt>You are calling from <say-as type="telephone">

<value expr="clientTelco"/> </say-as> </prompt> </block> </form> </vxml>

By implementing this small change the speaker will present the relevant information a more natural voice pattern. By calling the BeVocal system and logging in with this new script activated, the female voice sounds a little more believable - "You are calling from 8-2-7-5-5-2-1-2-7-1."

BeVocal Cafe

BeVocal Incorporated is a leader in the industry of telecommunications and voice telephony software. Focussing on the potential of the use of human speech as the medium of an application interface, BeVocal created Cafe, an online application development framework. Provided as an Application Service Provider (ASP), Cafe provides a registration form we can use to design Voice XML-based interfaces to back-end applications. At no charge to the developer, Cafe is provided as an online Voice XML SDK, rich with tools, documentation, and, most importantly, hosting facilities. Using tools that assist in the creation of Voice XML markup, such as markup validation and language definitions, we can design complex menu and navigation tools that users may manipulate through speech.

In addition to providing developmental tools and support, BeVocal offers hosting for the applications we develop. By registering with BeVocal, we provide a telephone number as an account number. In this way, the login process is somewhat automatic - Be Vocal's telephone-interface ASP architecture utilizes Caller Identification (CID) to authenticate each application request.

258

Custom Authentication

The technology behind BeVocal - Voice XML - is simply an implementation of XML that provides aVoice XML parser instructions on what it should say. Voice XML bridges the gap between text-to-speech technology and extensible solutions. Like other flavors of XML, Voice XML can be processed or transformed from other data structures via XSL transformations. Think of it this way - if you have transformed database-stored information into XML (or HTML) to be transferred to another computer, you're already comfortable with the process. The only difference between traditional presentation and Voice XML presentation is that a telephone system is needed to present the voice signals to the user. BeVocal completes this requirement by establishing a telephone connection between your Voice XML scripts and the user. Since BeVocal is hosted on the Internet, it can be used to connect telephone users with your back-end systems.

In order to use BeVocal, you must first establish an account for yourself. To take advantage of this free resource, go to http://cafe.bevocal.com. Don't worry -registration, hosting, and documentation are all free of charge.

Once we register with BeVocal we can use the administration interface to perform various tasks. Once you've successfully created an account and logged into Cafe, the Tools and File Management page should appear, with links to the various tools Cafe provides us throughout the development and hosting phases of your application.

The initial screen, shown below, displays your total space usage, any Voice XML files you've uploaded or written online, and a form to use if you plan to upload new Voice XML scripts from a local computer. Take note of the VXML Files section: the row highlighted in dark blue indicates the script -or URL - you wish to run as users first enter your system. The following screen presents an example of the Tools and File Management window below indicates that a file which is hosted in the Cafe service named getphonenumber . vxml has been set as the active startup script.

259

0e

&*

flew

Favorites

look

 

 

 

 

3

 

-- ^Jbtogs

^ devstepmcnl

^Amerto«te ^JetagThtsr ^)MSK8 ^MSNMoncy ^ MSN Money Top 10

 

 

 

 

 

 

 

 

 

 

 

 

 

Call Now to Test Your App: 1.877.33 VOCAL

 

 

 

 

 

(*44)

20 7961 3985 Other intwnaiional. 5*11408907.7328

[jU

Audios 0

Used Space

1544

Free Space 2095608

0 internet

The page is split into various sections. The navigation bar provides you with links to Voice XML documentation and code samples. Most importantly, some tools are available to assist with your development and deployment needs. During development of Voice XML scripts, one tool in particular - the Voice XML checker - will provide valuable assistance. By clicking on a particular script in the files area, the Voice XML checker window appears with the content of the script displayed in an editable text area.

260

Соседние файлы в предмете Программирование