|I l @ ve RuBoard|
Enterprise Security Scenarios
The following sections offer enterprise application development scenarios that implement the concepts we've discussed thus far in the chapter.
Windows security is based on a
Code Access Security
Code access security is all about control access to assemblies (or individual
The identity permissions found under the
namespace are used for this purpose. Identity permissions are provided for the following types of assembly identity: strong
Table 9-1. Types of Identity Demands
are performed when assemblies are loaded, they can be specified only declaratively using attributes at compile-time. The compiler places the declarative security in an assembly's manifest, where the CLR can read and act upon it when loading that assembly. A straight demand, on the other hand, occurs at run time and can be specified imperatively or
The following example
<StrongNameIdentityPermission(SecurityAction.LinkDemand,_ PublicKey:= "002400000...")>_ PublicSharedSubProtectedMethod() 'dosomething EndSub
attribute supports the additional properties
. By specifying
, a client can reliably demand an exact version of an assembly. If the client specifies only
, the demand will succeed if the assembly name and signature match, regardless of the assembly version. If the client specifies only
, as in the code example above, the security system will look for only the required signature; this is useful when you want to limit access to a
Attaching a strong name signature to your code involves two steps: creating the strong name key and compiling your assembly with that key. The first step is accomplished by using the SN (strong name) utility provided with the .NET Framework SDK. Below is the command-line syntax for creating a key pair and viewing the public key portion. (You must make an identity demand for code signed with the corresponding private key.)
sn-kkeypair.dat sn-pkeypair.datpublickey.dat sn-tppublickey.dat
For more information about the strong name tool, see Chapter 8.
The second step requires adding a declaration to the assembly to
<Assembly:AssemblyKeyFile("keypair.dat")> PublicClassMyClass 'somethinginteresting EndClass
You can also delay-sign an assembly. Delayed signing
For more information about delayed signing, check out the Framework SDK documentation that
Requesting Code Permissions
You do not
Table 9-2. Permission Request Types
Permission requests can be made only in a declarative fashion and must always be at the assembly level. (The assembly is the unit to which permissions are granted by the security system.) The following code is a request
<Assembly:FileIOPermission(SecurityAction.RequestMinimum,_ Unrestricted:=True)> PublicClassFileMover 'somethinginteresting EndClass
You can make several requests of the same type, in which case the final permission set requested will be the aggregate of all requests of that type. In the following example, RequestMinimum is used twice with different permissions to state that the assembly must have the ability to use Reflection Emit and perform serialization in order for it to function.
<Assembly:ReflectionPermission(SecurityAction.RequestMinimum,_ ReflectionEmit:=True)> <Assembly:SecurityPermission(SecurityAction.RequestMinimum,_ SerializationFormatter:=True)> PublicClassCodeGenerator 'somethinginteresting EndClass
The same permission can also appear in requests of different types. For instance, the following example program uses an
in each of its three requests (
Any permission you refuse using RequestRefuse will not be granted to your assembly even if you request that same permission using RequestMinimum .
In addition to requesting individual permissions, you can also request entire sets of permissions in a compact fashion. The following example shows two requests: one stating that an assembly must have unrestricted access to the file system in order to function, and one stating that it will take any and all other permissions that the security system is willing to grant it.
<Assembly:FileIOPermission(SecurityAction.RequestMinimum,_ Unrestricted:=True)> <Assembly:PermissionSet(SecurityAction.RequestOptional,_ Name:= "FullTrust")> PublicClassFileMover 'somethinginteresting EndClass
The example shows how to request a permission set by name, but it is also possible to use a custom permission set representing the exact permissions you want. For more information on how to do this, search on PermissionSetAttribute in the .NET Framework SDK Reference.
The PERMVIEW tool is useful for verifying that your permission requests are correct. You can run PERMVIEW on a compiled assembly to read the permission requests out of the assembly's manifest and display them, as shown here:
C:\>permviewfilemover.exe Microsoft(R).NETFrameworkPermissionRequestViewer.Version1.0.XXXX.0 Copyright(C)MicrosoftCorp.1998-2001 minimalpermissionset: <PermissionSetclass="System.Security.PermissionSet" version="1"> <IPermissionclass="System.Security.Permissions.FileIOPermission" version="1" Unrestricted="true"/> </PermissionSet> optionalpermissionset: <PermissionSetclass="System.Security.PermissionSet" version="1" Unrestricted="true"/> refusedpermissionset: Notspecified
User identity is a common means of controlling access to a business application or limiting the options available within that application. The
namespace contains classes that help make such role-based security determinations. These classes are highly extensible. They allow host code to provide its own user identity and role information, or they allow it to expose the user account and group information provided by Windows. For more complete details regarding how to extend the role-based security system,
If you simply need to check the user's Windows user name and group memberships from a client application, here is how. The WindowsIdentity class represents an authenticated Windows user, and the WindowsPrincipal class that encapsulates the WindowsIdentity contains information about the user's role memberships. These objects representing the current user are accessible using either a static property of the Thread object or a static method of the WindowsIdentity object. We'll look at examples of both shortly.
Accessing the current principal from the
object is the standard approach, and it works for all types of principal objects. But because this method returns an
, it must be cast as a
before it can be used as one. Notice that before the current principal is accessed, a call to
is made. This is noteworthy because without this call the principal returned would be a
containing no user information. Because the call to
(one not normally given out to
'GettheCurrentUser'sSecurityPolicy AppDomain.CurrentDomain.SetPrincipalPolicy(_ PrincipalPolicy.WindowsPrincipal) DimuserAsWindowsPrincipal=_ CType(System.Threading.Thread.CurrentPrincipal,WindowsPrincipal) DimidentAsWindowsIdentity=user.Identity
Checking for a Windows identity is a very common case, so this identity is easily accessible by using the static GetCurrent method of the WindowsIdentity class, as shown in the following example. Please note, however, that this method requires the same level of permission as the one above.
Once a WindowsPrincipal object is retrieved, a user's group membership can be determined using the IsInRole method. If the goal of checking role group membership is to deny access to an application (as opposed to customizing the user experience), an even simpler approach is to use the Principal Permission to demand the required role.
The .NET Framework SDK ships with some tools you can use to script user, machine, and enterprise security policies.
Scripting Security Policy Changes
The CLR ships with an advanced security policy system that allows for three policy levels: the enterprise policy, the machine policy, and the user policy. Each policy level consists of a tree of code groups. Each code group consists of a membership condition (which might be based on URL of origin or publisher certificate, for example) and an associated permission set. Code is granted the permission set associated with a code group if it meets the respective membership condition. By changing code groups in the user, machine, or enterprise policy, administrators can determine what permissions are granted to assemblies.
If you need to script policy changes, you can use the Code Access Security Policy (
For all standard administrative
Scripting Against Named Code Groups
The CLR's default policy gives each code group a unique name. If code groups have not been deleted or
Table 9-3. Common Code Group Names for Scripting
To see a complete list of code groups and their names in all policy levels, you can use the following caspol command:
To change a code group's permission set, include a command of the following form in your batch script:
To add a new code group, include a command of the following form in your batch script:
To reset policy to the default state at a policy level, include a command of the following form in your batch script:
The following caspol batch script resets policy to the default on all policy levels and grants full trust to intranet applications. Because the granted permissions to code are calculated as the intersection between policy levels and (in default policy) both enterprise and user policy levels are set to full trust, you need to change only the machine policy level in order to guarantee that intranet applications receive full trust. In our example, the following script
The first line in the following script of caspol commands shows how to set policy so code from the Internet will not receive any permissions from machine policy. The second command shows how to add a code group for granting full trust to code signed by the publisher that signed Myexe.exe. Note that the new code group is hung off the root of the machine policy.
Authentication and Authorization
Next, we'll look at .NET and operating system interaction in the areas of authentication and authorization.
Windows Identity in Server Applications
When you use ASP.NET Windows authentication, ASP.NET attaches a WindowsPrincipal object to the current request. This object is used for URL authorization. The application can also use it programmatically to determine whether a requesting identity is in a given role.
IfUser.IsInRole("Administrators")Then DisplayPrivilegedContent() EndIf
The WindowsPrincipal class determines the roles of the user's NT group membership. ASP.NET applications that want to determine their own roles can do so by handling the WindowsAuthentication_OnAuthenticate event in their Global.asax file and attaching their own class that implements System.Security.Principal.IPrincipal to the request, as shown in the following example:
'CreateaclassthatimplementsIPrincipal PublicClassMyPrincipal:ImplementsIPrincipal 'Implementapplication-definedrolemappings EndClass 'InaGlobal.asaxfile PublicSubWindowsAuthentication_OnAuthenticate(SourceAsObject,_ eAsWindowsAuthenticationEventArgs) 'Attachanewapplication-definedclassthatimplementsIPrincipalto 'therequest. 'NotethatsinceIIShasalreadyperformedauthentication,theprovided 'identityisused. e.User=NewMyPrincipal(e.Identity) EndSub
Forms-based authentication is an ASP.NET authentication service that enables applications to provide their own logon user interface and do their own credential verification. ASP.NET authenticates users, redirecting unauthenticated users to the logon page and performing all the necessary cookie management. This
An application must be configured to use forms-based authentication; you set <authentication> to Forms and deny access to anonymous users. The following example shows how this can be done in the Web.config file for the desired application:
<configuration> <system.web> <authenticationmode="Forms"/> <authorization> <denyusers="?" /> </authorization> </system.web> </configuration>
Administrators use forms-based authentication to configure the name of the cookie to use, the protection type, the URL to use for the logon page, the length of time the cookie will be in effect, and the path to use for the issued cookie. Table 9-4 shows the valid attributes for the <Forms> element, which is a subelement of the <authentication> element shown in the following example:
<authenticationmode="Forms"> <formsname=".ASPXCOOKIEDEMO" loginUrl="login.aspx" protection= "all" timeout="30" path="/"> <!--protection="[AllNoneEncryptionValidation]" --> </forms> </authentication>
Table 9-4. Security-Related Forms Attributes
After the application has been configured, you need to provide a logon page. The following example shows a simple logon page. When the sample is run, it requests the Default.aspx page. Unauthenticated requests are redirected to the logon page (Login.aspx), which
After validating the credentials, the application calls the following:
This redirects the user back to the originally requested URL. Applications that do not want to perform the redirection can call FormsAuthentication.GetAuthCookie to retrieve the cookie value or FormsAuthentication.SetAuthCookie to attach a properly encrypted cookie to the outgoing response. These techniques can be useful for applications that provide a logon user interface embedded in the containing page or that want to have more control over where users are redirected. Authentication cookies can be temporary or permanent (persistent). Temporary cookies last only for the duration of the current browser session. When the browser is closed, the cookie is lost. Permanent cookies are saved by the browser and are sent back across browser sessions unless explicitly deleted by the user.
The authentication cookie used by forms authentication consists of one version of the System.Web.Security.FormsAuthenticationTicket class. The information includes the username (but not the password), the version of forms authentication used, the date the cookie was issued, and a field for optional application-specific data.
Application code can revoke or remove authentication cookies using the
It's also possible to supply forms-based authentication services with a list of valid credentials using configuration, as shown in the following example:
<authentication> <credentialspasswordFormat="SHA1" > <username="Mary" password="GASDFSA9823598ASDBAD"/> <username="John" password="ZASDFADSFASD23483142"/> </credentials> </authentication>
The application can then call FormsAuthentication.Authenticate , supplying the username and password, and ASP.NET will verify the credentials. Credentials can be stored in cleartext or as SHA1 or MD5 hashes, according to the value of the passwordFormat attribute. The possible values are listed in Table 9-5.
Table 9-5. Values of the passwordFormat Attribute
Authorizing Users and Roles
ASP.NET is used to control client access to URL resources. It is configurable for the HTTP method used to make the request (
) and can be configured to allow or deny access to groups of users or roles. To
<authorization> <allowusers="email@example.com" /> <allowroles="Admins" /> <denyusers="*" /> </authorization>
Permissible elements for authorization directives are allow or deny . Each allow or deny element must contain a users or a roles attribute. You can specify multiple users or roles in a single element by providing a comma-separated list.
You can indicate the HTTP method by using the Verb attribute:
<allowVERB="POST" users="John,Mary" /> <denyVERB="POST" users="*" /> <allowVERB="GET" users="*" />
This example lets Mary and John POST to the protected resources, while allowing everyone else only to use GET .
Two special (reserved) usernames are similar to the Everyone and Anonymous accounts in Windows:
These special usernames are commonly used by applications that use forms-based authentication to deny access to unauthenticated users, as shown in the following example:
<authorization> <denyusers="?" /> </authorization>
URL authorization is computed hierarchically, and the rules used to determine access are as
This means applications that are not interested in inheriting their configuration should explicitly configure all of the possibilities relevant to them.
The default top-level Web.config file for a given computer allows access to all users. Unless an application is configured to the contrary (and
When roles are checked, URL authorization effectively marches down the list of configured roles and does something that looks like the following pseudocode:
IfUser.IsInRole("ConfiguredRole")Then ApplyRule() EndIf
What this means for your application is that you use your own class that implements System.Security.Principal.IPrincipal to provide your own role-mapping semantics (as explained earlier).
|I l @ ve RuBoard|