ASP. NET core implements OAUTH2 Authorizationcode mode authorization server
Program.cs to the Main method: Call Useurls to set the IP address of the IDENTITYSERVER4 authorization service
1 var host = new WebHostBuilder ()
2 .UseKestrel ()
3 // IdentityServer4 needs to configure UseUrls
4 .UseUrls ("http: // localhost: 5114")
5 .UseContentRoot (Directory.GetCurrentDirectory ())
6 .UseIISIntegration ()
7 .UseStartup <Startup> ()
8 .Build ();
Startup.cs configuration in the-->configureservices method:
1 // RSA: Certificate length is above 2048, otherwise throw exception
2 // Configure AccessToken encryption certificate
3 var rsa = new RSACryptoServiceProvider ();
4 // Get the encryption certificate from the configuration file
5 rsa.ImportCspBlob (Convert.FromBase64String (Configuration ["SigningCredential"]));
6 // Configure IdentityServer4
7 services.AddSingleton <IClientStore, MyClientStore> (); // Inject the implementation of IClientStore, which can be used to verify the client at runtime
8 services.AddSingleton <IScopeStore, MyScopeStore> (); // Inject the implementation of IScopeStore, which can be used to verify the scope at runtime
9 // Inject the implementation of IPersistedGrantStore, used to store AuthorizationCode, RefreshToken, etc. The default implementation is stored in memory.
10 // If the service restarts, these data will be emptied, so IPersistedGrantStore can be implemented to write these data to the database or NoSql (Redis)
11 services.AddSingleton <IPersistedGrantStore, MyPersistedGrantStore> ();
12 services.AddIdentityServer ()
13 .AddSigningCredential (new RsaSecurityKey (rsa));
14 //. AddTemporarySigningCredential () // Generate a temporary encryption certificate, which will be regenerated each time the service is restarted
15 //. AddInMemoryScopes (Config.GetScopes ()) // Set Scopes to memory
16 //. AddInMemoryClients (Config.GetClients ()) // Set Clients to memory
Startup.cs---Configure the configuration in the method:
1 // Use IdentityServer4
2 app.UseIdentityServer ();
3 // Use the cookie module
4 app.UseCookieAuthentication (new CookieAuthenticationOptions
5 {
6 AuthenticationScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme,
7 AutomaticAuthenticate = false,
8 AutomaticChallenge = false
9 });
Client Configuration
Way One:
. Addinmemoryclients (Config.getclients ())//Set clients in memory, IDENTITYSERVER4 get it for validation
Way Two (recommended):
Services. Addsingleton<iclientstore, myclientstore> (); Injection Iclientstore implementation for runtime acquisition and validation of the client
The realization of Iclientstore
1 public class MyClientStore: IClientStore
2 {
3 readonly Dictionary <string, Client> _clients;
4 readonly IScopeStore _scopes;
5 public MyClientStore (IScopeStore scopes)
6 {
7 _scopes = scopes;
8 _clients = new Dictionary <string, Client> ()
9 {
10 {
11 "auth_clientid",
12 new Client
13 {
14 ClientId = "auth_clientid",
15 ClientName = "AuthorizationCode Clientid",
16 AllowedGrantTypes = new string [] {GrantType.AuthorizationCode}, // Allow AuthorizationCode mode
17 ClientSecrets =
18 {
19 new Secret ("secret" .Sha256 ())
20},
21 RedirectUris = {"http: // localhost: 6321 / Home / AuthCode"},
22 PostLogoutRedirectUris = {"http: // localhost: 6321 /"},
23 // AccessTokenLifetime = 3600, // AccessToken expiration time, in seconds (defaults to 3600 seconds / 1 hour)
24 // AuthorizationCodeLifetime = 300, // Set the validity time of AuthorizationCode, in seconds (defaults to 300 seconds / 5 minutes)
25 // AbsoluteRefreshTokenLifetime = 2592000, // RefreshToken maximum expiration time, in seconds. Defaults to 2592000 seconds / 30 day
26 AllowedScopes = (from l in _scopes.GetEnabledScopesAsync (true) .Result select l.Name) .ToList (),
27}
28}
29};
30}
31
32 public Task <Client> FindClientByIdAsync (string clientId)
33 {
34 Client client;
35 _clients.TryGetValue (clientId, out client);
36 return Task.FromResult (client);
37}
38}
Scope configuration
Way One:
. Addinmemoryscopes (Config.getscopes ())//Set scopes in memory, IdentityServer4 get it for validation
Way Two (recommended):
Services. Addsingleton<iscopestore, myscopestore> (); Implementation of injection Iscopestore for runtime acquisition and validation of scope
The realization of Iscopestore
1 public class MyScopeStore: IScopeStore
2 {
3 readonly static Dictionary <string, Scope> _scopes = new Dictionary <string, Scope> ()
4 {
5 {
6 "api1",
7 new Scope
8 {
9 Name = "api1",
10 DisplayName = "api1",
11 Description = "My API",
12}
13},
14 {
15 // RefreshToken's Scope
16 StandardScopes.OfflineAccess.Name,
17 StandardScopes.OfflineAccess
18},
19};
20
21 public Task <IEnumerable <Scope >> FindScopesAsync (IEnumerable <string> scopeNames)
twenty two {
23 List <Scope> scopes = new List <Scope> ();
24 if (scopeNames! = Null)
25 {
26 Scope sc;
27 foreach (var sname in scopeNames)
28 {
29 if (_scopes.TryGetValue (sname, out sc))
30 {
31 scopes.Add (sc);
32}
33 else
34 {
35 break;
36}
37}
38}
39 // The return value scopes cannot be null
40 return Task.FromResult <IEnumerable <Scope >> (scopes);
41}
42
43 public Task <IEnumerable <Scope >> GetScopesAsync (bool publicOnly = true)
44 {
45 // publicOnly is true: get public scope; false: get all scope
46 // No distinction is made here
47 return Task.FromResult <IEnumerable <Scope >> (_ scopes.Values);
48}
49}
Resource server
The configuration of the resource server is described in the previous article (http://www.cnblogs.com/skig/p/6079457.html), and the details can be referenced in the source code.
Test
Flowchart of Authorizationcode mode (from: https://tools.ietf.org/html/rfc6749):
Process implementation Step A
A simple implementation of a third-party client page:
Click the Accesstoken button to access the authorization server, the flowchart is step a:
1 // Access authorization server
2 return Redirect (OAuthConstants.AuthorizationServerBaseAddress + OAuthConstants.AuthorizePath + "?"
3 + "response_type = code"
4 + "& client_id =" + OAuthConstants.Clientid
5 + "& redirect_uri =" + OAuthConstants.AuthorizeCodeCallBackPath
6 + "& scope =" + OAuthConstants.Scopes
7 + "& state =" + OAuthConstants.State);
Step b
When the authorization server receives the request, it will determine whether the user has logged in, and if not, jump to the landing page (if logged in, some relevant information will be stored in the cookie):
1 /// <summary>
2 /// landing page
3 /// </ summary>
4 [HttpGet]
5 public async Task <IActionResult> Login (string returnUrl)
6 {
7 var context = await _interaction.GetAuthorizationContextAsync (returnUrl);
8 var vm = BuildLoginViewModel (returnUrl, context);
9 return View (vm);
10}
11
12 /// <summary>
13 /// Login account verification
14 /// </ summary>
15 [HttpPost]
16 [ValidateAntiForgeryToken]
17 public async Task <IActionResult> Login (LoginInputModel model)
18 {
19 if (ModelState.IsValid)
20 {
21 // Account password verification
22 if (model.Username == "admin" && model.Password == "123456")
twenty three {
24 AuthenticationProperties props = null;
25 // judge whether to remember login
26 if (model.RememberLogin)
27 {
28 props = new AuthenticationProperties
29 {
30 IsPersistent = true,
31 ExpiresUtc = DateTimeOffset.UtcNow.AddMonths (1)
32};
33};
34 // Parameter 1: Subject, which can be obtained in the resource server. The resource server is obtained through User.Claims.Where (l => l.Type == "sub"). FirstOrDefault ();
35 // Parameter 2: Account number
36 await HttpContext.Authentication.SignInAsync ("admin", "admin", props);
37 // Verify ReturnUrl, ReturnUrl redirects to authorization page
38 if (_interaction.IsValidReturnUrl (model.ReturnUrl))
39 {
40 return Redirect (model.ReturnUrl);
41}
42 return Redirect ("~ /");
43}
44 ModelState.AddModelError ("", "Invalid username or password.");
45}
46 // LoginViewModel that generates the error message
47 var vm = await BuildLoginViewModelAsync (model);
48 return View (vm);
49}
After successful login, redirect to the authorization page, ask the user whether authorization, is the flowchart of step B:
1 /// <summary>
2 /// show the permissions that the user can grant
3 /// </ summary>
4 /// <param name = "returnUrl"> </ param>
5 /// <returns> </ returns>
6 [HttpGet]
7 public async Task <IActionResult> Index (string returnUrl)
8 {
9 var vm = await BuildViewModelAsync (returnUrl);
10 if (vm! = Null)
11 {
12 return View ("Index", vm);
13}
14
15 return View ("Error", new ErrorViewModel
16 {
17 Error = new ErrorMessage {Error = "Invalid Request"},
18});
19}
Step c
Authorization succeeds, redirects to the address specified by Redirect_uri (step a passes), and the authorization code is also set to the URL's parameter code:
1 /// <summary>
2 /// user authorization verification
3 /// </ summary>
4 [HttpPost]
5 [ValidateAntiForgeryToken]
6 public async Task <IActionResult> Index (ConsentInputModel model)
7 {
8 // parse returnUrl
9 var request = await _interaction.GetAuthorizationContextAsync (model.ReturnUrl);
10 if (request! = Null && model! = Null)
11 {
12 if (ModelState.IsValid)
13 {
14 ConsentResponse response = null;
15 // User does not agree with authorization
16 if (model.Button == "no")
17 {
18 response = ConsentResponse.Denied;
19}
20 // User agrees to authorize
21 else if (model.Button == "yes")
twenty two {
23 // Set the selected authorized scopes
24 if (model.ScopesConsented! = Null && model.ScopesConsented.Any ())
25 {
26 response = new ConsentResponse
27 {
28 RememberConsent = model.RememberConsent,
29 ScopesConsented = model.ScopesConsented
30};
31}
32 else
33 {
34 ModelState.AddModelError ("", "You must pick at least one permission.");
35}
36}
37 else
38 {
39 ModelState.AddModelError ("", "Invalid Selection");
40}
41 if (response! = Null)
42 {
43 // Set the authorization result to identityserver
44 await _interaction.GrantConsentAsync (request, response);
45 // Authorization redirected successfully
46 return Redirect (model.ReturnUrl);
47}
48}
49 // There is an error, reauthorize
50 var vm = await BuildViewModelAsync (model.ReturnUrl, model);
51 if (vm! = Null)
52 {
53 return View (vm);
54}
55}
56 return View ("Error", new ErrorViewModel
57 {
58 Error = new ErrorMessage {Error = "Invalid Request"},
59});
60}
Step d
After the authorization is successfully redirected to the specified third road square (Redirect_uri specified by step a), then the redirected address is implemented to obtain the Accesstoken (which is implemented by the third-party side):
1 public IActionResult AuthCode (AuthCodeModel model)
2 {
3 GrantClientViewModel vmodel = new GrantClientViewModel ();
4 if (model.state == OAuthConstants.State)
5 {
6 // Get AccessToken via Authorization Code
7 var client = new HttpClientHepler (OAuthConstants.AuthorizationServerBaseAddress + OAuthConstants.TokenPath);
8 client.PostAsync (null,
9 "grant_type =" + "authorization_code" +
10 "& code =" + model.code + // Authorization Code
11 "& redirect_uri =" + OAuthConstants.AuthorizeCodeCallBackPath +
12 "& client_id =" + OAuthConstants.Clientid +
13 "& client_secret =" + OAuthConstants.Secret,
14 hd => hd.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue ("application / x-www-form-urlencoded"),
15 rtnVal =>
16 {
17 var jsonVal = JsonConvert.DeserializeObject <dynamic> (rtnVal);
18 vmodel.AccessToken = jsonVal.access_token;
19 vmodel.RefreshToken = jsonVal.refresh_token;
20},
21 fault => _logger.LogError ("Get AccessToken Error:" + fault.ReasonPhrase),
22 ex => _logger.LogError ("Get AccessToken Error:" + ex)). Wait ();
twenty three }
twenty four
25 return Redirect ("~ / Home / Index?"
26 + nameof (vmodel.AccessToken) + "=" + vmodel.AccessToken + "&"
27 + nameof (vmodel.RefreshToken) + "=" + vmodel.RefreshToken);
28}
Step E
The authorization server validates the authorization code for step D request delivery and verifies that the Accesstoken is successfully generated and returns:
Where, click Refreshtoken to refresh Accesstoken:
1 // Refresh AccessToken
2 var client = new HttpClientHepler (OAuthConstants.AuthorizationServerBaseAddress + OAuthConstants.TokenPath);
3 client.PostAsync (null,
4 "grant_type =" + "refresh_token" +
5 "& client_id =" + OAuthConstants.Clientid +
6 "& client_secret =" + OAuthConstants.Secret +
7 "& refresh_token =" + model.RefreshToken,
8 hd => hd.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue ("application / x-www-form-urlencoded"),
9 rtnVal =>
10 {
11 var jsonVal = JsonConvert.DeserializeObject <dynamic> (rtnVal);
12 vmodel.AccessToken = jsonVal.access_token;
13 vmodel.RefreshToken = jsonVal.refresh_token;
14},
15 fault => _logger.LogError ("RefreshToken Error:" + fault.ReasonPhrase),
16 ex => _logger.LogError ("RefreshToken Error:" + ex)). Wait ();
Click Callresources to access the resource server:
1 // Access resource service
2 var client = new HttpClientHepler (OAuthConstants.ResourceServerBaseAddress + OAuthConstants.ResourcesPath);
3 client.GetAsync (null,
4 hd => hd.Add ("Authorization", "Bearer" + model.AccessToken),
5 rtnVal => vmodel.Resources = rtnVal,
6 fault => _logger.LogError ("CallResources Error:" + fault.ReasonPhrase),
7 ex => _logger.LogError ("CallResources Error:" + ex)). Wait ();
Click Logout to log out:
1 // Access the authorization server, log out and log in
2 return Redirect (OAuthConstants.AuthorizationServerBaseAddress + OAuthConstants.LogoutPath + "?"
3 + "logoutId =" + OAuthConstants.Clientid);
Logoff implementation code for the authorization server:
1 /// <summary>
2 /// Log out of the login page (because some information about the account will be stored in the cookie)
3 /// </ summary>
4 [HttpGet]
5 public async Task <IActionResult> Logout (string logoutId)
6 {
7 if (User.Identity.IsAuthenticated == false)
8 {
9 // If the user has not authorized, then return
10 return await Logout (new LogoutViewModel {LogoutId = logoutId});
11}
12 // Show logout prompt, this can prevent attacks if the user signs another malicious webpage
13 var vm = new LogoutViewModel
14 {
15 LogoutId = logoutId
16};
17 return View (vm);
18}
19
20 /// <summary>
21 /// Handle logout
22 /// </ summary>
23 [HttpPost]
24 [ValidateAntiForgeryToken]
25 public async Task <IActionResult> Logout (LogoutViewModel model)
26 {
27 // Clear authorization information in cookies
28 await HttpContext.Authentication.SignOutAsync ();
29 // Set User to appear as anonymous user
30 HttpContext.User = new ClaimsPrincipal (new ClaimsIdentity ());
31 Client logout = null;
32 if (model! = Null &&! String.IsNullOrEmpty (model.LogoutId))
33 {
34 // Get related information of Logout
35 logout = await _clientStore.FindClientByIdAsync (model.LogoutId);
36}
37 var vm = new LoggedOutViewModel
38 {
39 PostLogoutRedirectUri = logout? .PostLogoutRedirectUris? .FirstOrDefault (),
40 ClientName = logout? .ClientName,
41};
42 return View ("LoggedOut", vm);
43}
ASP. NET core implements OAuth2.0 Authorizationcode mode