www.extutorial.com
Open in
urlscan Pro
167.86.109.65
Public Scan
URL:
https://www.extutorial.com/en/share/362499
Submission Tags: falconsandbox
Submission: On April 09 via api from US — Scanned from DE
Submission Tags: falconsandbox
Submission: On April 09 via api from US — Scanned from DE
Form analysis
2 forms found in the DOMGET /search
<form action="/search" method="get" id="search-block-form" accept-charset="UTF-8">
<div class="js-form-item form-item custom-control custom-search js-form-type-search form-type-search js-form-item-keys form-item-keys form-no-label"><label for="edit-keys" class="custom-control-label visually-hidden">Search</label><input
title="Enter the terms you wish to search for." data-drupal-selector="edit-keys" type="search" id="edit-keys" name="key_word" value="" size="15" maxlength="80" class="form-search form-control" placeholder="Search"><input type="hidden" name="op"
value="Search"></div>
<div data-drupal-selector="edit-actions" class="form-actions js-form-wrapper form-wrapper" id="edit-actions"><input data-drupal-selector="edit-submit" type="submit" id="edit-submit" value="Search"
class="button js-form-submit form-submit btn btn-primary"></div>
</form>
POST /en/comment/reply/node/362499/comment
<form class="comment-comment-form comment-form" data-drupal-selector="comment-form" action="/en/comment/reply/node/362499/comment" method="post" id="comment-form" accept-charset="UTF-8">
<div class="js-form-item form-item custom-control custom-textfield js-form-type-textfield form-type-textfield js-form-item-name form-item-name">
<label for="edit-name" class="custom-control-label">Your name</label>
<input data-drupal-default-value="Anonymous" data-drupal-selector="edit-name" type="text" id="edit-name" name="name" value="" size="30" maxlength="60" class="form-text form-control">
</div>
<input autocomplete="off" data-drupal-selector="form-j-mjjmmdcukr57pr8k5to2wb1ligrjlfbbzhoxqvt4" type="hidden" name="form_build_id" value="form-j-_MjJmMdcUkR57pR8K5To2wb1LIgRJLfBbzhoxQVT4" class="form-control">
<input data-drupal-selector="edit-comment-comment-form" type="hidden" name="form_id" value="comment_comment_form" class="form-control">
<div class="field--type-string field--name-subject field--widget-string-textfield js-form-wrapper form-wrapper" data-drupal-selector="edit-subject-wrapper" id="edit-subject-wrapper">
<div class="js-form-item form-item custom-control custom-textfield js-form-type-textfield form-type-textfield js-form-item-subject-0-value form-item-subject-0-value">
<label for="edit-subject-0-value" class="custom-control-label">主题</label>
<input class="js-text-full text-full form-text form-control" data-drupal-selector="edit-subject-0-value" type="text" id="edit-subject-0-value" name="subject[0][value]" value="" size="60" maxlength="64" placeholder="">
</div>
</div>
<div class="field--type-text-long field--name-comment-body field--widget-text-textarea js-form-wrapper form-wrapper" data-drupal-selector="edit-comment-body-wrapper" id="edit-comment-body-wrapper">
<div class="js-text-format-wrapper text-format-wrapper js-form-item form-item">
<div class="js-form-item form-item custom-control custom-textarea js-form-type-textarea form-type-textarea js-form-item-comment-body-0-value form-item-comment-body-0-value">
<label for="edit-comment-body-0-value" class="custom-control-label js-form-required form-required">Comment</label>
<div class="form-textarea-wrapper">
<textarea class="js-text-full text-full form-textarea required form-control resize-vertical" data-drupal-selector="edit-comment-body-0-value" id="edit-comment-body-0-value" name="comment_body[0][value]" rows="5" cols="60" placeholder=""
required="required" aria-required="true"></textarea>
</div>
</div>
<div class="js-filter-wrapper filter-wrapper js-form-wrapper form-wrapper" data-drupal-selector="edit-comment-body-0-format" id="edit-comment-body-0-format">
<div class="filter-help js-form-wrapper form-wrapper" data-drupal-selector="edit-comment-body-0-format-help" id="edit-comment-body-0-format-help">
<a href="/en/filter/tips" target="_blank" data-drupal-selector="edit-comment-body-0-format-help-about" id="edit-comment-body-0-format-help-about">About text formats</a></div>
<div class="js-filter-guidelines filter-guidelines js-form-wrapper form-wrapper" data-drupal-selector="edit-comment-body-0-format-guidelines" id="edit-comment-body-0-format-guidelines">
<div data-drupal-format-id="restricted_html" class="filter-guidelines-item filter-guidelines-restricted_html">
<h4 class="label" style="display: none;">Restricted HTML</h4>
<ul class="tips">
<li>Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id>
<h4 id> <h5 id> <h6 id></li>
<li>Lines and paragraphs break automatically.</li>
<li>Web page addresses and email addresses turn into links automatically.</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<div data-drupal-selector="edit-captcha" class="captcha">
<input data-drupal-selector="edit-captcha-sid" type="hidden" name="captcha_sid" value="13220997" class="form-control">
<input data-drupal-selector="edit-captcha-token" type="hidden" name="captcha_token" value="TpxIGop7Rrp4gZ9uPN3udAdOaveJ5PYlQmWwu9-NPVY" class="form-control">
<input data-drupal-selector="edit-captcha-response" type="hidden" name="captcha_response" value="Google no captcha" class="form-control">
<div class="g-recaptcha" data-sitekey="6LdbPGAUAAAAAOdPB5V-TPAsS0lz9po4BR7pbnWT" data-theme="light" data-type="image">
<div style="width: 304px; height: 78px;">
<div><iframe title="reCAPTCHA"
src="https://www.recaptcha.net/recaptcha/api2/anchor?ar=1&k=6LdbPGAUAAAAAOdPB5V-TPAsS0lz9po4BR7pbnWT&co=aHR0cHM6Ly93d3cuZXh0dXRvcmlhbC5jb206NDQz&hl=en&type=image&v=Y-cOIEkAqcfDdup_qnnmkxIC&theme=light&size=normal&cb=rww2ieslb5ep"
width="304" height="78" role="presentation" name="a-s9tsiqnyy1uy" frameborder="0" scrolling="no" sandbox="allow-forms allow-popups allow-same-origin allow-scripts allow-top-navigation allow-modals allow-popups-to-escape-sandbox"></iframe>
</div><textarea id="g-recaptcha-response" name="g-recaptcha-response" class="g-recaptcha-response"
style="width: 250px; height: 40px; border: 1px solid rgb(193, 193, 193); margin: 10px 25px; padding: 0px; resize: none; display: none;"></textarea>
</div><iframe style="display: none;"></iframe>
</div>
<noscript>
<div style="width: 302px; height: 352px;">
<div style="width: 302px; height: 352px; position: relative;">
<div style="width: 302px; height: 352px; position: absolute;">
<iframe src="https://www.recaptcha.net/recaptcha/api/fallback?k=6LdbPGAUAAAAAOdPB5V-TPAsS0lz9po4BR7pbnWT&hl=en" frameborder="0" scrolling="no" style="width: 302px; height:352px; border-style: none;"></iframe>
</div>
<div style="width: 250px; height: 80px; position: absolute; border-style: none; bottom: 21px; left: 25px; margin: 0px; padding: 0px; right: 25px;">
<textarea id="g-recaptcha-response" name="g-recaptcha-response" class="g-recaptcha-response" style="width: 250px; height: 80px; border: 1px solid #c1c1c1; margin: 0px; padding: 0px; resize: none;" value=""></textarea>
</div>
</div>
</div>
</noscript>
<input data-drupal-selector="edit-captcha-cacheable" type="hidden" name="captcha_cacheable" value="1" class="form-control">
</div>
<div data-drupal-selector="edit-actions" class="form-actions js-form-wrapper form-wrapper" id="edit-actions">
<input data-drupal-selector="edit-submit" type="submit" id="edit-submit" name="op" value="Save" class="button button--primary js-form-submit form-submit btn btn-primary">
</div>
</form>
Text Content
Skip to main content 起点教程 天道酬勤,学无止境 起点教程 Toggle navigation * Home * 博客 * 技术分享 * 问答 Search FOOTER * Contact * Chinese, Simplified * English USER ACCOUNT MENU * Log in BREADCRUMB 1. Home 2. 技术分享 3. Encrypted Configuration In ASP.NET Core ENCRYPTED CONFIGURATION IN ASP.NET CORE Submitted by Angus on Wed, 04/14/2021 - 12:09 Question With web.config going away, what is the preferred way to store sensitive info (passwords, tokens) in the configurations of a web app built using ASP.NET Core? Is there a way to automatically get encrypted configuration sections in appsettings.json? Answer1 User secrets looks like a good solution for storing passwords, and, generally, application secrets, at least during development. Check the official Microsoft documentation. You can also review this other SO question. This is just a way to "hide" your secrets during development process and to avoid disclosing them into the source tree; the Secret Manager tool does not encrypt the stored secrets and should not be treated as a trusted store. If you want to bring an encrypted appsettings.json to production, you can do so by building a custom configuration provider. For example: public class CustomConfigProvider : ConfigurationProvider, IConfigurationSource { public CustomConfigProvider() { } public override void Load() { Data = UnencryptMyConfiguration(); } private IDictionary<string, string> UnencryptMyConfiguration() { // do whatever you need to do here, for example load the file and unencrypt key by key //Like: var configValues = new Dictionary<string, string> { {"key1", "unencryptedValue1"}, {"key2", "unencryptedValue2"} }; return configValues; } private IDictionary<string, string> CreateAndSaveDefaultValues(IDictionary<string, string> defaultDictionary) { var configValues = new Dictionary<string, string> { {"key1", "encryptedValue1"}, {"key2", "encryptedValue2"} }; return configValues; } public IConfigurationProvider Build(IConfigurationBuilder builder) { return new CustomConfigProvider(); } } Define a static class for your extension method: public static class CustomConfigProviderExtensions { public static IConfigurationBuilder AddEncryptedProvider(this IConfigurationBuilder builder) { return builder.Add(new CustomConfigProvider()); } } And then you can activate it: // Set up configuration sources. var builder = new ConfigurationBuilder() .AddJsonFile("appsettings.json") .AddEncryptedProvider() .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true); Answer2 I agree with @CoderSteve that writing a whole new provider is too much work. It also doesn't build on the existing standard JSON architecture. Here is a solution that I come up with the builds on top of the standard JSON architecture, uses the preferred .Net Core encryption libraries, and is very DI friendly. public static class IServiceCollectionExtensions { public static IServiceCollection AddProtectedConfiguration(this IServiceCollection services) { services .AddDataProtection() .PersistKeysToFileSystem(new DirectoryInfo(@"c:\keys")) .ProtectKeysWithDpapi(); return services; } public static IServiceCollection ConfigureProtected<TOptions>(this IServiceCollection services, IConfigurationSection section) where TOptions: class, new() { return services.AddSingleton(provider => { var dataProtectionProvider = provider.GetRequiredService<IDataProtectionProvider>(); section = new ProtectedConfigurationSection(dataProtectionProvider, section); var options = section.Get<TOptions>(); return Options.Create(options); }); } private class ProtectedConfigurationSection : IConfigurationSection { private readonly IDataProtectionProvider _dataProtectionProvider; private readonly IConfigurationSection _section; private readonly Lazy<IDataProtector> _protector; public ProtectedConfigurationSection( IDataProtectionProvider dataProtectionProvider, IConfigurationSection section) { _dataProtectionProvider = dataProtectionProvider; _section = section; _protector = new Lazy<IDataProtector>(() => dataProtectionProvider.CreateProtector(section.Path)); } public IConfigurationSection GetSection(string key) { return new ProtectedConfigurationSection(_dataProtectionProvider, _section.GetSection(key)); } public IEnumerable<IConfigurationSection> GetChildren() { return _section.GetChildren() .Select(x => new ProtectedConfigurationSection(_dataProtectionProvider, x)); } public IChangeToken GetReloadToken() { return _section.GetReloadToken(); } public string this[string key] { get => GetProtectedValue(_section[key]); set => _section[key] = _protector.Value.Protect(value); } public string Key => _section.Key; public string Path => _section.Path; public string Value { get => GetProtectedValue(_section.Value); set => _section.Value = _protector.Value.Protect(value); } private string GetProtectedValue(string value) { if (value == null) return null; return _protector.Value.Unprotect(value); } } } Wire up your protected config sections like this: public void ConfigureServices(IServiceCollection services) { services.AddMvc(); // Configure normal config settings services.Configure<MySettings>(Configuration.GetSection("MySettings")); // Configure protected config settings services.AddProtectedConfiguration(); services.ConfigureProtected<MyProtectedSettings>(Configuration.GetSection("MyProtectedSettings")); } You can easily create encrypted values for your config files using a controller like this: [Route("encrypt"), HttpGet, HttpPost] public string Encrypt(string section, string value) { var protector = _dataProtectionProvider.CreateProtector(section); return protector.Protect(value); } Usage: http://localhost/cryptography/encrypt?section=SectionName:KeyName&value=PlainTextValue Answer3 I didn't want to write a custom provider – way too much work. I just wanted to tap into JsonConfigurationProvider, so I figured out a way that works for me, hope it helps someone. public class JsonConfigurationProvider2 : JsonConfigurationProvider { public JsonConfigurationProvider2(JsonConfigurationSource2 source) : base(source) { } public override void Load(Stream stream) { // Let the base class do the heavy lifting. base.Load(stream); // Do decryption here, you can tap into the Data property like so: Data["abc:password"] = MyEncryptionLibrary.Decrypt(Data["abc:password"]); // But you have to make your own MyEncryptionLibrary, not included here } } public class JsonConfigurationSource2 : JsonConfigurationSource { public override IConfigurationProvider Build(IConfigurationBuilder builder) { EnsureDefaults(builder); return new JsonConfigurationProvider2(this); } } public static class JsonConfigurationExtensions2 { public static IConfigurationBuilder AddJsonFile2(this IConfigurationBuilder builder, string path, bool optional, bool reloadOnChange) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } if (string.IsNullOrEmpty(path)) { throw new ArgumentException("File path must be a non-empty string."); } var source = new JsonConfigurationSource2 { FileProvider = null, Path = path, Optional = optional, ReloadOnChange = reloadOnChange }; source.ResolveFileProvider(); builder.Add(source); return builder; } } Answer4 I managed to create a custom JSON configuration provider which uses DPAPI to encrypt and decrypt secrets. It basically uses simple regular expressions that you can define to specify what parts of the JSON needs to be encrypted. The following steps are performed: 1. Json file is loaded 2. Determine whether the JSON parts that match the given regular expressions are already encrypted (or not). This is done by base-64 decoding of the JSON part and verify whether it starts with the expected prefix !ENC!) 3. If not encrypted, then encrypt the JSON part by first using DPAPI and secondly add the prefix !ENC! and encode to base-64 4. Overwrite the unencrypted JSON parts with the encrypted (base-64) values in the Json file > Note that the base-64 does not bring better security, but only hides the > prefix !ENC! for cosmetic reasons. This is just a matter of taste of course ;) This solution consists of the following classes: 1. ProtectedJsonConfigurationProvider class (= custom JsonConfigurationProvider) 2. ProtectedJsonConfigurationSource class (= custom JsonConfigurationSource) 3. AddProtectedJsonFile() extension method on the IConfigurationBuilder in order to simple add the protected configuration Assuming the following initial authentication.json file: { "authentication": { "credentials": [ { user: "john", password: "just a password" }, { user: "jane", password: "just a password" } ] } } Which becomes (sort of) the following after loading { "authentication": { "credentials": [ { "user": "john", "password": "IUVOQyEBAAAA0Iyd3wEV0R==" }, { "user": "jane", "password": "IUVOQyEBAAAA0Iyd3wEV0R==" } ] } } And assuming the following configuration class based on the json format public class AuthenticationConfiguration { [JsonProperty("credentials")] public Collection<CredentialConfiguration> Credentials { get; set; } } public class CredentialConfiguration { [JsonProperty("user")] public string User { get; set; } [JsonProperty("password")] public string Password { get; set; } } Below the sample code: //Note that the regular expression will cause the authentication.credentials.password path to be encrypted. //Also note that the byte[] contains the entropy to increase security var configurationBuilder = new ConfigurationBuilder() .AddProtectedJsonFile("authentication.json", true, new byte[] { 9, 4, 5, 6, 2, 8, 1 }, new Regex("authentication:credentials:[0-9]*:password")); var configuration = configurationBuilder.Build(); var authenticationConfiguration = configuration.GetSection("authentication").Get<AuthenticationConfiguration>(); //Get the decrypted password from the encrypted JSON file. //Note that the ProtectedJsonConfigurationProvider.TryGet() method is called (I didn't expect that :D!) var password = authenticationConfiguration.Credentials.First().Password > Install the Microsoft.Extensions.Configuration.Binder package in order to get > the configuration.GetSection("authentication").Get<T>() implementation And finally the classes in which the magic happens :) /// <summary>Represents a <see cref="ProtectedJsonConfigurationProvider"/> source</summary> public class ProtectedJsonConfigurationSource : JsonConfigurationSource { /// <summary>Gets the byte array to increse protection</summary> internal byte[] Entropy { get; private set; } /// <summary>Represents a <see cref="ProtectedJsonConfigurationProvider"/> source</summary> /// <param name="entropy">Byte array to increase protection</param> /// <exception cref="ArgumentNullException"/> public ProtectedJsonConfigurationSource(byte[] entropy) { this.Entropy = entropy ?? throw new ArgumentNullException(Localization.EntropyNotSpecifiedError); } /// <summary>Builds the configuration provider</summary> /// <param name="builder">Builder to build in</param> /// <returns>Returns the configuration provider</returns> public override IConfigurationProvider Build(IConfigurationBuilder builder) { EnsureDefaults(builder); return new ProtectedJsonConfigurationProvider(this); } /// <summary>Gets or sets the protection scope of the configuration provider. Default value is <see cref="DataProtectionScope.CurrentUser"/></summary> public DataProtectionScope Scope { get; set; } /// <summary>Gets or sets the regular expressions that must match the keys to encrypt</summary> public IEnumerable<Regex> EncryptedKeyExpressions { get; set; } } /// <summary>Represents a provider that protects a JSON configuration file</summary> public partial class ProtectedJsonConfigurationProvider : JsonConfigurationProvider { private readonly ProtectedJsonConfigurationSource protectedSource; private readonly HashSet<string> encryptedKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private static readonly byte[] encryptedPrefixBytes = Encoding.UTF8.GetBytes("!ENC!"); /// <summary>Checks whether the given text is encrypted</summary> /// <param name="text">Text to check</param> /// <returns>Returns true in case the text is encrypted</returns> private bool isEncrypted(string text) { if (text == null) { return false; } //Decode the data in order to verify whether the decoded data starts with the expected prefix byte[] decodedBytes; try { decodedBytes = Convert.FromBase64String(text); } catch (FormatException) { return false; } return decodedBytes.Length >= encryptedPrefixBytes.Length && decodedBytes.AsSpan(0, encryptedPrefixBytes.Length).SequenceEqual(encryptedPrefixBytes); } /// <summary>Converts the given key to the JSON token path equivalent</summary> /// <param name="key">Key to convert</param> /// <returns>Returns the JSON token path equivalent</returns> private string convertToTokenPath(string key) { var jsonStringBuilder = new StringBuilder(); //Split the key by ':' var keyParts = key.Split(':'); for (var keyPartIndex = 0; keyPartIndex < keyParts.Length; keyPartIndex++) { var keyPart = keyParts[keyPartIndex]; if (keyPart.All(char.IsDigit)) { jsonStringBuilder.Append('[').Append(keyPart).Append(']'); } else if (keyPartIndex > 0) { jsonStringBuilder.Append('.').Append(keyPart); } else { jsonStringBuilder.Append(keyPart); } } return jsonStringBuilder.ToString(); } /// <summary>Writes the given encrypted key/values to the JSON oconfiguration file</summary> /// <param name="encryptedKeyValues">Encrypted key/values to write</param> private void writeValues(IDictionary<string, string> encryptedKeyValues) { try { if (encryptedKeyValues == null || encryptedKeyValues.Count == 0) { return; } using (var stream = new FileStream(this.protectedSource.Path, FileMode.Open, FileAccess.ReadWrite)) { JObject json; using (var streamReader = new StreamReader(stream, Encoding.UTF8, true, 4096, true)) { using (var jsonTextReader = new JsonTextReader(streamReader)) { json = JObject.Load(jsonTextReader); foreach (var encryptedKeyValue in encryptedKeyValues) { var tokenPath = this.convertToTokenPath(encryptedKeyValue.Key); var value = json.SelectToken(tokenPath) as JValue; if (value.Value != null) { value.Value = encryptedKeyValue.Value; } } } } stream.Seek(0, SeekOrigin.Begin); using (var streamWriter = new StreamWriter(stream)) { using (var jsonTextWriter = new JsonTextWriter(streamWriter) { Formatting = Formatting.Indented }) { json.WriteTo(jsonTextWriter); } } } } catch (Exception exception) { throw new Exception(string.Format(Localization.ProtectedJsonConfigurationWriteEncryptedValues, this.protectedSource.Path), exception); } } /// <summary>Represents a provider that protects a JSON configuration file</summary> /// <param name="source">Settings of the source</param> /// <see cref="ArgumentNullException"/> public ProtectedJsonConfigurationProvider(ProtectedJsonConfigurationSource source) : base(source) { this.protectedSource = source as ProtectedJsonConfigurationSource; } /// <summary>Loads the JSON data from the given <see cref="Stream"/></summary> /// <param name="stream"><see cref="Stream"/> to load</param> public override void Load(Stream stream) { //Call the base method first to ensure the data to be available base.Load(stream); var expressions = protectedSource.EncryptedKeyExpressions; if (expressions != null) { //Dictionary that contains the keys (and their encrypted value) that must be written to the JSON file var encryptedKeyValuesToWrite = new Dictionary<string, string>(); //Iterate through the data in order to verify whether the keys that require to be encrypted, as indeed encrypted. //Copy the keys to a new string array in order to avoid a collection modified exception var keys = new string[this.Data.Keys.Count]; this.Data.Keys.CopyTo(keys, 0); foreach (var key in keys) { //Iterate through each expression in order to check whether the current key must be encrypted and is encrypted. //If not then encrypt the value and overwrite the key var value = this.Data[key]; if (!string.IsNullOrEmpty(value) && expressions.Any(e => e.IsMatch(key))) { this.encryptedKeys.Add(key); //Verify whether the value is encrypted if (!this.isEncrypted(value)) { var protectedValue = ProtectedData.Protect(Encoding.UTF8.GetBytes(value), protectedSource.Entropy, protectedSource.Scope); var protectedValueWithPrefix = new List<byte>(encryptedPrefixBytes); protectedValueWithPrefix.AddRange(protectedValue); //Convert the protected value to a base-64 string in order to mask the prefix (for cosmetic purposes) //and overwrite the key with the encrypted value var protectedBase64Value = Convert.ToBase64String(protectedValueWithPrefix.ToArray()); encryptedKeyValuesToWrite.Add(key, protectedBase64Value); this.Data[key] = protectedBase64Value; } } } //Write the encrypted key/values to the JSON configuration file this.writeValues(encryptedKeyValuesToWrite); } } /// <summary>Attempts to get the value of the given key</summary> /// <param name="key">Key to get</param> /// <param name="value">Value of the key</param> /// <returns>Returns true in case the key has been found</returns> public override bool TryGet(string key, out string value) { if (!base.TryGet(key, out value)) { return false; } else if (!this.encryptedKeys.Contains(key)) { return true; } //Key is encrypted and must therefore be decrypted in order to return. //Note that the decoded base-64 bytes contains the encrypted prefix which must be excluded when unprotection var protectedValueWithPrefix = Convert.FromBase64String(value); var protectedValue = new byte[protectedValueWithPrefix.Length - encryptedPrefixBytes.Length]; Buffer.BlockCopy(protectedValueWithPrefix, encryptedPrefixBytes.Length, protectedValue, 0, protectedValue.Length); var unprotectedValue = ProtectedData.Unprotect(protectedValue, this.protectedSource.Entropy, this.protectedSource.Scope); value = Encoding.UTF8.GetString(unprotectedValue); return true; } /// <summary>Provides extensions concerning <see cref="ProtectedJsonConfigurationProvider"/></summary> public static class ProtectedJsonConfigurationProviderExtensions { /// <summary>Adds a protected JSON file</summary> /// <param name="configurationBuilder"><see cref="IConfigurationBuilder"/> in which to apply the JSON file</param> /// <param name="path">Path to the JSON file</param> /// <param name="optional">Specifies whether the JSON file is optional</param> /// <param name="entropy">Byte array to increase protection</param> /// <returns>Returns the <see cref="IConfigurationBuilder"/></returns> /// <exception cref="ArgumentNullException"/> public static IConfigurationBuilder AddProtectedJsonFile(this IConfigurationBuilder configurationBuilder, string path, bool optional, byte[] entropy, params Regex[] encryptedKeyExpressions) { var source = new ProtectedJsonConfigurationSource(entropy) { Path = path, Optional = optional, EncryptedKeyExpressions = encryptedKeyExpressions }; return configurationBuilder.Add(source); } } Answer5 public static IServiceCollection ConfigureProtected<TOptions>(this IServiceCollection services, IConfigurationSection section) where TOptions: class, new() { return services.AddSingleton(provider => { var dataProtectionProvider = provider.GetRequiredService<IDataProtectionProvider>(); var protectedSection = new ProtectedConfigurationSection(dataProtectionProvider, section); var options = protectedSection.Get<TOptions>(); return Options.Create(options); }); } This method is correct Answer6 Just a few clarifications to help avoid problems. When you encrypt a value, it's using the section as 'Purpose' (https://docs.microsoft.com/en-us/aspnet/core/security/data-protection/consumer-apis/purpose-strings?view=aspnetcore-2.2) When you get a 'Payload not valid' or something similar, it's likely that the purpose you used to encrypt it, differs from the purpose use to decrypt it. So, let's say I have a first level section in my appsettings.json named 'SecureSettings' and within it a connection string: { "SecureSettings": { "ConnectionString":"MyClearTextConnectionString" } } To encrypt the value, I'd call: http://localhost/cryptography/encrypt?section=SecureSettings:ConnectionString&value=MyClearTextConnectionString You may not want to keep an Encrypt controller in the app itself btw. Origin:https://stackoverflow.com/questions/36062670/encrypted-configuration-in-asp-net-core 标签 asp.net-mvc asp.net-core asp.net-core-mvc .net-core Your name 主题 Comment About text formats RESTRICTED HTML * Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id> * Lines and paragraphs break automatically. * Web page addresses and email addresses turn into links automatically. 相关推荐 * CONFIGURE THE AUTHORIZATION SERVER ENDPOINT QUESTION QUESTION HOW DO WE USE A BEARER TOKEN WITH ASP.NET 5 USING A USERNAME AND PASSWORD FLOW? FOR OUR SCENARIO, WE WANT TO LET A USER REGISTER AND LOGIN USING AJAX CALLS WITHOUT NEEDING TO USE AN EXTERNAL LOGIN. TO DO THIS, WE NEED TO HAVE AN AUTHORIZATION SERVER ENDPOINT. IN THE PREVIOUS VERSIONS OF ASP.NET WE WOULD DO THE FOLLOWING AND THEN LOGIN AT THE OURDOMAIN.COM/TOKEN URL. // CONFIGURE THE APPLICATION FOR OAUTH BASED FLOW PUBLICCLIENTID = "SELF"; OAUTHOPTIONS = NEW OAUTHAUTHORIZATIONSERVEROPTIONS { TOKENENDPOINTPATH = NEW PATHSTRING("/TOKEN"), PROVIDER = NEW APPLICATIONOAUTHPROVIDER * TOKEN BASED AUTHENTICATION IN ASP.NET CORE (REFRESHED) QUESTION I'M WORKING WITH ASP.NET CORE APPLICATION. I'M TRYING TO IMPLEMENT TOKEN BASED AUTHENTICATION BUT CAN NOT FIGURE OUT HOW TO USE NEW SECURITY SYSTEM. MY SCENARIO: A CLIENT REQUESTS A TOKEN. MY SERVER SHOULD AUTHORIZE THE USER AND RETURN ACCESS_TOKEN WHICH WILL BE USED BY THE CLIENT IN FOLLOWING REQUESTS. HERE ARE TWO GREAT ARTICLES ABOUT IMPLEMENTING EXACTLY WHAT I NEED: TOKEN BASED AUTHENTICATION USING ASP.NET WEB API 2, OWIN, AND IDENTITY USING JSON WEB TOKENS THE PROBLEM IS - IT IS NOT OBVIOUS FOR ME HOW TO DO THE SAME THING IN ASP.NET CORE. MY QUESTION IS: HOW TO CONFIGURE ASP.NET * ASP.NET CORE DATA PROTECTION DATA PROTECTION WEB APPLICATIONS OFTEN NEED TO STORE SECURE AND SENSITIVE DATA. WINDOWS DESKTOP APPLICATIONS PROVIDE DPAPI, BUT THIS IS NOT SUITABLE FOR WEB APPLICATIONS. THE ASP.NET CORE DATA PROTECTION STACK PROVIDES A SIMPLE, EASY-TO-USE ENCRYPTION API THAT DEVELOPERS CAN USE TO PROTECT DATA, INCLUDING KEY MANAGEMENT AND ROTATION. THE ASP.NET CORE DATA PROTECTION STACK IS INTENDED TO BE USED AS A LONG-TERM REPLACEMENT FOR <MACHINEKEY> ELEMENTS IN ASP.NET 1.X-4.X . IT AIMS TO SOLVE MANY OF THE SHORTCOMINGS OF THE OLD ENCRYPTION STACK WHILE PROVIDING AN OUT-OF-THE-BOX SOLUTION FOR THE USE CASES THAT MOST * HOW TO GET HTTPCONTEXT.CURRENT IN ASP.NET CORE? [DUPLICATE] QUESTION THIS QUESTION ALREADY HAS ANSWERS HERE: ACCESS THE CURRENT HTTPCONTEXT IN ASP.NET CORE (7 ANSWERS) CLOSED 4 YEARS AGO. WE ARE CURRENTLY REWRITING/CONVERTING OUR ASP.NET WEBFORMS APPLICATION USING ASP.NET CORE. TRYING TO AVOID RE-ENGINEERING AS MUCH AS POSSIBLE. THERE IS A SECTION WHERE WE USE HTTPCONTEXT IN A CLASS LIBRARY TO CHECK THE CURRENT STATE. HOW CAN I ACCESS HTTPCONTEXT.CURRENT IN .NET CORE 1.0? VAR CURRENT = HTTPCONTEXT.CURRENT; IF (CURRENT == NULL) { // DO SOMETHING HERE // STRING CONNECTION = CONFIGURATION.GETCONNECTIONSTRING("MYDB"); } I NEED TO ACCESS THIS IN ORDER TO * VIRTUAL DIRECTORY INSIDE OF ASP.NET CORE APP IN IIS QUESTION WE HAVE AN APPLICATION USING ASP.NET CORE 1.0 RC1 AND HOSTED ON IIS. IT WORKS FINE. NOW WE HAVE STATIC CONTENT, THAT IS AVAILABLE ON A FILE SHARE AND SHOULD BE ACCESSIBLE FROM THE APPLICATION. BEFORE ASP.NET 5, WE ADDED A VIRTUAL DIRECTORY IN IIS AND COULD ACCESS THE SHARED CONTENT EASILY. WITH OUR HOSTED ASP.NET 5 APPLICATION, THIS UNFORTUNATELY DOESN'T SEEM TO WORK. WE JUST GET A 404 BACK WHEN TRYING TO ACCESS THE STATIC CONTENT. OUR APPLICATION IS USING APP.USEIISPLATFORMHANDLER() AND APP.USESTATICFILES(), BUT THIS DOESN'T WORK. WE DISCOVERED THAT WE COULD USE APP.USEFILESERVER() * ACCESS THE CURRENT HTTPCONTEXT IN ASP.NET CORE QUESTION I NEED TO ACCESS CURRENT HTTPCONTEXT IN A STATIC METHOD OR A UTILITY SERVICE. WITH CLASSIC ASP.NET MVC AND SYSTEM.WEB, I WOULD JUST USE HTTPCONTEXT.CURRENT TO ACCESS THE CONTEXT STATICALLY. BUT HOW DO I DO THIS IN ASP.NET CORE? ANSWER1 HTTPCONTEXT.CURRENT DOESN'T EXIST ANYMORE IN ASP.NET CORE BUT THERE'S A NEW IHTTPCONTEXTACCESSOR THAT YOU CAN INJECT IN YOUR DEPENDENCIES AND USE TO RETRIEVE THE CURRENT HTTPCONTEXT: PUBLIC CLASS MYCOMPONENT : IMYCOMPONENT { PRIVATE READONLY IHTTPCONTEXTACCESSOR _CONTEXTACCESSOR; PUBLIC MYCOMPONENT(IHTTPCONTEXTACCESSOR CONTEXTACCESSOR) { * ASP.NET CORE WEBAPI LEARNING-3 ASP.NET CORE WEBAPI LEARNING-1 ASP.NET CORE WEBAPI LEARNING-2 ASP.NET CORE WEBAPI LEARNING-3 ASP.NET CORE WEBAPI LEARNING-4 ASP.NET CORE WEBAPI LEARNING-5 ASP.NET CORE WEBAPI LEARNING-6 USE OF CONTENT NEGOTIATION AUTOMAPPER CONTENT NEGOTIATION IS A PROCESS: FOR A RESPONSE, WHEN MULTIPLE PRESENTATION FORMATS ARE AVAILABLE, THE BEST ONE IS SELECTED.THE DATA FORMAT IS JSON, XML AND OTHER FORMATS MEDIA TYPE: -APPLICAION/JSON -APPLICATION/XML THE OUTPUT FORMAT IS DEFINED IN STARTUP.CS. WHEN THE REQUESTED FORMAT DOES NOT MATCH THE OUTPUT FORMAT, THE 406 NOT ACCEPTABLE STATUS CODE WILL BE RETURNED AS * INCREASE UPLOAD FILE SIZE IN ASP.NET CORE QUESTION CURRENTLY, I AM WORKING WITH ASP.NET CORE AND MVC6 NEED TO UPLOAD FILE SIZE UNLIMITED. I HAVE SEARCHED ITS SOLUTION BUT STILL NOT GETTING THE ACTUAL ANSWER. I HAVE TRIED THIS LINK IF ANYONE HAVE ANY IDEA PLEASE HELP. THANKS. ANSWER1 THE OTHER ANSWERS SOLVE THE IIS RESTRICTION. HOWEVER, AS OF ASP.NET CORE 2.0, KESTREL SERVER ALSO IMPOSES ITS OWN DEFAULT LIMITS. GITHUB OF KESTRELSERVERLIMITS.CS ANNOUNCEMENT OF REQUEST BODY SIZE LIMIT AND SOLUTION (QUOTED BELOW) MVC INSTRUCTIONS IF YOU WANT TO CHANGE THE MAX REQUEST BODY SIZE LIMIT FOR A SPECIFIC MVC ACTION OR CONTROLLER, YOU CAN USE THE * HOW TO RESOLVE INSTANCE INSIDE CONFIGURESERVICES IN ASP.NET CORE QUESTION IS IT POSSIBLE TO RESOLVE AN INSTANCE OF IOPTIONS<APPSETTINGS> FROM THE CONFIGURESERVICES METHOD IN STARTUP? NORMALLY YOU CAN USE ISERVICEPROVIDER TO INITIALIZE INSTANCES BUT YOU DON'T HAVE IT AT THIS STAGE WHEN YOU ARE REGISTERING SERVICES. PUBLIC VOID CONFIGURESERVICES(ISERVICECOLLECTION SERVICES) { SERVICES.CONFIGURE<APPSETTINGS>( CONFIGURATION.GETCONFIGURATIONSECTION(NAMEOF(APPSETTINGS))); // HOW CAN I RESOLVE IOPTIONS<APPSETTINGS> HERE? } ANSWER1 YOU CAN BUILD A SERVICE PROVIDER USING THE BUILDSERVICEPROVIDER() METHOD ON THE ISERVICECOLLECTION: PUBLIC VOID CONFIGURESERVICE * KESTREL SHUTDOWN FUNCTION IN STARTUP.CS IN ASP.NET CORE QUESTION IS THERE A SHUTDOWN FUNCTION WHEN USING MICROSOFT.ASPNET.SERVER.KESTREL? ASP.NET CORE (FORMERLY ASP.NET VNEXT) CLEARLY HAS A STARTUP SEQUENCE, BUT NO MENTION OF SHUTDOWN SEQUENCE AND HOW TO HANDLE CLEAN CLOSURE. ANSWER1 IN ASP.NET CORE YOU CAN REGISTER TO THE CANCELLATION TOKENS PROVIDED BY IAPPLICATIONLIFETIME PUBLIC CLASS STARTUP { PUBLIC VOID CONFIGURE(IAPPLICATIONBUILDER APP, IAPPLICATIONLIFETIME APPLICATIONLIFETIME) { APPLICATIONLIFETIME.APPLICATIONSTOPPING.REGISTER(ONSHUTDOWN); } PRIVATE VOID ONSHUTDOWN() { // DO YOUR CLEANUP HERE } } IAPPLICATIONLIFETIME IS ALSO EXPOSING * WHERE DID IMVCBUILDER ADDJSONOPTIONS GO IN .NET CORE 3.0? QUESTION I'VE JUST UPGRADED MY ASP WEB API PROJECT FROM .NET CORE 2.0 TO 3.0. I WAS USING SERVICES.ADDMVC() .ADDJSONOPTIONS(OPTIONS =>OPTIONS.SERIALIZERSETTINGS.CONTRACTRESOLVER = NEW DEFAULTCONTRACTRESOLVER()); PREVIOUSLY TO ENSURE LOWER-CASING OF THE SERIALIZED JSON. AFTER THE UPGRADE TO 3.0 I GET THIS ERROR: ERROR CS1061 'IMVCBUILDER' DOES NOT CONTAIN A DEFINITION FOR 'ADDJSONOPTIONS' AND NO ACCESSIBLE EXTENSION METHOD 'ADDJSONOPTIONS' ACCEPTING A FIRST ARGUMENT OF TYPE 'IMVCBUILDER' COULD BE FOUND (ARE YOU MISSING A USING DIRECTIVE OR AN ASSEMBLY REFERENCE?) ACCORDING TO ADDJSONOPTIONS FOR * DO I NEED A GLOBAL.ASAX.CS FILE AT ALL IF I'M USING AN OWIN STARTUP.CS CLASS AND MOVE ALL CONFIGURATION THERE? QUESTION LET'S SAY FOR EXAMPLE IN A BRAND NEW ASP.NET MVC 5 APPLICATION MADE FROM THE MVC WITH INDIVIDUAL ACCOUNTS TEMPLATE, IF I DELETE THE GLOBAL.ASAX.CS CLASS AND MOVE IT'S CONFIGURATION CODE TO STARTUP.CS CONFIGURATION() METHOD AS FOLLOW, WHAT ARE THE DOWNSIDES? PUBLIC PARTIAL CLASS STARTUP { PUBLIC VOID CONFIGURATION(IAPPBUILDER APP) { AREAREGISTRATION.REGISTERALLAREAS(); FILTERCONFIG.REGISTERGLOBALFILTERS(GLOBALFILTERS.FILTERS); ROUTECONFIG.REGISTERROUTES(ROUTETABLE.ROUTES); BUNDLECONFIG.REGISTERBUNDLES(BUNDLETABLE.BUNDLES); CONFIGUREAUTH(APP); } } THE UPSIDES FOR ME IS THAT WHEN * RESOLVING INSTANCES WITH ASP.NET CORE DI FROM WITHIN CONFIGURESERVICES QUESTION HOW DO I MANUALLY RESOLVE A TYPE USING THE ASP.NET CORE MVC BUILT-IN DEPENDENCY INJECTION FRAMEWORK? SETTING UP THE CONTAINER IS EASY ENOUGH: PUBLIC VOID CONFIGURESERVICES(ISERVICECOLLECTION SERVICES) { // ... SERVICES.ADDTRANSIENT<ISOMESERVICE, SOMECONCRETESERVICE>(); } BUT HOW CAN I RESOLVE ISOMESERVICE WITHOUT PERFORMING INJECTION? FOR EXAMPLE, I WANT TO DO THIS: ISOMESERVICE SERVICE = SERVICES.RESOLVE<ISOMESERVICE>(); THERE ARE NO SUCH METHODS IN ISERVICECOLLECTION. ANSWER1 THE ISERVICECOLLECTION INTERFACE IS USED FOR BUILDING A DEPENDENCY INJECTION CONTAINER. AFTER IT'S FULLY * HOW TO ENABLE CORS IN ASP.NET CORE WEBAPI QUESTION WHAT I AM TRYING TO DO I HAVE A BACKEND ASP.NET CORE WEB API HOSTED ON AN AZURE FREE PLAN (SOURCE CODE: HTTPS://GITHUB.COM/KILLERRIN/PORTFOLIO-BACKEND). I ALSO HAVE A CLIENT WEBSITE WHICH I WANT TO MAKE CONSUME THAT API. THE CLIENT APPLICATION WILL NOT BE HOSTED ON AZURE, BUT RATHER WILL BE HOSTED ON GITHUB PAGES OR ON ANOTHER WEB HOSTING SERVICE THAT I HAVE ACCESS TO. BECAUSE OF THIS THE DOMAIN NAMES WON'T LINE UP. LOOKING INTO THIS, I NEED TO ENABLE CORS ON THE WEB API SIDE, HOWEVER I HAVE TRIED JUST ABOUT EVERYTHING FOR SEVERAL HOURS NOW AND IT IS REFUSING TO WORK. HOW I HAVE THE * HOW TO SET UP AUTOMAPPER IN ASP.NET CORE QUESTION I'M RELATIVELY NEW AT .NET, AND I DECIDED TO TACKLE .NET CORE INSTEAD OF LEARNING THE "OLD WAYS". I FOUND A DETAILED ARTICLE ABOUT SETTING UP AUTOMAPPER FOR .NET CORE HERE, BUT IS THERE A MORE SIMPLE WALKTHROUGH FOR A NEWBIE? ANSWER1 I FIGURED IT OUT! HERE'S THE DETAILS: ADD THE MAIN AUTOMAPPER PACKAGE TO YOUR SOLUTION VIA NUGET. ADD THE AUTOMAPPER DEPENDENCY INJECTION PACKAGE TO YOUR SOLUTION VIA NUGET. CREATE A NEW CLASS FOR A MAPPING PROFILE. (I MADE A CLASS IN THE MAIN SOLUTION DIRECTORY CALLED MAPPINGPROFILE.CS AND ADD THE FOLLOWING CODE.) I'LL USE A USER AND USERDTO OBJECT AS AN * ASP.NET CORE: A MULTI-TIER DATA SERVICE APPLICATION MIGRATED FROM ASP.NET WEB API TABLE OF CONTENTS INTRODUCTION SET UP AND RUN THE SAMPLE APPLICATION CLASS LIBRARY PROJECT DEPENDENCY INJECTION ACCESS APPLICATION SETTINGS ENTITY FRAMEWORK CORE RELATED CHANGES PRIMARY KEY ID INSERTION PROBLEM DATA CONTEXT AND CONNECTION STRING CUSTOM REPOSITORIES (REPOSITORIES) LINQ EXPRESSION REFURBISHMENT (ONLY FOR EF CORE 3.0) EXECUTE STORED PROCEDURE CUSTOM MODEL BINDER USE IIS EXPRESS AND LOCAL IIS SUMMARY DOWNLOAD ASPNETCORE3.0_DATASERVICES-75.8 KB DOWNLOAD ASPNETCORE2.0-2.2_DATASERVICES-211.1 KB DOWNLOAD WEBAPI_DATASERVICES-309.1 KB INTRODUCTION RESTFUL DATA SERVICE API HAS BEEN THE * ASP.NET CORE BASIC SERIES (6) (MIDDLEWARE) 1. WHAT IS MIDDLEWARE ASP.NET CORE MIDDLEWARE COMPONENTS ARE SOFTWARE COMPONENTS THAT ARE ASSEMBLED INTO THE APPLICATION PIPELINE TO PROCESS HTTP REQUESTS AND RESPONSES. EACH MIDDLEWARE COMPONENT PERFORMS THE FOLLOWING TASKS: CHOOSE WHETHER TO PASS THE HTTP REQUEST TO THE NEXT COMPONENT IN THE PIPELINE. THIS CAN BE ACHIEVED BY CALLING THE NEXT() METHOD IN THE MIDDLEWARE. WORK CAN BE PERFORMED BEFORE AND AFTER THE NEXT COMPONENT IN THE PIPELINE. IN ASP.NET CORE, THERE ARE ALREADY MANY BUILT-IN MIDDLEWARE COMPONENTS AVAILABLE FOR USE, SEE THE OFFICIAL WEBSITE FOR DETAILS: HTTPS://DOCS.MICROSOFT * DISABLE APPLICATION INSIGHTS IN DEBUG QUESTION HOW CAN I DISABLE APPLICATION INSIGHTS AUTOMATICALLY WHEN USING A DEBUG CONFIGURATION AND ENABLE IT ONLY ON RELEASE? IS IT POSSIBLE TO DO THIS WITHOUT CREATING ANOTHER INSTRUMENTATION KEY ONLY FOR DEBUG? I HAVE TRACKEVENT STATEMENTS SCATTERED ALL OVER THE CODE, ENCLOSING THEM INSIDE A DEBUG PREPROCESSOR CHECK IS NOT AN IDEAL SOLUTION. MY CURRENT SOLUTION IS TO SET THE BUILD ACTION OF THE APPLICATIONINSIGHTS.CONFIG FILE TO NONE SO THAT IT'S NOT COPIED TO THE PROJECT'S OUTPUT DIRECTORY, BUT THIS ISN'T A PROCESS THAT CAN BE AUTOMATED BASED ON THE ACTIVE BUILD CONFIGURATION. THERE IS A * WHAT IS THE DIFFERENCE BETWEEN NUOHE ZHIYUAN, ASP.NET CORE MIDDLEWARE AND HTTPMODULE WHAT IS THE DIFFERENCE BETWEEN ASP.NET CORE MIDDLEWARE AND HTTPMODULE PREFACE ONE OF THE BIGGEST CHANGES IN ASP.NET CORE IS THE CHANGE TO THE HTTP REQUEST PIPELINE. IN ASP.NET WE UNDERSTAND HTTPHANDLER AND HTTPMODULE, BUT UNTIL NOW THESE HAVE BEEN EXCHANGED AS INTERMEDIATES. SO LET’S TAKE A LOOK AT THEIR DIFFERENCES BELOW. HTTPHANDLER HANDLERS DEAL WITH SPECIFIC REQUESTS BASED ON EXTENSIONS, AND HTTPHANDLERS STOP FUNCTIONING AS THEY RESPOND TO REQUESTS FOR ASP.NET. HE IS A CLASS THAT COMPLETES THE SYSTEM.WEB.IHTTPHANDLER INTERFACE. ANY CLASS THAT COMPLETES THE IHTTPHANDLER INTERFACE CAN BE * ASP.NET CORE SERVICE INTEGRATION IDENTITYSERVER4 CERTIFICATION ARTICLE DIRECTORYPREFACESTEPS FOR USAGE 1. CREATE A WEBAPI PROJECT AND NAME IT ORDERCONSUMER 2. INTRODUCE IDENTITYSERVER4.ACCESSTOKENVALIDATION LIBRARY VERSION 3.0.1 3. CONTROLLER MODIFICATION 4. STARTUP CLASS MODIFICATION 5. TEST BY POSTMAN PREFACE THE LAST ARTICLE SUCCESSFULLY CREATED THE IDENTITYSERVER4 RESOURCE SERVICE (ASP.NET CORE INTEGRATED IDENTITYSERVER4), AND INTEGRATED TWO MODES (CLIENT MODE AND PASSWORD MODE). THIS ARTICLE TALKS ABOUT THE CLIENT REQUESTING RESOURCES FOR AUTHENTICATION. CODE FRAMEWORK ENVIRONMENT ASP.NET CORE 3.1 TIP: THE FOLLOWING IS THE CONTENT OF THIS ARTICLE 更多相关文章... RECENT CONTENT * Launching WinRT app from PowerShell 1 minute ago * Can't access port 8080 on EC2 (AWS) 2 minutes ago * is it good to store decimal value as varchar in mysql? 2 minutes 40 seconds ago * Issue with converting string to int 2 minutes 45 seconds ago * c# equivalent of "java.security.spec.RSAPublicKeySpec" and "java.security.PublicKey" 2 minutes 49 seconds ago RECENT COMMENTS * The considerations for savin 1 day ago * First, I've written about th 1 day ago * 'Error calling Driver#connec 1 day ago * You'll need to bound the dro 1 day 18 hours ago * It looks like you can't. I j 1 day 23 hours ago 热门标签 drawerlayout记录成长零信任安全IP合并facebook-pixelhive 本机测试使用local模式可以gmaven-plugintrusted-vs-untrustedlearning-lockerdropbox-sdk-jsNew-ADUser思科基础命令base-class-libraryhttpruntime.cacheDocker软件 联系我们 免责申明 隐私条款 Back to top