In the previous blog, I wrote a middleware to deal with API authorization verification, now in another way to deal with the issue of authorization verification, after all, it is now
There are a lot of open source things to use, today is the JWT.
What is a JWT? The full name of the JWT is the JSON WEB TOKENS, which is a self-contained token format. Official website: https://jwt.io/, more or less should have heard this.
Let's take a look at the following two graphs:
Site is the way to access the API through RPC to obtain resources, when the site is directly access to the API, do not get access to the token, then the site is not to get the relevant data resources.
As shown in the diagram on the left, the request was initiated but not the desired result, and when the site first went to the authorization server to access the API's Access_token (token), the
Access_token access to API,API will not return the protected data resource.
This is the approximate process based on token verification. It can be seen that the authorization server occupies a very important position.
Let's take a look at what the authorization server does and how to implement a simple authorization.
What have you done? The role of the authorization server throughout the process is to receive the client to initiate a request for Access_token and verify the legitimacy of its identity, and eventually return a containing
The JSON string for the Access_token.
How is it implemented? We are still inseparable from the middleware of this thing. This time we wrote a tokenprovidermiddleware, mainly to look at the Invoke method and generate Access_token
The method.
1// <summary> 2// invoke the Middleware 3// </summary> 4// <param name= "context" & Gt;</param> 5 //<returns></returns> 6 public Async Task Invoke (HttpContext context) 7 { 8 if (!context. Request.Path.Equals (_options. Path, StringComparison.Ordinal)) 9 { await _next (context); }12 /Request must be POST with Content-type:application/x-www-form-urlencoded14 if (!context. Request.Method.Equals ("POST") | |!context. Request.hasformcontenttype) (returnbadrequest) (context); }19 await generateauthorizedresult (context);
The Invoke method does not have to say much, but here we do a control, only receive the POST request, and is only to receive the data submitted in form form, get request and its
His contenttype type is an illegal request and will return the status of bad request.
Here is the more important thing in authorization, the generation of Access_token.
1//<summary> 2//Get the JWT 3//</summary> 4//<param name= "user Name "></param> 5//<returns></returns> 6 private string GETJWT (string username) 7 {8 var now = Datetime.utcnow; 9 var claims = new Claim[]11 {12 New Claim (jwtregisteredclaimnames.sub, username), new Claim (JWTREGISTEREDCLAIMNAMES.JTI, Guid.newgui D (). ToString ()), Claim New (Jwtregisteredclaimnames.iat, now. ToUniversalTime (). ToString (), claimvaluetypes.integer64)-};17 var JWT = new Jwtsecu Ritytoken (issuer: _options. issuer,20 Audience: _options. audience,21 claims:claims,22 notbefore:now,23 Expires:now. ADD (_options. Expiration), Signingcredentials: _options. Signingcredentials); 25 var encodedjwt = new Jwtsecuritytokenhandler (). Writetoken (JWT); var response = new28 {Access_token = encodedjwt,30 expires_in = (int) _options. expiration.totalseconds,31 Token_type = "Bearer" 32}; Jsonconvert.serializeobject return (response, new jsonserializersettings {formatting = formatting.indented }); 34}
Claims contains multiple claim, which you want to add to your needs, jwtregisteredclaimnames is a struct with all the options available.
Jwtregisteredclaimnames
A Jwtsecuritytoken object is also required, and this object is critical. With the time, claims, and Jwtsecuritytoken objects, just call Jwtsecuritytokenhandler
Writetoken can get an encrypted string like this, which is made up of 3 parts using '. ' Separated . Each section represents what can be found on the official website.
EyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBa B30rmhrhdcefxjoyzgefonfh7hgq
Finally, we'll return the Access_token, Access_token's effective time, and some other information in the form of JSON.
We also need to call our middleware in the Configure method of startup.
1 var audienceconfig = configuration.getsection ("Audience"); 2 var symmetricKeyAsBase64 = audienceconfig[" Secret "]; 3 var keybytearray = Encoding.ASCII.GetBytes (symmetricKeyAsBase64); 4 var signingkey = new Symmetricsecuritykey (Keybytearray); 5 6 apps. Usetokenprovider (New Tokenprovideroptions 7 {8 audience = "Catcher Wong", 9 Issuer = "/http/ catcher1994.cnblogs.com/", signingcredentials = new Signingcredentials (Signingkey, securityalgorithms.hmacsha256), one });
Here, our authorized Service site is ready. Write a few unit tests below to verify this authorization.
Test one: The Authorized Service site can generate the correct JWT.
1 [Fact] 2 public async Task authorized_server_should_generate_token_success () 3 {4 //arrange 5 var data = new dictionary<string, string> (); 6 data. ADD ("username", "Member"); 7 data. ADD ("password", "123"); 8 httpcontent ct = new formurlencodedcontent (data), 9 //act11 System.Net.Http.HttpResponseMessage Message_token = await _client. Postasync ("Http://127.0.0.1:8000/auth/token", CT); string res = await Message_token. Content.readasstringasync (); var obj = newtonsoft.json.jsonconvert.deserializeobject<token> (res); 14 //assert16 assert.notnull (obj), assert.equal ("All", obj.expires_in); assert.equal (3, Obj.access_token. Split ('. '). Length); assert.equal ("Bearer", obj.token_type);
Test Two: The Authorized Service site cannot generate the correct JWT because the user name or password is incorrect.
1 [Fact] 2 public async Task Authorized_server_should_generate_token_fault_by_invalid_app () 3 {4 // Arrange 5 var data = new dictionary<string, string> (); 6 data. ADD ("username", "Member"); 7 data. ADD ("Password", "123456"); 8 httpcontent ct = new formurlencodedcontent (data), 9 //act11 System.Net.Http.HttpResponseMessage Message_token = await _client. Postasync ("Http://127.0.0.1:8000/auth/token", CT); var res = await Message_token. Content.readasstringasync (); Dynamic obj = Newtonsoft.Json.JsonConvert.DeserializeObject (res); Assert16 assert.equal ("Invalid_grant", (string) obj.error), assert.equal (Httpstatuscode.badrequest, Message_token. StatusCode);
Test Three: The Authorized Service site cannot generate the correct JWT because it is not initiating a POST request.
1 [Fact] 2 public async Task Authorized_server_should_generate_token_fault_by_invalid_httpmethod () 3 {4 //arrange 5 Uri uri = new Uri ("http://127.0.0.1:8000/auth/token?username=Member&password=123456"); 6 7 //act 8 System.Net.Http.HttpResponseMessage Message_token = await _client. Getasync (URI); 9 var res = await Message_token. Content.readasstringasync (); Dynamic obj = Newtonsoft.Json.JsonConvert.DeserializeObject (res); assert13 assert.equal ("Invalid_grant", (string) obj.error); assert.equal (Httpstatuscode.badrequest, Message_token. StatusCode);
Take a look at the results of the test: all passed.
Breakpoint take a access_token to http://jwt.calebb.net/decryption look
Eyjhbgcioijiuzi1niisinr5cci6ikpxvcj9.eyjzdwiioijnzw1izxiilcjqdgkioii2mzi1mme1my0ymjy5ltq4yzetymqwni1lowrimzdmmtrmytqilcjp Yxqioiiymde2lzexlzeyidi6ndg6mtcilcjuymyioje0nzg5mtg4otcsimv4cci6mtq3odkxotq5nywiaxnzijoiahr0cdovl2nhdgnozxixotk0lmnuymxvz 3muy29tlyisimf1zci6iknhdgnozxigv29uzyj9.cu2vtj4jahgbjgzwv2jcmvz17hcyosrntjktiea0ebq
The following is the development of the API.
This is a demonstration of the valuecontroller generated directly from the new API project, which is similar to the ASP. The point here is to configure
Jwtbearerauthentication, here is no need for us to write another middleware, we are defined to use the option and then directly with the jwtbearerauthentication can be.
1 public void Configurejwtauth (Iapplicationbuilder app) 2 {3 var audienceconfig = Configuration.getsection ("Audience"); 4 var symmetricKeyAsBase64 = audienceconfig["Secret"]; 5 var Keybytearray = Encoding.ASCII.GetBytes (symmetricKeyAsBase64); 6 var signingkey = new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey (Keybytearray); 7 8 var tokenvalidationparameters = new Tokenvalidationparameters 9 {ten//the SI Gning key must match!11 Validateissuersigningkey = true,12 Issuersigningkey = signingkey,1 3//Validate the JWT Issuer (ISS) claim15 Validateissuer = true,16 Val Idissuer = "http://catcher1994.cnblogs.com/", +//Validate the JWT audience (AUD) claim19 Validateaudience = true,20 validaudience = "Catcher Wong", 21 22 Validate the token expiry23 validatelifetime = true,24 Clockskew = Timespan.zero26};27 app. Usejwtbearerauthentication (new JwtBearerOptions29 {automaticauthenticate = true,31 Automaticchallenge = true,32 Tokenvalidationparameters = tokenvalidationparameters,33 }); 34}
Then call the above method in the startup configure.
1 public void Configure (Iapplicationbuilder app, Ihostingenvironment env, iloggerfactory loggerfactory) 2 {3 Loggerfactory.addconsole (configuration.getsection ("Logging")); 4 loggerfactory.adddebug (); 5 6 Configurejwtauth (APP), 7 8 app. Usemvc (); 9 }
After this, most of the work is done, and the most important step is to add authorize to the API that you want to protect, so that the Get method will be
Ask for Access_token to return the result, otherwise it will return 401. This is done on a single method, or it can be added to the entire controller, so that the side of the controller is attribute
Law will be protected.
1 //Get API/VALUES/52 [HttpGet ("{ID}")]3 [authorize]4 public string GET (int id) 5 {6 return "Value"; 7 }
OK, also write several unit tests to verify.
Test One: Valueapi returns a 401 status in the absence of an authorization request.
1 [Fact] 2 public void Value_api_should_return_unauthorized_without_auth () 3 { 4 //act 5 Httpresponsemessage message = _client. Getasync ("HTTP://LOCALHOST:63324/API/VALUES/1"). Result; 6 String result = message. Content.readasstringasync (). Result; 7 8 //assert 9 assert.false (message. Issuccessstatuscode); assert.equal (httpstatuscode.unauthorized,message. StatusCode); assert.empty (result);
Test two: Valueapi request a method without the [authorize] flag to return the result normally.
1 [Fact] 2 public void Value_api_should_return_result_without_authorize_attribute () 3 {4 //act 5 Httpresponsemessage message = _client. Getasync ("Http://localhost:63324/api/values"). Result; 6 String result = message. Content.readasstringasync (). Result; 7 var res = newtonsoft.json.jsonconvert.deserializeobject<string[]> (result); 8 9 //assert10 assert.true (message. Issuccessstatuscode); assert.equal (2, Res. Length);
Test Three: Valueapi returns the correct result in an authorized request.
1 [Fact] 2 public void Value_api_should_success_by_valid_auth () 3 {4 //arrange 5 var data = new Dictionary<string, string> (); 6 data. ADD ("username", "Member"); 7 data. ADD ("password", "123"); 8 httpcontent ct = new formurlencodedcontent (data), 9 //act11 var obj = getaccesstoken (CT); _client. Defaultrequestheaders.add ("Authorization", "Bearer" + obj.access_token); Httpresponsemessage message = _ Client. Getasync ("HTTP://LOCALHOST:63324/API/VALUES/1"). result;14 String result = message. Content.readasstringasync (). result;15 //assert17 assert.true (message. Issuccessstatuscode) assert.equal (3, Obj.access_token. Split ('. '). Length); assert.equal ("value", result); ( }
Take a look at the results of the test:
Test passed.
The protected method is accessed directly through the browser. The response header will prompt Www-authenticate:bearer, which is an authentication challenge that tells the client that it must provide a
Access to this resource (API) should be authenticated.
This is the reason why a header is added to the unit test, and the normal use is to add this to the requested message header.
_client. Defaultrequestheaders.add ("Authorization", "Bearer" + obj.access_token);
Actually look at the source code, faster to know why. JwtBearerHandler.cs
is about the Head plus authorization source interpretation.
Jwtbearer Source: Microsoft.AspNetCore.Authentication.JwtBearer Sample code for this article: Jwttokendemo
Thanks for your reading!!!
JSON WEB TOKENS