如何在ASP.NET MVC中加密URL?

5

我需要在我的ASP.NET MVC应用程序中加密URL。

我是否需要在全局页面中的路由集合中编写代码以加密所有URL?


URL加密有什么好处?或者你是想编码URL的某些部分吗?如果是这样,Base64编码应该可以帮助你。 - Matthew Abbott
我的意思是,最终用户不应该看到我的URL带有Id值等等。 - kumar
如何在全局文件中声明这些内容? - kumar
5
可能是与https://dev59.com/EHNA5IYBdhLWcg3wmfO5相同的问题。 - rsenna
@kumar 即使这篇帖子不是你发的,但你的帖子涵盖了与之前帖子相同的主题,使得你的帖子成为之前帖子的重复。 - George Stocker
显示剩余2条评论
5个回答

7
加密URL是一个不好的想法。毫无疑问。
你可能会想知道我为什么这么说。
我曾经为一家加密了它的URL的公司开发过一个WebForms应用程序。仅从URL上,几乎不可能知道我正在访问代码的哪一部分来引起问题。由于调用WebForm控件的动态性质,你只需要知道软件将要走的路径。这非常令人不安。
再加上应用程序中没有基于角色的授权。所有的授权都是基于URL被加密。如果你可以解密URL(如果它可以被加密,那么它也可以被解密),那么你就可以模仿另一个用户输入另一个加密的URL。我不是说这很简单,但它确实可能发生。
最后,你在使用互联网时有多少次看到加密的URL?当你看到时,你是否感到有点失落?我是这样的。URLs旨在传达公共信息。如果你不希望它这样做,不要将其放在URL中(或者要求对站点的敏感区域进行授权)。
你在数据库中使用的ID应该是用户可以看到的ID。如果你正在使用社会安全号码作为主键,那么你应该为Web应用程序更改该架构。
任何可以加密的东西都可以被解密,因此易受攻击。
如果你希望用户只能在被授权的情况下访问某些URL,则应使用ASP.NET MVC中可用的[Authorize]属性。

6
“任何可以加密的东西都可以被解密,因此易受攻击。”这是一个非常无用的陈述。我同意你的其他评论。 - Noon Silk
4
@George:这个陈述的含义是不应该对任何内容进行加密,这显然是荒谬的。 - Noon Silk
1
这绝对不是加密的状态,否则在线银行和购物将会陷入困境。我们使用密码哈希值,因为这意味着一些流氓员工无法访问用户可能在其他50个网站上使用的密码。我认为URL加密没有意义,但像SSL这样的东西可以很好地防止第三方查看您的数据。 - StriplingWarrior
2
@George:我不能和你继续讨论这个话题了,我们已经陷入了荒谬的境地。我会留给你处理。 - Noon Silk
1
抱歉,我必须在这里同意Noon的观点。有时候URL中的ID应该进行某种混淆,因为即使是“已认证用户”也会尝试更改路由中的ID值,以查看他们可能不应该看到的内容。我正在研究这个问题,并会给出更好的答案。 - enorl76
显示剩余8条评论

6
整个 URL 加密,我认为不是一个好主意。但加密 URL 参数并不是一个坏主意,实际上这是一项有效且广泛使用的技术。
如果你真的想要加密/解密 URL 参数(这绝不是个坏主意),那么请查看 Mads Kristensen 的文章 "HttpModule for query string encryption"。
你需要修改 context_BeginRequest 才能使其在 MVC 中工作。只需删除 if 语句中检查原始 URL 是否包含 "aspx" 的第一部分即可。
话虽如此,我已经在几个项目中使用了该模块(如果需要,还有一个转换后的 VB 版本),在大多数情况下,它都可以完美地工作。
但是,有些情况下,我发现 jQuery/Ajax 调用无法正常工作。我相信该模块可以修改以弥补这些情况。

3
根据这里的答案,它们对我没有用,顺便说一下,我找到了另一个解决方案,基于我的特定MVC实现,以及它也取决于您是否使用II7或II6。在两种情况下都需要进行轻微更改。
II6
首先,您需要将以下内容添加到您的web.config中(根目录,而不是View文件夹中的文件)。
 <system.web>
    <httpModules>
      <add name="URIHandler" type="URIHandler" />
    </httpModules>

将此代码添加到您的 web.config 文件中(根目录下的,不是 View 文件夹中的):

II7

  <system.webServer>
    <validation validateIntegratedModeConfiguration="false" />
    <modules runAllManagedModulesForAllRequests="true">
      <remove name="URIHandler" />
      <add name="URIHandler" type="URIHandler" />
    </modules>

或者您可以两个都添加。其实并不重要。
接下来使用这个类。我将它称为——URIHandler,您可能已经注意到了。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.IO;
using System.Text;
using System.Security.Cryptography;
using System.Diagnostics.CodeAnalysis;

public class URIHandler : IHttpModule
    {
        #region IHttpModule members
        public void Dispose()
        {
        }

        public void Init(HttpApplication context)
        {
            context.BeginRequest += new EventHandler(context_BeginRequest);
        }
        #endregion

        private const string PARAMETER_NAME = "enc=";
        private const string ENCRYPTION_KEY = "key";

        private void context_BeginRequest(object sender, EventArgs e)
        {
            HttpContext context = HttpContext.Current;
            //if (context.Request.Url.OriginalString.Contains("aspx") && context.Request.RawUrl.Contains("?"))
            if (context.Request.RawUrl.Contains("?"))
            {
                string query = ExtractQuery(context.Request.RawUrl);
                string path = GetVirtualPath();

                if (query.StartsWith(PARAMETER_NAME, StringComparison.OrdinalIgnoreCase))
                {
                    // Decrypts the query string and rewrites the path.
                    string rawQuery = query.Replace(PARAMETER_NAME, string.Empty);
                    string decryptedQuery = Decrypt(rawQuery);
                    context.RewritePath(path, string.Empty, decryptedQuery);
                }
                else if (context.Request.HttpMethod == "GET")
                {
                    // Encrypt the query string and redirects to the encrypted URL.
                    // Remove if you don't want all query strings to be encrypted automatically.
                    string encryptedQuery = Encrypt(query);
                    context.Response.Redirect(path + encryptedQuery);
                }
            }
        }

        /// <summary>
        /// Parses the current URL and extracts the virtual path without query string.
        /// </summary>
        /// <returns>The virtual path of the current URL.</returns>
        private static string GetVirtualPath()
        {
            string path = HttpContext.Current.Request.RawUrl;
            path = path.Substring(0, path.IndexOf("?"));
            path = path.Substring(path.LastIndexOf("/") + 1);
            return path;
        }

        /// <summary>
        /// Parses a URL and returns the query string.
        /// </summary>
        /// <param name="url">The URL to parse.</param>
        /// <returns>The query string without the question mark.</returns>
        private static string ExtractQuery(string url)
        {
            int index = url.IndexOf("?") + 1;
            return url.Substring(index);
        }

        #region Encryption/decryption

        /// <summary>
        /// The salt value used to strengthen the encryption.
        /// </summary>
        private readonly static byte[] SALT = Encoding.ASCII.GetBytes(ENCRYPTION_KEY.Length.ToString());

        /// <summary>
        /// Encrypts any string using the Rijndael algorithm.
        /// </summary>
        /// <param name="inputText">The string to encrypt.</param>
        /// <returns>A Base64 encrypted string.</returns>
        [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")]
        public static string Encrypt(string inputText)
        {
            RijndaelManaged rijndaelCipher = new RijndaelManaged();
            byte[] plainText = Encoding.Unicode.GetBytes(inputText);
            PasswordDeriveBytes SecretKey = new PasswordDeriveBytes(ENCRYPTION_KEY, SALT);

            using (ICryptoTransform encryptor = rijndaelCipher.CreateEncryptor(SecretKey.GetBytes(32), SecretKey.GetBytes(16)))
            {
                using (MemoryStream memoryStream = new MemoryStream())
                {
                    using (CryptoStream cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
                    {
                        cryptoStream.Write(plainText, 0, plainText.Length);
                        cryptoStream.FlushFinalBlock();
                        return "?" + PARAMETER_NAME + Convert.ToBase64String(memoryStream.ToArray());
                    }
                }
            }
        }

        /// <summary>
        /// Decrypts a previously encrypted string.
        /// </summary>
        /// <param name="inputText">The encrypted string to decrypt.</param>
        /// <returns>A decrypted string.</returns>
        [SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times")]
        public static string Decrypt(string inputText)
        {
            RijndaelManaged rijndaelCipher = new RijndaelManaged();
            byte[] encryptedData = Convert.FromBase64String(inputText);
            PasswordDeriveBytes secretKey = new PasswordDeriveBytes(ENCRYPTION_KEY, SALT);

            using (ICryptoTransform decryptor = rijndaelCipher.CreateDecryptor(secretKey.GetBytes(32), secretKey.GetBytes(16)))
            {
                using (MemoryStream memoryStream = new MemoryStream(encryptedData))
                {
                    using (CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
                    {
                        byte[] plainText = new byte[encryptedData.Length];
                        int decryptedCount = cryptoStream.Read(plainText, 0, plainText.Length);
                        return Encoding.Unicode.GetString(plainText, 0, decryptedCount);
                    }
                }
            }
        }
        #endregion
    }

你不需要一个“命名空间”。
上面的类可以完成加密和解密任何以“?”字符开头的URL参数所需的所有操作。它甚至可以很好地将您的参数变量重命名为“enc”,这是一个额外的奖励。
最后,将该类放置在您的“App_Start”文件夹中,而不是“App_Code”文件夹中,因为那会与“无歧义错误”发生冲突。
完成了。
信用:

https://www.codeproject.com/questions/1036066/how-to-hide-url-parameter-asp-net-mvc

https://msdn.microsoft.com/en-us/library/aa719858(v=vs.71).aspx

HttpModule的Init方法没有被调用

C#请在类型名称中明确指定程序集

https://dev59.com/UHM_5IYBdhLWcg3waSX9 being-called


这对包含 ? 的 URL 很有效。如果没有 ? 传递 ID,你会如何加密呢?例如:myDomain/myController/myAction/10 而不是:myDomain/myController/myAction?myID=10 - DNKROZ
1
我稍微修改了你的代码,以允许在使用默认MVC样式传递ID时进行加密。如果需要,可以添加代码。 - DNKROZ
@DNKROZ 对于你的第一个问题,那正是我使用它的目的,用于参数化URL选项,如?CustID=1234。 - Fandango68
1
我还注意到用户在使用这个解决方案时仍然可以进行查询字符串操作。我现在正在修改代码以加密视图中的操作链接,以防止这种情况发生。 - DNKROZ
1
我最初这样做,它工作得很好(我也检查了 / 前的所有数字值),但是我注意到有人可以使用F12开发工具在视图中更改ID参数,然后在加密之前成功操作URL。因此,我不得不放弃上面的解决方案,并正在实施另一个解决方案,该解决方案涉及扩展 ActionLink。 我正在使用 https://www.codeproject.com/Articles/130588/Preventive-Method-for-URL-Request-Forgery-An-Examp 作为参考。 - DNKROZ
显示剩余2条评论

0

您可以创建自定义的HTML助手来加密查询字符串,并使用自定义操作过滤器属性进行解密和获取原始值。您可以全局实现它,这样不会花费太多时间。您可以参考这里Asp.Net MVC中的URL加密。这将帮助您使用自定义助手和自定义操作过滤器属性。


-1

全局加密所有URL参数(查询字符串)可能是没有意义的。大多数参数都是HttpGet使用的显示项。如果全部加密,那么这将不能生成非常信息化的页面。但是,如果有一些敏感参数仅在客户端上隐藏字段(键),最终返回给服务器以识别记录,则可能值得加密。

考虑此viewModel:

public viewModel
{
    public int key {get;set;}           // Might want to encrypt
    public string FirstName {get;set;}  // Don't want this encrypted
    public string LastName {get;set;}   // Don't want this encrypted
}

viewModel会被转换成一个查询字符串,类似于...appName.com/index?Id=2;FirstName="John";LastName="Doe"

如果将此viewModel作为查询字符串传递,那么加密名字的意义何在?

需要注意的是,查询字符串是HttpGet。HttpPost使用会话来传递值而不是查询字符串。 HttpPost会话是加密的。但是,HttpPost有开销。因此,如果您的页面确实包含需要显示的敏感数据(例如用户当前密码),则考虑改用HttpPost。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接