由线程安全引起的一个有关Realm问题

本文探讨了在Tomcat的Realm实现中如何安全地使用HttpServletRequest对象。由于Realm实例是单例模式,直接保存Request可能导致多线程安全问题。文章提出了一些解决方案并寻求更优的方法。
我的项目里需要给Tomcat写一个 Realm,大家都知道Realm接口里简化如下:

public interface Realm {
public Principal authenticate(String username, String credentials);
...
public SecurityConstraint [] findSecurityConstraints(Request request, Context context);
...
}

为了节省篇幅,把Realm接口简化了一下。 由于项目需要必须要在authenticate方法里使用request, 而通过摸索在Realm里只有findSecurityConstraints方法里能得到这个request. 于是我就写了Realm如下:

public class MyRealm extends JAASRealm {
protected HttpServletRequest request;
public Principal authenticate(String username, String credentials){
//在这里使用request。
}
...
public SecurityConstraint [] findSecurityConstraints(Request request, Context context){
this.request = request;
return super.findSecurityConstraints(request, context);
}
}


这样测试"良好", 似乎是可行的。 功能也都能过。
但是高手一看就会发现问题,因为request对象是变化的,并且Realm在整个Tomcat里只有一个实例(至少在只配置一个Realm的时候是这样的,配置多个Realm我还没有试)。这样就导致了request方法对多线程不安全,甚至混乱。
请高手帮着看一下:
[b]
1. 是不是有其他更好的办法在Realm访问request对象?
2. 如果第一个问题是否定的,看有没有办法使这个request安全的被访问?[/b] (我知道用ThreadLocal来解决,还有没有其他办法)
请大家帮忙了。谢谢。
#### 步骤一:修改CONTEXT结构体(httpd.h)** 在CONTEXT结构体中增加Digest认证相关的字段: ```c #define LEN_USERNAME 256 #define LEN_REALM 256 #define LEN_DIGEST_NONCE 256 #define LEN_URI 256 #define LEN_RESPONSE 256 #define LEN_ALGORITHM 64 #define LEN_QOP 64 #define LEN_NC 32 #define LEN_CNONCE 64 #define LEN_OPAQUE 256 typedef struct _CONTEXT { /* 现有字段... */ /* 新增Digest认证相关字段 */ BOOL digest_authenticated; /* Digest认证是否通过 */ BOOL wss_authenticated; /* Digest认证是否通过 */ char generate_realm[LEN_REALM]; /* 生成的realm */ char generate_opaque[LEN_OPAQUE]; /* 生成的opaque */ char digest_username[LEN_USERNAME]; /* Digest认证用户名 */ char digest_realm[LEN_REALM]; /* Digest认证域 */ char digest_nonce[LEN_DIGEST_NONCE]; /* 服务器生成的nonce */ char digest_uri[LEN_URI]; /* 请求的URI */ char digest_response[LEN_RESPONSE]; /* 客户端计算的响应值 */ char digest_algorithm[LEN_ALGORITHM];/* 算法类型 */ char digest_qop[LEN_QOP]; /* 保护质量 */ char digest_nc[LEN_NC]; /* 随机数计数器 */ char digest_cnonce[LEN_CNONCE]; /* 客户端随机数 */ char digest_opaque[LEN_OPAQUE]; /* 服务器不透明数据 */ /* 其他现有字段... */ } CONTEXT; ``` #### 步骤二:解析Authorization头(`http_parser.c`) **添加Authorization头解析器** 在`http_parser_init()`函数中添加: ```c void http_parser_init() { /* 现有初始化代码... */ /* 添加Authorization头解析器 */ http_key_parser_add("Authorization", http_parse_authorization); /* ... */ } ``` **实现Authorization头解析函数** 参考`http`头格式: ```c GET/cgi-bin/checkout?a=b HTTP/1.1 Authorization: Digest username="Mufasa", realm="realm", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c0", uri="/xxxx/System/Register", response="6629fae49393a05397450978507c4ef1", algorithm=MD5, qop=auth, nc=00000001, cnonce="0a4f113b", /* opaque="5ccc069c403ebaf9f0171e9517f40e41" */ ``` 辅助函数:`trim_whitespace` - 去除键前后的空格,避免因空格导致匹配失败。 - 十六进制字符转数值 ```c /* 移除字符串两端的空格 */ void trim_whitespace(char *str) { char *end; /* 去除前导空格 */ while (isspace((unsigned char)*str)) str++; /* 全空格字符串处理 */ if (*str == 0) return; /* 去除尾部空格 */ end = str + strlen(str) - 1; while (end > str && isspace((unsigned char)*end)) end--; *(end + 1) = '\0'; } /* 十六进制字符转数值 */ int hex_val(char c) { return isdigit(c) ? c - '0' : tolower(c) - 'a' + 10; } ``` **`http_parse_authorization`:** 该函数假设各参数之间使用逗号分隔,且参数值中不包含逗号。 ```c LOCAL S32 http_parse_authorization(CONTEXT *context, char *value) { char *parse_ptr = value; char *token; int error_count = 0; /* 检查Digest认证类型 */ if (strncasecmp(parse_ptr, "Digest ", 7) != 0) { return OK; } parse_ptr += 7; /* 跳过前缀 */ char *saveptr; /* strtok_r上下文指针 */ token = strtok_r(parse_ptr, ",", &saveptr); while (token != NULL) { /* 分割键值对 */ char *eq = strchr(token, '='); if (!eq) { HTTPD_DEBUG("Invalid param format: %s", token); error_count++; token = strtok_r(NULL, ",", &saveptr); continue; } /* 分割键值 */ *eq = '\0'; char *key = token; char *val = eq + 1; /* 移除键值两端的空格 */ trim_whitespace(key); trim_whitespace(val); /* 处理引号包裹的值 */ size_t vlen = strlen(val); if (vlen >= 2 && val[0] == '"' && val[vlen-1] == '"') { val[vlen-1] = '\0'; /* 移除尾部引号 */ memmove(val, val+1, vlen-1); /* 移除头部引号 */ vlen -= 2; /* 更新长度 */ } /* 存储到CONTEXT字段 */ if (strcasecmp(key, "username") == 0) { strncpy(context->digest_username, val, LEN_USERNAME-1); } else if (strcasecmp(key, "realm") == 0) { strncpy(context->digest_realm, val, LEN_REALM-1); } else if (strcasecmp(key, "nonce") == 0) { strncpy(context->digest_nonce, val, LEN_DIGEST_NONCE-1); } else if (strcasecmp(key, "uri") == 0) { /* 规范化URI路径(移除查询参数)*/ char *query_start = strchr(val, '?'); if (query_start) *query_start = '\0'; strncpy(context->digest_uri, val, LEN_URI-1); } else if (strcasecmp(key, "response") == 0) { strncpy(context->digest_response, val, LEN_RESPONSE-1); } else if (strcasecmp(key, "algorithm") == 0) { strncpy(context->digest_algorithm, val, LEN_ALGORITHM-1); } else if (strcasecmp(key, "qop") == 0) { strncpy(context->digest_qop, val, LEN_QOP-1); } else if (strcasecmp(key, "nc") == 0) { strncpy(context->digest_nc, val, LEN_NC-1); } else if (strcasecmp(key, "cnonce") == 0) { strncpy(context->digest_cnonce, val, LEN_CNONCE-1); } else if (strcasecmp(key, "opaque") == 0) { strncpy(context->digest_opaque, val, LEN_OPAQUE-1); } else { HTTPD_DEBUG("Unknown Digest param: %s=%s", key, val); } token = strtok_r(NULL, ",", &saveptr); } /* 检查必需参数是否齐全 */ if (strlen(context->digest_response) == 0) { HTTPD_WARNING("Missing required response field"); return ERROR; } return (error_count > 3) ? ERROR : OK; } ``` #### 步骤三:生成响应Authorization头 `onvif_srv.c`文件,`onvif_make_response(CONTEXT *context)`函数会在状态码非200时调用`http_make_response()`函数发送HTTP错误响应。 ```c #define HTTP_UNAUTHORIZED 401 LOCAL S32 onvif_make_response(CONTEXT *context) { /* 其他字段... */ if (context->code == HTTP_REQ_OK) { if (soap->use_udp == FALSE && soap->xml_buf.start != NULL && soap->xml_buf.start != soap->xml_buf.last) { ONVIF_TRACE("Onvif_make_response return onvif_send_http_rsp_packet."); ret = onvif_send_http_rsp_packet(soap); goto out; } else { ONVIF_TRACE("Onvif_make_response return OK."); ret = OK; goto out; } } ONVIF_TRACE("Onvif_make_response return http_make_response."); ret = http_make_response(context); out: /* 清除content内容 */ http_reset_context(context); return ret; } ``` 在`http_make_response()`函数中,通过`http_send_rsp_header()`设置HTTP响应报文头部。因此,需要修改`http_send_rsp_header()`函数,使其能够在HTTP状态码为401时添加`WWW-Authenticate`头部。 ```c /* Location */ if (HTTP_MOVE_TEMPORARILY == context->code) { // ...原有Location代码... } /* ========== 新增Digest认证头 ========== */ if (HTTP_UNAUTHORIZED == context->code) { /* 必需字段: realm, nonce, qop */ context->digest_realm = “Restricted Area”; /* 生成带时效的nonce */ if (generate_digest_nonce(context) != OK) { return ERROR; } length = snprintf(context->head_end, (context->head_buf_end - context->head_end), "WWW-Authenticate: Digest realm=\"Restricted Area\", nonce=\"%s\", qop=\"auth\"", context->digest_nonce); /* 结束头部 */ length += snprintf(context->head_end + length, (context->head_buf_end - context->head_end - length), "\r\n"); context->head_end += length; context->header_len += length; } /* Access-Control-Allow-Origin */ if (context->origin == TRUE) { /* ...原有代码... */ } ``` #### 步骤四:nonce生成及过期管理: Rand_Byte实现: 在tpssl中定义了RandBytes,但是return 0; ```c int tpssl_RandBytes(unsigned char* buf, int num) { return 0;// 接口已无效,待处理 } ``` 在`tpssl_rsa_encrypt`和其他函数中,使用了`mbedtls_ctr_drbg_random`作为随机数生成器,但这是通过`mbedtls`的上下文来生成随机数的,并不是直接可以调用的函数。 在`mbedtls`中,生成随机数的常用函数是`mbedtls_ctr_drbg_random`。但是,这个函数需要一个`mbedtls_ctr_drbg_context`上下文,而在代码中并没有一个直接封装好的函数来生成随机字节。 但是,在多个函数中(例如`tpssl_rsa_encrypt`)都使用了`mbedtls_ctr_drbg_random`,因此,可以自己封装一个类似于`RandBytes`的函数,但需要确保已经初始化了随机数生成器。 在`tpssl_private_decrypt`函数中,有一个使用随机数生成器的例子: ```c mbedtls_ctr_drbg_init( &ctr_drbg ); mbedtls_entropy_init( &entropy ); ret = mbedtls_ctr_drbg_seed( &ctr_drbg, mbedtls_entropy_func, &entropy, (const unsigned char *) pers, strlen( pers ) ); ... /* 然后使用 mbedtls_ctr_drbg_random 生成随机数 */ ``` 所以,如果需要生成随机数,可以按照上述步骤初始化并使用`mbedtls_ctr_drbg_random`。 ```c int tpssl_SecureRandBytes(unsigned char* buf, int len) { static mbedtls_ctr_drbg_context drbg; static mbedtls_entropy_context entropy; static int initialized = 0; const char pers[] = "tpssl_rand_seed"; if (!initialized) { mbedtls_entropy_init(&entropy); mbedtls_ctr_drbg_init(&drbg); if (mbedtls_ctr_drbg_seed(&drbg, mbedtls_entropy_func, &entropy, (unsigned char*)pers, sizeof(pers)-1) != 0) { return 0; /* 失败返回0 */ } initialized = 1; } if (mbedtls_ctr_drbg_random(&drbg, buf, len) != 0) { return 0; /* 生成随机数失败 */ } return 1; /* 成功返回1 */ } ``` 可实现: ```c #include <time.h> #include <string.h> #define ERROR_CRYPTO_FAIL -1 #define ERROR_BUFFER_OVERFLOW -2 #define OK 0 #define NONCE_LIFETIME 30 S32 generate_digest_nonce(CONTEXT *context) { /* 数据源:时间戳+随机数 */ struct { time_t timestamp; /* 精确到秒的时间戳 */ time_t expiration_time; /* 过期时间 */ unsigned char rand_bytes[16]; /* 强随机数 */ } nonce_seed; /* 取当前时间戳 */ nonce_seed.timestamp = time(NULL); nonce_seed.expiration_time = nonce_seed.timestamp + NONCE_LIFETIME; /* 过期时间 */ /* 生成密码学强随机数 (使用OpenSSL RAND_bytes) */ if (RAND_bytes(nonce_seed.rand_bytes, sizeof(nonce_seed.rand_bytes)) != 1) { return ERROR_CRYPTO_FAIL; /* 随机数生成失败 */ } /* 转为十六进制字符串格式 */ char hex_buffer[2 * sizeof(nonce_seed) + 1]; const unsigned char *ptr = (const unsigned char *)&nonce_seed; for (size_t i = 0; i < sizeof(nonce_seed); i++) { snprintf(hex_buffer + 2*i, 3, "%02x", ptr[i]); } hex_buffer[2 * sizeof(nonce_seed)] = '\0'; /* 组合realm和hex_buffer生成nonce:realm:hex_buffer" */ int len = snprintf(context->digest_nonce, LEN_DIGEST_NONCE, "%s:%s", context->generate_realm, hex_buffer); if (len < 0 || len >= LEN_DIGEST_NONCE) { return ERROR_BUFFER_OVERFLOW; } return OK; } /* NONCE验证函数(应在认证流程中调用) */ int validate_nonce(const char *nonce_str) { /* 提取时间结构体部分(跳过realm前缀) */ const char *hex_seed = strchr(nonce_str, ':') + 1; size_t seed_len = strlen(hex_seed)/2; /* 将HEX转回二进制结构 */ struct { time_t timestamp; time_t expiration_time; unsigned char rand_bytes[16]; } seed; for (size_t i = 0; i < seed_len; i++) { sscanf(hex_seed + 2*i, "%2hhx", (unsigned char*)&seed + i); } /* 过期判断 */ time_t current_time = time(NULL); if (current_time > seed.expiration_time) { return 0; /* 过期 */ } return 1; /* 有效 */ } ``` #### 步骤五:修改`onvif_proc_data_srv`函数(`onvif_srv.c`) 在`onvif_proc_data_srv`函数中,在解析SOAP之前,先检查是否有Digest认证头,并进行验证。 ```c S32 onvif_proc_data_srv(CONTEXT *context) { /* 现有代码(初始化和HTTP检查)... */ /* ==== 新增Digest认证检查 ==== */ /* 初始化认证状态 */ context->digest_authenticated = FALSE; context->wss_authenticated = FALSE; /* 检查是否有Digest认证头以及对realm、nonce进行校验 */ if (context->digest_username[0] != '\0'&& strcmp(context->digest_realm,context->generate_realm) == 0 && validate_nonce(context->digest_nonce)==1) { /* 执行Digest认证 */ if (check_digest_auth(context) == OK) { context->digest_authenticated = TRUE; ONVIF_TRACE("Digest authentication successful"); } else { ONVIF_TRACE("Digest authentication failed"); /* 认证失败,返回401 */ context->code = HTTP_UNAUTHORIZED; return ERROR; } } /* 继续原有处理逻辑(端口处理和XML解析)... */ /* ==== 新增WSS认证检查 ==== */ if (soap->has_header) { if (soap_usernametoken_auth(soap, USER_TYPE_ADMIN) != OK) { ONVIF_ERROR("WSS authentication failed"); soap_fault(soap, "SOAP-ENV:Sender", "ter:NotAuthorized", NULL, "Authentication failed"); soap->error = SOAP_FAULT; context->wss_authenticated = FALSE; } else { context->wss_authenticated = TRUE; ONVIF_TRACE("WSS authentication successful"); } } /* ==== 最终认证检查 ==== */ if (!context->digest_authenticated && !context->wss_authenticated) { ONVIF_TRACE("No valid authentication provided"); context->code = HTTP_UNAUTHORIZED; return ERROR; } /* ==== 请求处理(原有逻辑)... */ return OK; } ``` #### 步骤六:实现Digest认证验证函数 实现一个函数,用于验证Digest认证的响应是否正确。 ```c extern char g_ciphertext[]; S32 check_digest_auth(CONTEXT *context) { if (!context) return -1; /* 验证必要参数存在 */ if (strlen(context->digest_username) == 0 || strlen(context->digest_realm) == 0 || strlen(context->digest_nonce) == 0 || strlen(context->digest_uri) == 0 || strlen(context->digest_response) == 0) { ONVIF_WARN("Missing required Digest auth parameters"); return -1; } /* 获取用户对应的密码 */ char stored_pwd[USER_MANAGEMENT_PASSWD_MAX_STR_LEN + 1] = {0}; if (strcmp(context->digest_username, TP_ROOT_USER) == 0) { /* 管理员账户 */ char *plain_pwd = tpssl_rsa_decrypt(PRI_KEY_FILE_PATH, (U8 *)g_ciphertext,, (U32)strlen(g_ciphertext)); if (!plain_pwd) return -1; strncpy(stored_pwd, plain_pwd, sizeof(stored_pwd)-1); ONVIF_FREE(plain_pwd); } else { /* 第三方账户 */ THIRD_ACCOUNT_USER_MANAGEMENT third_acct; if (0 == ds_read(THIRD_ACCOUNT_PATH, &third_acct, sizeof(third_acct))) { char *plain_pwd = tpssl_rsa_decrypt(PRI_KEY_FILE_PATH, (U8 *)third_acct.ciphertext, (U32)strlen(third_acct.ciphertext)); if (!plain_pwd) return -1; strncpy(stored_pwd, plain_pwd, sizeof(stored_pwd)-1); ONVIF_FREE(plain_pwd); } else { return -1; } } /* 计算HA1 = MD5(username:realm:password) */ char ha1_input[256]; snprintf(ha1_input, sizeof(ha1_input), "%s:%s:%s", context->digest_username, context->digest_realm, stored_pwd); char ha1[33]; tpssl_md5_hex(ha1_input, ha1); memset(stored_pwd, 0, sizeof(stored_pwd)); /* 立即清除密码 */ /* 计算HA2 = MD5(method:uri) */ char ha2_input[256]; snprintf(ha2_input, sizeof(ha2_input), "%s:%s", http_method_str(context->method), context->digest_uri); char ha2[33]; tpssl_md5_hex(ha2_input, ha2); /* 计算预期响应 = MD5(HA1:nonce:nc:cnonce:qop:HA2) */ char resp_input[512]; snprintf(resp_input, sizeof(resp_input), "%s:%s:%s:%s:%s:%s", ha1, context->digest_nonce, context->digest_nc, context->digest_cnonce, context->digest_qop, ha2); char expected_resp[33]; tpssl_md5_hex(resp_input, expected_resp); /* 验证响应值 */ if (strcasecmp(expected_resp, context->digest_response) == 0) { ONVIF_TRACE("Digest auth success for user: %s", context->digest_username); return 0; /* 认证成功 */ } ONVIF_WARN("Digest auth failed. Expected: %s, Received: %s", expected_resp, context->digest_response); return -1; /* 认证失败 */ } /* 使用tpssl实现的MD5计算(十六进制输出) */ void tpssl_md5_hex(const char *input, char *output) { if (!input || !output) return; Md5 ctx; unsigned char digest[16]; // MD5_DIGEST_LENGTH=16 tpssl_InitMd5(&ctx); tpssl_Md5Update(&ctx, (const byte*)input, strlen(input)); tpssl_Md5Final(&ctx, digest); for (int i = 0; i < 16; i++) { sprintf(output + (i * 2), "%02x", digest[i]); } output[32] = '\0'; } /* 将HTTP方法枚举转换为字符串 */ const char *http_method_str(HTTP_METHOD method) { switch(method) { case HTTP_METHOD_GET: return "GET"; case HTTP_METHOD_POST: return "POST"; /* 添加PUT和DELETE(如果枚举中有定义)*/ #if defined(INCLUDE_UPNP_SERVER) case HTTP_METHOD_MGET: return "MGET"; case HTTP_METHOD_MPOST: return "MPOST"; case HTTP_METHOD_SUBSCRIBE: return "SUBSCRIBE"; case HTTP_METHOD_UNSUBSCRIBE:return "UNSUBSCRIBE"; #endif case HTTP_METHOD_UNKOWN: return "UNKNOWN"; default: return "INVALID"; } } ``` ——是我添加了上述代码才出错的,有没有可能是哪里引起的?
最新发布
09-16
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值