1+ using System ;
2+ using System . Linq ;
3+ using Microsoft . AspNetCore . Mvc . Filters ;
4+ using Microsoft . Extensions . Caching . Distributed ;
5+ using Microsoft . Extensions . DependencyInjection ;
6+ using SimApi . Exceptions ;
7+ using SimApi . Helpers ;
8+ using SimApi . ModelBinders ;
9+
10+ namespace SimApi . Attributes ;
11+
12+ [ AttributeUsage ( AttributeTargets . Class | AttributeTargets . Method ) ]
13+ public class SimApiSignAttribute : ActionFilterAttribute
14+ {
15+ protected Type KeyProvider { get ; set ; } = typeof ( SimApiSignProviderBase ) ;
16+
17+ public override void OnActionExecuting ( ActionExecutingContext context )
18+ {
19+ if ( context . HttpContext . RequestServices . GetService ( KeyProvider ) is not SimApiSignProviderBase keyProvider )
20+ {
21+ throw new SimApiException ( 400 , "未配置签名器" ) ;
22+ }
23+
24+ string ? appId = null ;
25+ if ( ! string . IsNullOrEmpty ( keyProvider . AppIdName ) )
26+ {
27+ appId = context . HttpContext . Request . Query [ keyProvider . AppIdName ]
28+ . FirstOrDefault ( ) ?? context . HttpContext . Request . Headers [ keyProvider . AppIdName ]
29+ . FirstOrDefault ( ) ;
30+
31+ if ( string . IsNullOrEmpty ( appId ) )
32+ {
33+ throw new SimApiException ( 400 , $ "获取{ keyProvider . AppIdName } 失败") ;
34+ }
35+ }
36+
37+ // 4. 通过接口获取密钥(解耦的核心:不再直接依赖数据库)
38+ var key = keyProvider . GetKey ( appId ) ;
39+ if ( string . IsNullOrEmpty ( key ) )
40+ {
41+ throw new SimApiException ( 400 , "获取签名KEY失败" ) ;
42+ }
43+
44+ var timestamp = context . HttpContext . Request . Query [ keyProvider . TimestampName ]
45+ . FirstOrDefault ( ) ?? context . HttpContext . Request . Headers [ keyProvider . TimestampName ]
46+ . FirstOrDefault ( ) ;
47+ if ( string . IsNullOrEmpty ( timestamp ) )
48+ {
49+ throw new SimApiException ( 400 , $ "{ keyProvider . TimestampName } 不能为空") ;
50+ }
51+
52+ var nonce = context . HttpContext . Request . Query [ keyProvider . NonceName ]
53+ . FirstOrDefault ( ) ?? context . HttpContext . Request . Headers [ keyProvider . NonceName ]
54+ . FirstOrDefault ( ) ;
55+ if ( string . IsNullOrEmpty ( nonce ) )
56+ {
57+ throw new SimApiException ( 400 , $ "{ keyProvider . NonceName } 不能为空") ;
58+ }
59+
60+ if ( ! int . TryParse ( timestamp , out var ts ) )
61+ {
62+ throw new SimApiException ( 400 , $ "{ keyProvider . TimestampName } 格式错误") ;
63+ }
64+
65+ if ( keyProvider . QueryExpires != 0 )
66+ {
67+ if ( ts > SimApiUtil . TimestampNow + 2 )
68+ {
69+ throw new SimApiException ( 400 , "请校准本地时间" ) ;
70+ }
71+
72+ if ( ts + keyProvider . QueryExpires < SimApiUtil . TimestampNow )
73+ {
74+ throw new SimApiException ( 400 , "请求已过期" ) ;
75+ }
76+
77+ if ( keyProvider . DuplicateRequestProtection )
78+ {
79+ var cache = context . HttpContext . RequestServices . GetRequiredService < IDistributedCache > ( ) ;
80+ if ( cache . GetString ( "SignQuery:" + nonce ) == null )
81+ {
82+ cache . SetString ( "SignQuery:" + nonce , timestamp , new DistributedCacheEntryOptions ( )
83+ {
84+ SlidingExpiration = TimeSpan . FromSeconds ( keyProvider . QueryExpires + 2 )
85+ } ) ;
86+ }
87+ else
88+ {
89+ throw new SimApiException ( 400 , "重复请求" ) ;
90+ }
91+ }
92+ }
93+
94+ var signStr = string . Empty ;
95+ foreach ( var item in keyProvider . SignFields )
96+ {
97+ signStr += $ "{ item } =";
98+ signStr += context . HttpContext . Request . Query [ item ]
99+ . FirstOrDefault ( ) ?? context . HttpContext . Request . Headers [ item ]
100+ . FirstOrDefault ( ) ;
101+ signStr += "&" ;
102+ }
103+
104+ signStr += $ "{ keyProvider . TimestampName } ={ ts } &{ keyProvider . NonceName } ={ nonce } &{ key } ";
105+ var sign = context . HttpContext . Request . Query [ keyProvider . SignName ]
106+ . FirstOrDefault ( ) ?? context . HttpContext . Request . Headers [ keyProvider . SignName ]
107+ . FirstOrDefault ( ) ;
108+ if ( SimApiUtil . Md5 ( signStr ) != sign )
109+ {
110+ throw new SimApiException ( 400 , "签名错误" ) ;
111+ }
112+ }
113+ }
0 commit comments