HTTP Digest authentication

article/2025/11/8 15:50:40

什么是摘要认证

摘要认证( Digest authentication)是一个简单的认证机制,最初是为HTTP协议开发的,因而也常叫做HTTP摘要,在RFC2617中描述。其身份验证机制很简单,它采用杂凑式(hash)加密方法,以避免用明文传输用户的口令。

摘要认证就是要核实,参与通信的双方,都知道双方共享的一个秘密(即口令)。

摘要认证流程

图一

  • 服务器核实用户身份

    server收到client的HTTP request(INVITE),如果server需要客户端摘要认证,就需要生成一个摘要盘问(digest challenge),通过Response给client一个401 Unauthorized状态发送给用户。

    摘要盘问如 图二 中的WWW-Authenticate header所示:

    图二

    摘要盘问中的各个参数意义如下:

  • realm(领域):必须的,在所有的盘问中都必须有。它是目的是鉴别SIP消息中的机密。在实际应用中,它通常设置为server所负责的域名。

  • nonce (现时):必须的,这是由服务器规定的数据字符串,在服务器每次产生一个摘要盘问时,这个参数都是不一样的(与前面所产生的不会雷同)。nonce 通常是由一些数据通过md5杂凑运算构造的。这样的数据通常包括时间标识和服务器的机密短语。确保每个nonce 都有一个有限的生命期(也就是过了一些时间后会失效,并且以后再也不会使用),而且是独一无二的(即任何其它的服务器都不能产生一个相同的nonce )。

  • Stale:不必须,一个标志,用来指示客户端先前的请求因其nonce值过期而被拒绝。如果stale是TRUE(大小写敏感),客户端可能希望用新的加密回应重新进行请求,而不用麻烦用户提供新的用户名和口令。服务器端只有在收到的请求nonce值不合法,而该nonce对应的摘要(digest)是合法的情况下(即客户端知道正确的用户名/口令),才能将stale置成TRUE值。如果stale是FALSE或其它非TRUE值,或者其stale域不存在,说明用户名、口令非法,要求输入新的值。

  • opaque(不透明体):必须的,这是一个不透明的(不让外人知道其意义)数据字符串,在盘问中发送给用户。

  • algorithm(算法):不必须,这是用来计算杂凑的算法。当前只支持MD5算法。

  • qop(保护的质量):必须的,这个参数规定服务器支持哪种保护方案。客户端可以从列表中选择一个。值 “auth”表示只进行身份查验, “auth-int”表示进行查验外,还有一些完整性保护。需要看更详细的描述,请参阅RFC2617。

    1. 客户端反馈用户身份

    client 生成 生成摘要响应(digest response),然后再次通过 http request (INVITE (Withink digest))发给 server。

    摘要响应如 图三 中的Authenticate header所示:

    图三

    摘要响应中的各个参数意义如下:

  • username: 不用再说明了
  • realm: 需要和 server 盘问的realm保持一致
  • nonce:客户端使用这个“现时”来产生摘要响应(digest response),需要和server 盘问中携带的nonce保持一致,这样服务器也会在一个摘要响应中收到“现时”的内容。服务器先要检查了“现时”的有效性后,才会检查摘要响应的其它部分。

    因而,nonce 在本质上是一种标识符,确保收到的摘要机密,是从某个特定的摘要盘问产生的。还限制了摘要盘问的生命期,防止未来的重播攻击。

  • qop:客户端选择的保护方式。

  • nc (现时计数器):这是一个16进制的数值,即客户端发送出请求的数量(包括当前这个请求),这些请求都使用了当前请求中这个“现时”值。例如,对一个给定的“现时”值,在响应的第一个请求中,客户端将发送“nc=00000001”。这个指示值的目的,是让服务器保持这个计数器的一个副本,以便检测重复的请求。如果这个相同的值看到了两次,则这个请求是重复的。

  • response:这是由用户代理软件计算出的一个字符串,以证明用户知道口令。比如可以通过 username、password、http method、uri、以及nonce、qop等使用MD5加密生成。

  • cnonce:这也是一个不透明的字符串值,由客户端提供,并且客户端和服务器都会使用,以避免用明文文本。这使得双方都可以查验对方的身份,并对消息的完整性提供一些保护。

  • uri:这个参数包含了客户端想要访问的URI。

    1. server 确认用户 
      确认用户主要由两部分构成:
  • 检查nonce的有效性
  • 检查摘要响应中的其他信息, 比如server可以按照和客户端同样的算法生成一个response值,和client传递的response进行对比。

代码实现

Server 端

<?php// 解析PHP_AUTH_DIGESTfunction http_digest_parse($txt){// 判断 Authorization数据是否完整$needed_parts = array('nonce' => 1,'nc' => 1,'cnonce' => 1,'qop' => 1,'username' => 1,'uri' => 1,'response' => 1);$data = array();//把 txt 解析成了二维数组,结构 array(array('key','"','value'),...);preg_match_all('@(\w+)=([\'"]?)([a-zA-Z0-9=./\_-]+)\2@', $txt, $matches, PREG_SET_ORDER);foreach ($matches as $m) {//$m[1]是key值,$m[3]是value值$data[$m[1]] = $m[3];//将needed_parts中对应的key-value释放掉unset($needed_parts[$m[1]]);}//判断needed_parts是否已经被完全释放,如果是,则Authorization数据完整且解析成功,否则,解析失败return $needed_parts ? false : $data;}public function digest_authorization(){$realm = 'Restricted area';// username => password$users = array('admin' => 'mypass','guest' => 'guest');//响应客户端 INVITE 的请求if (empty($_SERVER['PHP_AUTH_DIGEST'])) {header('HTTP/1.1 401 Unauthorized');header('WWW-Authenticate: Digest realm="' . $realm . '",qop="auth",nonce="' . uniqid() . '",opaque="' . md5($realm) . '"');die('Text to send if user hits Cancel button');}// 分析Authorization header数据,如果Authorization header数据未被成功解析,或者不能根据Authorization header中的username查询密码,响应凭证错误if (! ($data = http_digest_parse($_SERVER['PHP_AUTH_DIGEST'])) || ! isset($users[$data['username']])) {die('Wrong Credentials!');}// 生成 有效的response// A1 = md5(Authorization header中的username + 本地realm + 根据Authorization header中的username查询的密码);$A1 = md5($data['username'] . ':' . $realm . ':' . $users[$data['username']]);// A2 同 客户端生成的方式$A2 = md5($_SERVER['REQUEST_METHOD'] . ':' . $data['uri']);// valid_code 同 客户端生成的方式$valid_code = md5($A1 . ':' . $data['nonce'] . ':' . $data['nc'] . ':' . $data['cnonce'] . ':' . $data['qop'] . ':' . $A2);//对比请求中携带的response和服务器生成的valid_code,如果不一致则响应凭据错误if ($data['response'] != $valid_code) {die('Wrong Credentials!');}// 校验通过,则根据uri获取并返回数据 echo 'Your are logged in as: ' . $data['username'];}
?>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69

client 端 (Android)

  • LoginActivity.java
 UserLoginTask mAuthTask = new UserLoginTask(email, password);mAuthTask.execute((Void) null);
  • 1
  • 2
  • UserLoginTask.java
public class UserLoginTask extends AsyncTask<Void, Void, Boolean> implements HttpHelper.Callback{private final String mEmail;private final String mPassword;UserLoginTask(String email, String password) {mEmail = email;mPassword = password;}@Overrideprotected Boolean doInBackground(Void... params) {// TODO: attempt authentication against a network service.try {HttpHelper httpHelper = new HttpHelper(URL,mEmail,mPassword,getApplicationContext());httpHelper.setCallback(this);httpHelper.fetchHttpData();} catch (IOException e) {e.printStackTrace();}for (String credential : DUMMY_CREDENTIALS) {String[] pieces = credential.split(":");if (pieces[0].equals(mEmail)) {// Account exists, return true if the password matches.return pieces[1].equals(mPassword);}}// TODO: register the new account here.return true;}@Overrideprotected void onPostExecute(final Boolean success) {mAuthTask = null;showProgress(false);if (success) {finish();} else {mPasswordView.setError(getString(R.string.error_incorrect_password));mPasswordView.requestFocus();}}@Overrideprotected void onCancelled() {mAuthTask = null;showProgress(false);}@Overridepublic void onSuccess(String result) {Log.i(TAG,"result:" + result);}@Overridepublic void onUnauthorized(String result) {Log.i(TAG,"result:" + result);}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • HttpHelper.java

import android.content.Context;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;import com.google.common.base.CharMatcher;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.turbomanage.httpclient.BasicHttpClient;
import com.turbomanage.httpclient.ConsoleRequestLogger;
import com.turbomanage.httpclient.HttpMethod;
import com.turbomanage.httpclient.HttpResponse;
import com.turbomanage.httpclient.RequestLogger;import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;public class HttpHelper {private static final String TAG = "HttpHelper";private static final String BASIC = "Basic ";private static final String DIGEST = "Digest ";private static final String NONCE = "nonce";private static final String QOP = "qop";private static final String REALM = "realm";private static final String OPAQUE = "opaque";private static final String USERNAME = "username";private static final String NC = "nc";private static final String nc = "00000001";private static final String CNONCE = "cnonce";private static final String cnonce = "0a4f113b";private static final String RESPONSE = "response";private static final String URI = "uri";// URL of the remote serviceprivate String mUri = null;private String mRemoteService = null;private String mTimestamp = null;private String mUsername = null;private String mPassword = null;private Context mContext = null;Callback mCallback = null;static {// HTTP connection reuse which was buggy pre-froyoif (Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO) {System.setProperty("http.keepAlive", "false");} else {System.setProperty("http.keepAlive", "true");}}private RequestLogger mQuietLogger = new ConsoleRequestLogger();public HttpHelper(String uri, Context context) {mUri = uri;mContext = context;mRemoteService = "Server IP" + mUri;}public HttpHelper(String url, String timestamp, Context context) {this(url, context);mTimestamp = timestamp;}public HttpHelper(String url, String username, String password, Context context) {this(url, context);mUsername = username;mPassword = password;}public void setCallback(Callback callback) {mCallback = callback;}private void fetchHttpResponse(HttpResponse httpResponse) throws IOException {int status = httpResponse.getStatus();if (status == HttpURLConnection.HTTP_OK) {String str = httpResponse.getBodyAsString();mCallback.onSuccess(str);} else if (status == HttpURLConnection.HTTP_UNAUTHORIZED) {//服务器获得401响应,并解析WWW-Authenticate header的类型String str = httpResponse.getBodyAsString();mCallback.onUnauthorized(str);Map<String, List<String>> headers = httpResponse.getHeaders();List<String> wwwAuthenticate = headers.get("WWW-Authenticate");String auth = wwwAuthenticate.get(0);if (auth == null) {}// Digestif (auth.startsWith(DIGEST.trim())) {//这里实现Digest Auth逻辑HashMap<String, String> authFields = splitAuthFields(auth.substring(7));Joiner colonJoiner = Joiner.on(':');String A1 = null; //A1 = MD5("usarname:realm:password");String A2 = null; //A2 = MD5("httpmethod:uri");String response = null; //response = MD5("A1:nonce:nc:cnonce:qop:A2");MessageDigest md5 = null;try {md5 = MessageDigest.getInstance("MD5");} catch (NoSuchAlgorithmException e) {e.printStackTrace();}try {md5.reset();String A1Str = colonJoiner.join(mUsername, authFields.get(REALM), mPassword);md5.update(A1Str.getBytes("ISO-8859-1"));A1 = bytesToHexString(md5.digest());} catch (UnsupportedEncodingException e) {}try {md5.reset();String A2Str = colonJoiner.join(HttpMethod.GET.toString(), mUri);md5.update(A2Str.getBytes("ISO-8859-1"));A2 = bytesToHexString(md5.digest());} catch (UnsupportedEncodingException e) {}try {md5.reset();String A2Str = colonJoiner.join(A1, authFields.get(NONCE), nc, cnonce, authFields.get(QOP), A2);md5.update(A2Str.getBytes("ISO-8859-1"));response = bytesToHexString(md5.digest());} catch (UnsupportedEncodingException e) {}// 拼接 Authorization Header,格式如 Digest username="admin",realm="Restricted area",nonce="554a3304805fe",qop=auth,opaque="cdce8a5c95a1427d74df7acbf41c9ce0", nc=00000001,response="391bee80324349ea1be02552608c0b10",cnonce="0a4f113b",uri="/MyBlog/home/Response/response_last_modified"StringBuilder sb = new StringBuilder();sb.append(DIGEST);sb.append(USERNAME).append("=\"").append(mUsername).append("\",");sb.append(REALM).append("=\"").append(authFields.get(REALM)).append("\",");sb.append(NONCE).append("=\"").append(authFields.get(NONCE)).append("\",");sb.append(QOP).append("=").append(authFields.get(QOP)).append(",");sb.append(OPAQUE).append("=\"").append(authFields.get(OPAQUE)).append("\",");sb.append(NC).append("=").append(nc).append(",");sb.append(RESPONSE).append("=\"").append(response).append("\",");sb.append(CNONCE).append("=\"").append(cnonce).append("\",");sb.append(URI).append("=\"").append(mUri).append("\"");//再请求一次BasicHttpClient httpClient = new BasicHttpClient();httpClient.setRequestLogger(mQuietLogger);if (mTimestamp != null && !TextUtils.isEmpty(mTimestamp)) {if (TimeUtils.isValidFormatForIfModifiedSinceHeader(mTimestamp)) {httpClient.addHeader("If-Modified-Since", mTimestamp);} else {Log.w(TAG, "Could not set If-Modified-Since HTTP header. Potentially downloading " +"unnecessary data. Invalid format of refTimestamp argument: " + mTimestamp);}}httpClient.addHeader("Authorization", sb.toString());fetchHttpResponse(httpClient.get(mRemoteService, null));} else if (auth.startsWith(BASIC.trim())) { // Basic//这里实现Basic Auth逻辑}} else if (status == HttpURLConnection.HTTP_NOT_MODIFIED) {Log.d(TAG, "HTTP_NOT_MODIFIED: data has not changed since " + mTimestamp);} else {Log.e(TAG, "Error fetching conference data: HTTP status " + status);throw new IOException("Error fetching conference data: HTTP status " + status);}}public void fetchHttpData() throws IOException {if (TextUtils.isEmpty(mUri)) {Log.w(TAG, "Manifest URL is empty.");}BasicHttpClient httpClient = new BasicHttpClient();httpClient.setRequestLogger(mQuietLogger);if (mTimestamp != null && !TextUtils.isEmpty(mTimestamp)) {if (TimeUtils.isValidFormatForIfModifiedSinceHeader(mTimestamp)) {httpClient.addHeader("If-Modified-Since", mTimestamp);} else {Log.w(TAG, "Could not set If-Modified-Since HTTP header. Potentially downloading " +"unnecessary data. Invalid format of refTimestamp argument: " + mTimestamp);}}HttpResponse response = httpClient.get(mRemoteService, null);fetchHttpResponse(response);}private static HashMap<String, String> splitAuthFields(String authString) {final HashMap<String, String> fields = Maps.newHashMap();final CharMatcher trimmer = CharMatcher.anyOf("\"\t ");final Splitter commas = Splitter.on(',').trimResults().omitEmptyStrings();final Splitter equals = Splitter.on('=').trimResults(trimmer).limit(2);String[] valuePair;for (String keyPair : commas.split(authString)) {valuePair = Iterables.toArray(equals.split(keyPair), String.class);fields.put(valuePair[0], valuePair[1]);}return fields;}private static final String HEX_LOOKUP = "0123456789abcdef";private static String bytesToHexString(byte[] bytes) {StringBuilder sb = new StringBuilder();for (int i = 0; i < bytes.length; i++) {sb.append(HEX_LOOKUP.charAt((bytes[i] & 0xF0) >> 4));sb.append(HEX_LOOKUP.charAt((bytes[i] & 0x0F) >> 0));}return sb.toString();}public static interface Callback {void onSuccess(String result);void onUnauthorized(String result);}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232

HTTP Digest Auth的学习过程实在奇怪,首先google/baidu了一大堆文章,但是前方出现大量的艰深难懂的术语,轻易的让我brain fart。无奈先参考PHP官方文档coding了吧。coding一遍之后,那些词汇的意义仿佛清晰了很多。感谢BabyUnion的总结:http://blog.163.com/hlz_2599/blog/static/1423784742013415101252410/


http://chatgpt.dhexx.cn/article/Sm23wbkj.shtml

相关文章

配置Apache Digest认证

Apache常见的用户认证可以分为下面三种&#xff1a; 基于IP&#xff0c;子网的访问控制(ACL)基本用户验证(Basic Authentication)消息摘要式身份验证(Digest Authentication) 消息摘要式身份验证(Digest Authentication) Digest Authentication在基本身份验证上面扩展了安全…

http Digest认证计算方法整理

摘要认证及实现HTTP digest authentication - 简书 HTTP Basic和Digest认证介绍与计算 - 诸子流 - 博客园 不要不知道上面说的URI是什么意思啊 图解HTTP 第 8 章 确认访问用户身份的认证 - 简书8.1 何为认证 为了弄清究竟是谁在访问服务器&#xff0c;就得让对方的客户端自报家…

业务维度digest日志的记录与监控方案

需求 ​   为了满足从业务整体的维度 实现监控和链路复原&#xff0c;我们希望对于一个业务接口&#xff0c;记录一行请求日志&#xff0c;并通过某个 Unique Id&#xff08;如UserId、OrderId&#xff09;将多行日志关联起来&#xff0c;最终产出一批和业务强相关的数据&am…

java发起Digest Auth请求

常规认证方式 上代码&#xff1a; 需要的Maven <dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>4.5</version></dependency><dependency><groupId>org.…

认证学习3 - Digest摘要认证讲解、代码实现、演示

文章目录 Digest摘要认证 - 密文讲解&#xff08;Digest摘要认证&#xff09;实现&#xff08;Digest认证&#xff09;代码&#xff08;Digest认证&#xff09;代码&#xff08;Digest认证-客户端&#xff09;演示&#xff08;Digest认证-postman&#xff09; 认证大全&#xf…

HTTP的认证方式之DIGEST 认证(摘要认证)

核心步骤&#xff1a; 步骤 1&#xff1a; 请求需认证的资源时&#xff0c;服务器会随着状态码 401Authorization Required&#xff0c;返回带WWW-Authenticate 首部字段的响应。该字段内包含质问响应方式认证所需的临时质询码&#xff08;随机数&#xff0c;nonce&#xff09;…

Digest Auth 摘要认证

Digest Auth 摘要认证 1.非常规方式 转载&#xff1a;https://blog.csdn.net/qq_25391785/article/details/86595529 public static void postMethod(String url, String query) {try {CredentialsProvider credsProvider new BasicCredentialsProvider();credsProvider.setC…

digest鉴权

“摘要”式认证&#xff08; Digest authentication&#xff09;是一个简单的认证机制&#xff0c;最初是为HTTP协议开发的&#xff0c;因而也常叫做HTTP摘要&#xff0c;在RFC2671中描述。其身份验证机制很简单&#xff0c;它采用杂凑式&#xff08;hash&#xff09;加密方法&…

消息摘要(Digest),数字签名(Signature),数字证书(Certificate)是什么?

1. 消息摘要&#xff08;Digest&#xff09; 1. 什么是消息摘要&#xff1f; 对一份数据&#xff0c;进行一个单向的 Hash 函数&#xff0c;生成一个固定长度的 Hash 值&#xff0c;这个值就是这份数据的摘要&#xff0c;也称为指纹。 2. 摘要算法 常见的摘要算法有 MD5、SHA…

HTTP通讯安全中的Digest摘要认证释义与实现

摘要 出于安全考虑&#xff0c;HTTP规范定义了几种认证方式以对访问者身份进行鉴权&#xff0c;最常见的认证方式之一是Digest认证 Digest认证简介 HTTP通讯采用人类可阅读的文本格式进行数据通讯&#xff0c;其内容非常容易被解读。出于安全考虑&#xff0c;HTTP规范定义了几…

http协议之digest(摘要)认证,详细讲解并附Java SpringBoot源码

目录 1.digest认证是什么&#xff1f; 2.digest认证过程 3.digest认证参数详解 4.基于SpringBoot实现digest认证 5.digest认证演示 6.digest认证完整项目 7.参考博客 1.digest认证是什么&#xff1f; HTTP通讯采用人类可阅读的文本格式进行数据通讯&#xff0c;其内容非…

【WinRAR】WinRAR 6.01 官方最新简体中文版

WinRAR 6.01 官方简体中文商业版下载地址&#xff08;需要注册&#xff09;&#xff1a; 64位&#xff1a; https://www.win-rar.com/fileadmin/winrar-versions/sc/sc20210414/wrr/winrar-x64-601sc.exe https://www.win-rar.com/fileadmin/winrar-versions/sc/sc20210414/…

WinRAR命令行

基本使用 实践 将文件夹压缩到zip包 输入&#xff1a;文件夹如下&#xff0c;文件夹为class。 输出&#xff1a;classes.zip 指令如下&#xff1a; rar a classes.zip .\classes或者 WinRAR a classes.zip .\classes结果如下&#xff1a; PS C:\Users\liyd\Desktop\kuai…

WinRAR安装教程

文章目录 WinRAR安装教程无广告1. 下载2. 安装3. 注册4. 去广告 WinRAR安装教程无广告 1. 下载 国内官网&#xff1a;https://www.winrar.com.cn/ 2. 安装 双击&#xff0c;使用默认路径&#xff1a; 点击“安装”。 点击“确定”。 点击“完成”。 3. 注册 链接&#x…

WinRAR注册+去广告教程

1、注册 在WinRAR安装目录创建rarreg.key文件&#xff0c; 拷贝如下内容并保存&#xff1a; RAR registration data Federal Agency for Education 1000000 PC usage license UIDb621cca9a84bc5deffbf 6412612250ffbf533df6db2dfe8ccc3aae5362c06d54762105357d 5e3b1489e751c…

WinRAR4.20注册文件rarreg.key

2019独角兽企业重金招聘Python工程师标准>>> 在WinRAR的安装目录下&#xff0c;新建rarreg.key文件&#xff08;注意不要创建成rarreg.key.txt文件了^_^&#xff09;&#xff0c;内容为如下&#xff1a; RAR registration data Team EAT Single PC usage license UI…

Android按钮样式

//创建一个新的XML文件&#xff0c;可命名为styles<style name"button1"><item name"android:layout_height">wrap_content</item><item name"android:textColor">#FFFFFF</item><item name"android:text…

漂亮的Button按钮样式

开发中各种样式的Button,其实这些样式所有的View都可以共用的,可能对于你改变的只有颜色 所有的都是用代码实现 边框样式,给你的View加上边框 <Buttonandroid:layout_width="0dip"android:layout_height="match_parent"android:layout_margin=&q…

「HTML+CSS」--自定义按钮样式【001】

前言 Hello&#xff01;小伙伴&#xff01; 首先非常感谢您阅读海轰的文章&#xff0c;倘若文中有错误的地方&#xff0c;欢迎您指出&#xff5e; 哈哈 自我介绍一下 昵称&#xff1a;海轰 标签&#xff1a;程序猿一只&#xff5c;C选手&#xff5c;学生 简介&#xff1a;因C语…

HTML_炫酷的按钮样式

html部分 <a href"#"><span></span><span></span><span></span><span></span>Neon button</a><a href"#"><span></span><span></span><span></span…