简体   繁体   中英

.Net web application behind a WebSeal reverse proxy

We are currently designing a solution that will run as a .Net Web application behind a WebSeal reverse proxy.

I have seen some comments on the net where people have had various problems with this, for example rewriting of viewstate.

Question is: Has anyone implemented this combination of techologies and got it to work?

I made an ASP.NET application workin behind WEBSEAL. After lot of study and development and test it works.

I suggest some issues to help you:

IIS and ASP.NET are case insensitive

("...Login.aspx" and "...login.aspx" both lead to the same page); by default webseal is case sensitive. So you should set WEBSEAL junction to be case insensitive or check any single link (page, javascript, image)

Internal links, written as server relative URLs won't be served

WEBSEAL changes any link referring your application but doesn't change links to other applications. Internal links, written as server relative URLs instead of application relative URLs won't be changed (WEBSEAL doesn't recognize it's the same application) and won't be served (WEBSEAL rejects links that are not modified).
First rule is to check any single link and make it an application relative URL .
Look at rendered HTML if you find <.. href=/ anything> : this ia server relative URL and it is bad.
Look in the Code Behind if you use "= ~/ anything" it is good. If you use "= / anything" OR ResolveUrl(..) it is bad.

But this is not enough: AJAX puts loads of javascript and code inside ScriptResource.axd and WebResource.axd and creates server relative URL to link it. This links are not controlled by programmers and there is no easy way to change them.
Easy solution (if possible): solve the problem setting WEBSEAL junction to be transparent .
Hard solution: write the following code (thanks to this answer )

protected void Page_Load(object sender, EventArgs e)
    {
        //Initialises my dirty hack to remove the leading slash from all web reference files.
        Response.Filter = new WebResourceResponseFilter(Response.Filter);
    }

public class WebResourceResponseFilter : Stream
{
    private Stream baseStream;

    public WebResourceResponseFilter(Stream responseStream)
    {
        if (responseStream == null)
            throw new ArgumentNullException("ResponseStream");
        baseStream = responseStream;
    }

    public override bool CanRead
    { get { return baseStream.CanRead; } }

    public override bool CanSeek
    { get { return baseStream.CanSeek; } }

    public override bool CanWrite
    { get { return baseStream.CanWrite; } }

    public override void Flush()
    { baseStream.Flush(); }

    public override long Length
    { get { return baseStream.Length; } }

    public override long Position
    {
        get { return baseStream.Position; }
        set { baseStream.Position = value; }
    }

    public override int Read(byte[] buffer, int offset, int count)
    { return baseStream.Read(buffer, offset, count); }

    public override long Seek(long offset, System.IO.SeekOrigin origin)
    { return baseStream.Seek(offset, origin); }

    public override void SetLength(long value)
    { baseStream.SetLength(value); }

    public override void Write(byte[] buffer, int offset, int count)
    {
        //Get text from response stream.
        string originalText = System.Text.Encoding.UTF8.GetString(buffer, offset, count);

        //Alter the text.
        originalText = originalText.Replace(HttpContext.Current.Request.ApplicationPath + "/WebResource.axd",
            VirtualPathUtility.MakeRelative(HttpContext.Current.Request.Url.AbsolutePath, "~/WebResource.axd"));
        originalText = originalText.Replace(HttpContext.Current.Request.ApplicationPath + "/ScriptResource.axd",
            VirtualPathUtility.MakeRelative(HttpContext.Current.Request.Url.AbsolutePath, "~/ScriptResource.axd"));

        //Write the altered text to the response stream.
        buffer = System.Text.Encoding.UTF8.GetBytes(originalText);
        this.baseStream.Write(buffer, 0, buffer.Length);

    }

This intercepts the stream to the page and replaces all occurrences of "/WebResource.axd" or "ScriptResource.axd" with "../../WebResource.axd" and "../../ScriptResource.axd"

Develop code to get actual WEBSEAL user

WEBSEAL has been configured to put username inside HTTP_IV_USER . I created Webseal\\Login.aspx form to read it programmatically. Now, in order to make this user the CurrentUser I put an hidden asp.Login

<span style="visibility:hidden"> 
<asp:Login ID="Login1" runat="server" DestinationPageUrl="~/Default.aspx">..

and clicked the button programmatically

protected void Page_Load(object sender, EventArgs e)
{
    string username = Request.ServerVariables["HTTP_IV_USER"];
    (Login1.FindControl("Password") as TextBox).Text = MyCustomProvider.PswJump;
    if (!string.IsNullOrEmpty(username))
    {
        (Login1.FindControl("UserName") as TextBox).Text = username;
        Button btn = Login1.FindControl("LoginButton") as Button;
        ((IPostBackEventHandler)btn).RaisePostBackEvent(null);
     }
    else
    {
        lblError.Text = "Login error.";
    }
}

When LoginButton fires, application reads UserName (set from WEBSEAL variable) and password (hard coded). So i implemented a custom membership provider that validates users and sets current Principal.

Changes in web.config

loginUrl is the URL for the login page that the FormsAuthentication class will redirect to. It has been set to WEBSEAL portal: not authenticated user and logout button will redirect to portal.

<authentication mode="Forms">
  <forms loginUrl="https://my.webseal.portal/" defaultUrl="default.aspx"...."/>
</authentication>

Since Webseal/login.aspx is NOT default login page, authorization tag grants access to not authenticated users:

<location path="Webseal/login.aspx">
    <system.web>
        <authorization>
            <allow users="*"/>
        </authorization>
    </system.web>
</location>

Application is set to use custom membership providers:

 <membership defaultProvider="MyCustomMembershipProvider">
  <providers>
    <add name="MyCustomMembershipProvider" type="MyNamespace.MyCustomMembershipProvider" connectionStringName="LocalSqlServer"/>
  </providers>
</membership>
<roleManager enabled="true" defaultProvider="MyCustomRoleProvider">
  <providers>
    <add name="MyCustomRoleProvider" type="MyNamespace.MyCustomRoleProvider" connectionStringName="LocalSqlServer"/>
  </providers>
</roleManager>

Debug is set to off:

<compilation debug="false" targetFramework="4.0">

that's all folks!

I initially has some issues with the ASP.Net app when accessed through WebSeal. I was running the site on a development server. What worked for me was to deploy the application with debugging turned off in the config file.

<compilation debug="false" ...>

With debugging turned on, there were some AJAX calls that would work fine when I accessed the site directly but would fail when access through WebSeal. Once I turned the debugging off, everything work fine.

Also, because WebSeal requires anonymous authentication, we couldn't have used Windows Authentication.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM