Authentication, authorization
What is identity authentication
Identity authentication is a mechanism to verify the validity of a client when it accesses server resources
What is authorization
Authorization is a mechanism by which clients can access server resources only after being authenticated
Why use authentication and authorization
In order to ensure the security of server-side resources, we must understand it from the real project
What are the ways of identity authentication and authorization
1. Base authentication
Base64 Id authentication == HTTPS
2. Digest authentication
MD5 message digest authentication == HTTPS
3. Bearer certification
Token (electronic ID card) authentication is based on the user information and other information into a token, and then the token authentication
Token authentication is a stateless authentication mode that can be extended indefinitely and is particularly suitable for single sign-on (SSO)
3.1 OAuth 2.0 ==== Authentication mode
3.2 JWT is also a form of identification
There are two types of tokens
Reference type Token == OAuth 2.0
None User information
Self contained token
There is user related information JWT === address, phone, ID, etc
In actual distributed projects, Bearer is mostly used for identity authentication and authorization
In distributed projects or microservice projects, to ensure unified system login (SSO login),
Use the OpenID protocol standard to standardize the identity authentication function === specification
It also uses the OAuth 2.0 protocol standard to standardize access === specification
To combine authentication (single sign-on) with authorization, the OpenID Connect protocol standard === interface emerged
OpenID Connect = OpenID + OAuth 2.0
SSO+OAuth 2.0 enables single sign-on and authorization
Single sign-on and authorization are also available for IdentityServer4
And then we implemented all of these frameworks together and we’re going to talk about IdentityServer4
How do I use IdentityServer4 in a project
What is IdentityServer4
IdentityServer is an identity authentication and authorization program based on the OpenID Connect protocol standard, which implements the OpenID Connect and OAuth 2.0 protocols.
IdentityServer4 function
Secure your resources Authenticate users using local accounts or through external identity providers provide session management and single sign-on management and authentication Clients issue identity and access tokens to customers authentication tokens
IdentityServer4 Internal concepts
The following terms are explained
User A User is a person who accesses resources using a registered client (that is, a registered client in ID4).
Client
A client is a piece of software that requests tokens from IdentityServer, authenticating users with tokens, and accessing server resources with authorization tokens. However, the client must first be registered with the identityserver service before applying for a token.
The actual client can not only be a Web application, APP or desktop application (you can think of it as PC software), SPA, server process, etc.
Resources
A resource is something you want to protect with IdentityServer. It could be the user’s identity data or an API resource. Each resource has a unique name, use this unique name to identify the client which want to access resources (before the visit, the actual identityserver server has been configured for which the client can access which resources, so you don’t have to understand as long as you specify a name for the client they can access any resource) casually.
The user’s identity information actually consists of a set of claims. For example, the name or email will be included in the identity information (which will be returned to the called client after verification by IdentityServer).
The API resource is the function that the client wants to call (usually returned to the client in JSON or XML format, such as WebAPI, WCF, webService). It is usually modeled through webAPI, but not necessarily webAPI. I have emphasized that you can make other types of formats, This depends on the specific use scenario.
Id_token JWT An identity token is a description of the authentication process. It must at least identify the master identity information of a user (Called the sub aka subject claim), and the authentication time and authentication method of the user. However, the identity token can contain additional identity data, which can be customized by the developer. However, in order to ensure the efficiency of data transfer, the developer usually does not do too much extra Settings.
Access_token Oauth 2.0 Access token allows a client to access an API resource. The client requests an access token, which is then used to access API resources. The access token contains information about the client and the user (if any, depending on whether the business needs it, but not usually necessary) that the API uses to grant data access to the client.
IdentityServer4 Role logical diagram
See figure part
IdentityServer4 is used in microservices
IdentityServer4 official website address
Chinese address: www.identityserver.com.cn/Home/Detail…
English address: identityserver4. Readthedocs. IO/en / 3.1.0 /
Client authentication mode
conditions
IdentityServer4 Authentication Server
2. Client
3. Microservices (Resources)
4, the user
steps
1, NutGet download IdentityServer4 in resource server
1.1. Configure IdentityServer4 in the IOC container
/ / 1, the ioc container, add IdentityServer4 services. AddIdentityServer () AddDeveloperSigningCredential () / / 1, the user login configuration .addinmemoryApiResources (config.getapiResources ()).addinMemoryClients (config.getClients ())Copy the code
1.2. Use IdentityServer4
// 1, use IdentityServe4 app.useidentityServer (); //2, add static resource access app.usestaticFiles ();Copy the code
1.3. Configure the test user Config class
// </summary> public class Config {/// </summary> // 1, micro service API resources /// </summary> /// 1, micro service API resources /// </summary> /// <returns></returns> public static IEnumerable<ApiResource> GetApiResources() { return new List<ApiResource> { new ApiResource("TeamService", "TeamService API needs to be protected ")}; /// </summary> /// </returns> public static IEnumerable<Client> GetClients() {return </ // </summary> // </returns> public static IEnumerable<Client> GetClients() {return New List<Client> {new Client {ClientId = "Client ", // No interactive user, use ClientId /secret to implement authentication. AllowedGrantTypes = GrantTypes ClientCredentials, / / for the password authentication ClientSecrets = {new Secret (" Secret ". Sha256 ())}, // Scopes that the client has access to (Scopes) AllowedScopes = {"TeamService"}}}; }}Copy the code
2. Micro-service configuration
2.1 download IdentityServer4 Nuget. AccessTokenValidation
2.2 Configuring Identity and Authorization Authentication (Configure Authentication resources to be protected by resource Servers)
/ / 6, calibration AccessToken, calibration services from the identity check center. AddAuthentication (IdentityServerAuthenticationDefaults. AuthenticationScheme) .AddIdentityServerAuthentication(options => { options.Authority = "http://localhost:5005"; Options. ApiName = "TeamService"; / / 2, name of the API (project name) options. RequireHttpsMetadata = false; });Copy the code
2.3 Enabling Identity Authentication
app.UseAuthentication(); // 1. Use authenticationCopy the code
3. Client configuration
3.1 Nuget Download IdentityModel
3.2 Call the code
The default client authentication mode is described above
Client password authentication mode
conditions
1. Test
steps
1. Add a new client in Config
New Client {ClientId = "Client - password", / / use the user name password interactive authentication AllowedGrantTypes = GrantTypes. ResourceOwnerPassword, // ClientSecrets = {new Secret(" Secret ".sha256 ())}, // AllowedScopes = {"TeamService"}},Copy the code
2. In config, add the test user
/// </summary> // </returns> public static List<TestUser> GetUsers() {return new List<TestUser>() { new TestUser { SubjectId="1", Username="tony", Password="123456" } }; }Copy the code
3. Add the configuration in startup
/ / 1, the ioc container, add IdentityServer4 services. AddIdentityServer () AddDeveloperSigningCredential () / / 1, the user login configuration .addinmemoryApiResources (config.getapiResources ()).addinMemoryClients (config.getClients ()) .addTestUsers (config.getUsers ())//Copy the code
4. Start the call
The call procedure is already written in the code
Authorization code mode authentication (obtain the assess_token by code)
conditions
1, OpenIdConnect
steps
1, download Microsoft Nuget. AspNetCore. Authentication. OpenIdConnect
2. Configure the authentication server
2.1 Add the OpenID identity resource statement in config
public static IEnumerable<IdentityResource> Ids => new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile()
};
Copy the code
2.2 Adding a Client in Config
// OpenId new Client {ClientId="client-code", ClientSecrets={new Secret(" Secret ".sha256 ())}, AllowedGrantTypes=GrantTypes.Code, RequireConsent=false, RequirePkce=true, RedirectUris={ "https://localhost:5006/signin-oidc"}, PostLogoutRedirectUris={ "https://localhost:5006/signout-callback-oidc"}, AllowedScopes=new List<string>{ IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants. StandardScopes. Profile, "TeamService" / / to enable support for the refresh token}, / / add grant access AllowOfflineAccess = true}Copy the code
2.3 Adding an OpenidTest User
// OpenID authentication new TestUser{SubjectId = "818727", Username = "SubjectId ", Password = "123456", Claims = {new Claim(JwtClaimTypes.Name, "three "), new Claim(JwtClaimTypes.GivenName," three "), New Claim (JwtClaimTypes FamilyName, "zhang"), a new Claim (JwtClaimTypes. Email, "zhangsan.com"), new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean), new Claim(JwtClaimTypes.WebSite, "http://zhangsan.com"), // new Claim(jwtclaimtypes.address, @"{' city ': 'hangzhou ',' zip ': '310000' }", IdentityServer4.IdentityServerConstants.ClaimValueTypes.Json) } }Copy the code
2.4. Add the configuration to startup
/ / 1, the ioc container, add IdentityServer4 services. AddIdentityServer () AddDeveloperSigningCredential () / / 1, the user login configuration .addinmemoryApiResources (config.getapiResources ()).addinMemoryClients (config.getClients ()) AddTestUsers (Config. GetUsers ()) / / 4, add the logged in user (patterns). AddInMemoryIdentityResources (Config. Ids); // use openID modeCopy the code
2.5 Preparing for logging in to the UI
It’s ready in the code
3. Client configuration
3.1 Configuration in startup.cs File
// We use Cookies to log in to users locally (through "Cookies" as DefaultScheme) and set DefaultChallengeScheme to OIDC. Because when we need users to log in, we will use the OpenID Connect protocol. services.AddAuthentication(options => { options.DefaultScheme = "Cookies"; options.DefaultChallengeScheme = "oidc"; }) // Add a handler that can handle Cookies.AddCookie("Cookies") // Use the handler to configure the implementation of OpenID Connect protocol. options => { options.Authority = "http://localhost:5005"; / / trusted token service address options. RequireHttpsMetadata = false; options.ClientId = "client-code"; options.ClientSecret = "secret"; options.ResponseType = "code"; options.SaveTokens = true; Add("TeamService"); // Add("TeamService"); // Add("TeamService"); options.Scope.Add("offline_access"); });Copy the code
3.2 Using the Configuration
// 1, use static page app.usestaticFiles (); // 2. Enable app.useAuthentication ();Copy the code
IdentityServer4 persistence
It is mainly to store resource data, user data and identity data in the database.
conditions
1, ConfigurationDbContext
Used to configure data such as Clients, Resources, and scopes.
2, PersistedGrantDbContext
Used for temporary manipulation of data, such as authorization codes and refreshing tokens
steps
1, install IdentityServer4 Nuge. EntityFramework
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.Tools
2. Reconfigure the startup. cs file
Var migrationsAssembly = typeof(Startup).gettypeInfo ().assembly.getName ().name; var connectionString = Configuration.GetConnectionString("DefaultConnection"); services.AddIdentityServer() .AddConfigurationStore(options => { options.ConfigureDbContext = builder => { builder.UseSqlServer(connectionString, options => options.MigrationsAssembly(migrationsAssembly)); }; }) .AddOperationalStore(options => { options.ConfigureDbContext = builder => { builder.UseSqlServer(connectionString, options => options.MigrationsAssembly(migrationsAssembly)); }; }) .AddTestUsers(Config.GetUsers()) .AddDeveloperSigningCredential();Copy the code
3. Configuration in AppSettings. json
{ "ConnectionStrings": { "DefaultConnection": "Data Source=.; Initial Catalog=IndentityServer4; Persist Security Info=True; User ID=sa; Password=tony" } }Copy the code
4. Execute the migration script
dotnet ef migrations add PersistedGrantDbMigration -c PersistedGrantDbContext -o Data/Migrations/IdentityServer/PersistedGrantDb
dotnet ef migrations add ConfigurationDbMigration -c ConfigurationDbContext -o Data/Migrations/IdentityServer/ConfigurationDb
Copy the code
5. Migration scripts generate databases and tables
dotnet ef database update -c PersistedGrantDbContext
dotnet ef database update -c ConfigurationDbContext
Copy the code
6. Sample data store
Private void InitializeDatabase(IApplicationBuilder app) {using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope()) { serviceScope.ServiceProvider.GetService<PersistedGrantDbContext>().Database.Migrate(); var context = serviceScope.ServiceProvider.GetService<ConfigurationDbContext>(); context.Database.Migrate(); if (! context.Clients.Any()) { foreach (var client in Config.GetClients()) { context.Clients.Add(client.ToEntity()); } context.SaveChanges(); } if (! context.IdentityResources.Any()) { foreach (var resource in Config.Ids) { context.IdentityResources.Add(resource.ToEntity()); } context.SaveChanges(); } if (! context.ApiResources.Any()) { foreach (var resource in Config.GetApiResources()) { context.ApiResources.Add(resource.ToEntity()); } context.SaveChanges(); }}}Copy the code
6. Access from the client
Persistent user IdentityServer4 (data store)
conditions
1, the Identity
steps
1, install Microsoft Nuget. AspNetCore. Identity. EntityFrameworkCore
2. Add context classes
public class IdentityServerUserDbContext : IdentityDbContext<IdentityUser> { public IdentityServerUserDbContext(DbContextOptions<IdentityServerUserDbContext> options) : base(options) { } protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); }}Copy the code
3. Configure startup.cs file
services.AddDbContext<IdentityServerUserDbContext>(options => { options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")); }); AddIdentity<IdentityUser, IdentityRole > (options = > {/ / 1.2 Password complexity configuration options. Password. RequireDigit = true; options.Password.RequiredLength = 6; options.Password.RequiredUniqueChars = 1; options.Password.RequireLowercase = false; options.Password.RequireNonAlphanumeric = false; options.Password.RequireUppercase = false; }) .AddEntityFrameworkStores<IdentityServerUserDbContext>() .AddDefaultTokenProviders();Copy the code
4. Execute the migration script
dotnet ef migrations add IdentityServerUserDbMigration -c IdentityServerUserDbContext -o Data/Migrations/IdentityServer/IdentityServerUserDb
Copy the code
5. Migration scripts generate databases and tables
dotnet ef database update -c IdentityServerUserDbContext
Copy the code
6. Sample data store
Private void InitializeUserDatabase(IApplicationBuilder app) {using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope()) { var context = serviceScope.ServiceProvider.GetService<IdentityServerUserDbContext>(); context.Database.Migrate(); var userManager = serviceScope.ServiceProvider.GetRequiredService<UserManager<IdentityUser>>(); var idnetityUser = userManager.FindByNameAsync("tony").Result; if (idnetityUser == null) { idnetityUser = new IdentityUser { UserName = "zhangsan", Email = "[email protected]" }; var result = userManager.CreateAsync(idnetityUser, "123456").Result; if (! result.Succeeded) { throw new Exception(result.Errors.First().Description); } result = userManager.AddClaimsAsync(idnetityUser, new Claim[] { new Claim(JwtClaimTypes.Name, "tony"), new Claim(JwtClaimTypes.GivenName, "tony"), new Claim(JwtClaimTypes.FamilyName, "tony"), new Claim(JwtClaimTypes.Email, "[email protected]"), new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean), new Claim(JwtClaimTypes.WebSite, "http://tony.com") }).Result; if (! result.Succeeded) { throw new Exception(result.Errors.First().Description); }}}}Copy the code
6. AccountController Changes the Login code
public class AccountController : Controller { private UserManager<ApplicationUser> _userManager; private SignInManager<ApplicationUser> _signInManager; private IIdentityServerInteractionService _interaction; public AccountController(UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager, IIdentityServerInteractionService interaction) { _userManager = userManager; _signInManager = signInManager; _interaction = interaction; } public IActionResult Login(string returnUrl) { ViewData["ReturnUrl"] = returnUrl; return View(); } [HttpPost] public async Task<IActionResult> Login(LoginViewModel loginViewModel, string returnUrl) { // check if we are in the context of an authorization request var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); // the user clicked the "cancel" button if (button ! = "login") { if (context ! = null) { // if the user cancels, send a result back into IdentityServer as if they // denied the consent (even if this client does not require consent). // this will send back an access denied OIDC error response to the client. await _interaction.GrantConsentAsync(context, ConsentResponse.Denied); // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null if (await _clientStore.IsPkceClientAsync(context.ClientId)) { // if the client is PKCE then we assume it's native, so this change in how to // return the response is for better UX for the end user. return this.LoadingPage("Redirect", model.ReturnUrl); } return Redirect(model.ReturnUrl); } else { // since we don't have a valid context, then we just go back to the home page return Redirect("~/"); } } if (ModelState.IsValid) { var user = await _userManager.FindByNameAsync(model.Username); If (user == null) {modelstate.addModelError (nameof(model.username), $" Username {model.username} not exists"); } else { if (await _userManager.CheckPasswordAsync(user, model.Password)) { AuthenticationProperties props = null; if (model.RememberLogin) { props = new AuthenticationProperties { IsPersistent = true, ExpiresUtc = DateTimeOffset.UtcNow.Add(TimeSpan.FromMinutes(30)) }; } var isuser = new IdentityServerUser(user.Id) { DisplayName = user.UserName }; await HttpContext.SignInAsync(isuser, props); if (_interaction.IsValidReturnUrl(model.ReturnUrl)) { return Redirect(model.ReturnUrl); } return Redirect("~/"); } modelstate.addModelError (nameof(model.password), "Password error "); } } // something went wrong, show form with error var vm = await BuildLoginViewModelAsync(model); return View(vm); }}Copy the code
Ocelot integration IdentityServer4
conditions
1, IdentityServer4
steps
1, install IdentityServer4 Nuget. AccessTokenValidation
2. Configure IndentityServer4 in the startup file
/ / 1, configuration IdentityServer var authenticationProviderKey = "OcelotKey"; services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme) .AddIdentityServerAuthentication(options => { options.Authority = "http://localhost:5005"; Options. ApiName = "TeamService"; / / 2, name of the API (project name) options. RequireHttpsMetadata = false; Options.SupportedTokens = SupportedTokens.Both; });Copy the code
3. Configure authentication in Ocelot
"ReRoutes": [
{
"AuthenticationOptions": {
"AuthenticationProviderKey": "OcelotKey",
"AllowedScopes": []
}
}
]
Copy the code
Ocelot integrates IdentityServer4 configuration encapsulation
conditions
1, IdentityServerOptions class
steps
1. Create IdentityServerOptions class
/// </summary> public class IdentityServerOptions {/// </summary> /// authorization server address /// </summary> public int AuthorityAddress { get; set; } // <summary> // access_token type, /// </summary> Public string IdentityScheme {get; set; } /// <summary> // ResourceName, authentication service registered resource list name is the same, /// </summary> public string ResourceName {get; set; }}Copy the code
2. Configure IdentityServerOptions
Var identityServerOptions = new identityServerOptions (); Configuration.Bind("IdentityServerOptions", identityServerOptions); var authenticationProviderKey = "OcelotKey"; services.AddAuthentication(identityServerOptions.IdentityScheme) .AddIdentityServerAuthentication(options => { options.Authority = identityServerOptions.AuthorityAddress; / / 1, authorization center address options. ApiName = identityServerOptions. ResourceName; / / 2, name of the API (project name) options. RequireHttpsMetadata = false; Options.SupportedTokens = SupportedTokens.Both; });Copy the code
Ocelot dynamic routing
conditions
1, the consul
steps
1. Configure dynamic routes
{
"ReRoutes": [],
"Aggregates": [],
"GlobalConfiguration": {
"RequestIdKey": null,
"ServiceDiscoveryProvider": {
"Host": "localhost",
"Port": 8500,
"Type": "Consul",
"Token": null,
"ConfigurationKey": null
},
"RateLimitOptions": {
"ClientIdHeader": "ClientId",
"QuotaExceededMessage": null,
"RateLimitCounterPrefix": "ocelot",
"DisableRateLimitHeaders": false,
"HttpStatusCode": 429
},
"QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 0,
"DurationOfBreak": 0,
"TimeoutValue": 0
},
"BaseUrl": null,
"LoadBalancerOptions": {
"Type": "LeastConnection",
"Key": null,
"Expiry": 0
},
"DownstreamScheme": "https",
"HttpHandlerOptions": {
"AllowAutoRedirect": false,
"UseCookieContainer": false,
"UseTracing": false
}
}
}
Copy the code
2. Configure traffic limiting for dynamic routing services
{
"DynamicReRoutes": [
{
"ServiceName": "TeamService",
"RateLimitRule": {
"ClientWhitelist": [],
"EnableRateLimiting": true,
"Period": "1s",
"PeriodTimespan": 3000,
"Limit": 3
}
}
],
"GlobalConfiguration": {
"RequestIdKey": null,
"ServiceDiscoveryProvider": {
"Host": "localhost",
"Port": 8500,
"Type": "Consul"
},
"RateLimitOptions": {
"ClientIdHeader": "ClientId",
"QuotaExceededMessage": "",
"RateLimitCounterPrefix": "",
"DisableRateLimitHeaders": false,
"HttpStatusCode": 428
},
"DownstreamScheme": "http"
}
}
Copy the code
The specific code is as follows:
IdentityServer4 Authentication center
/// <summary>
///Identity Test use
/// </summary>
public class Config
{
/// <summary>
///1. Microservice API resources
/// </summary>
/// <returns></returns>
public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource("TeamService"."TeamService API needs to be protected".new List<string> {"role"."admin"})}; }/// <summary>
///2. Client
/// </summary>
/// <returns></returns>
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
new Client
{
ClientId = "client".// Without interactivity, use clientid/secret for authentication.
// 1. Client authentication mode
// 2. Client user password authentication mode
// 3. Authorization Code Authentication Mode
// Simple authentication mode (js)
AllowedGrantTypes = GrantTypes.ClientCredentials,
// Password for authentication
ClientSecrets =
{
new Secret("secret".Sha256())
},
// Scopes that the client has access to (Scopes)
AllowedScopes = { "TeamService"."MemberService"}},new Client
{
ClientId = "client-password".// Use the username and password for interactive authentication
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
// Password for authentication
ClientSecrets =
{
new Secret("secret".Sha256())
},
// Scopes that the client has access to (Scopes)
AllowedScopes = { "TeamService"}},// OpenID client
new Client
{
ClientId="client-code",
ClientSecrets={new Secret("secret".Sha256())},
AllowedGrantTypes=GrantTypes.Code,
RequireConsent=false,
RequirePkce=true,
RedirectUris={ "https://localhost:5006/signin-oidc"}, // 1. Client address
PostLogoutRedirectUris={ "https://localhost:5006/signout-callback-oidc"},// 2. Login and exit address
AllowedScopes=new List<string>{
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"TeamService" // Enable service authorization support
},
// Add authorized access
AllowOfflineAccess=true}}; }/// <summary>
///2.1 OpenID Identity Resources
/// </summary>
public static IEnumerable<IdentityResource> Ids => new List<IdentityResource>
{
new IdentityResources.OpenId(),
new IdentityResources.Profile()
};
/// <summary>
///3. Test users
/// </summary>
/// <returns></returns>
public static List<TestUser> GetUsers()
{
return new List<TestUser>()
{
new TestUser
{
SubjectId="1",
Username="tony",
Password="123456"
},
// OpenID authentication
new TestUser{SubjectId = "818727", Username = "tony-1", Password = "123456",
Claims =
{
new Claim(JwtClaimTypes.Name, "tony-1"),
new Claim(JwtClaimTypes.GivenName, "tony-1"),
new Claim(JwtClaimTypes.FamilyName, "tony-1"),
new Claim(JwtClaimTypes.Email, "[email protected]"),
new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean),
new Claim(JwtClaimTypes.WebSite, "http://tony-1.com"),
// new Claim(jwtclaimtypes. Address, @"{' city ': 'hangzhou ',' zip ': '310000' }", IdentityServer4.IdentityServerConstants.ClaimValueTypes.Json)}}}; }}Copy the code
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// add IdentityServer4 to ioc container
/ * services. AddIdentityServer (.) AddDeveloperSigningCredential () / / 1, the user login configuration .addinmemoryApiResources (config.getapiResources ()).addinMemoryClients (config.getClients ()) AddTestUsers (Config. GetUsers ()) / / 4, the client user. AddInMemoryIdentityResources (Config. Ids); // 5. Openid */
// 2, how to persist Config data to Sqlserver
// 2. Resource client persistence
var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
var connectionString = Configuration.GetConnectionString("DefaultConnection");
services.AddIdentityServer()
.AddConfigurationStore(options =>
{
options.ConfigureDbContext = builder =>
{
builder.UseSqlServer(connectionString, options =>
options.MigrationsAssembly(migrationsAssembly));
};
})
.AddOperationalStore(options =>
{
options.ConfigureDbContext = builder =>
{
builder.UseSqlServer(connectionString, options =>
options.MigrationsAssembly(migrationsAssembly));
};
})
.AddDeveloperSigningCredential();
// 2. User related configuration
services.AddDbContext<IdentityServerUserDbContext>(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
});
1.1 Adding a User
services.AddIdentity<IdentityUser, IdentityRole>(options => {
1.2 Password complexity Configuration
options.Password.RequireDigit = true;
options.Password.RequiredLength = 6;
options.Password.RequiredUniqueChars = 1;
options.Password.RequireLowercase = false;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
})
.AddEntityFrameworkStores<IdentityServerUserDbContext>()
.AddDefaultTokenProviders();
services.AddControllersWithViews();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// 1. Initialize data
InitializeDatabase(app);
// 2. Initialize user data
InitializeUserDatabase(app);
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
// 1、使用IdentityServe4
app.UseIdentityServer();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id? }");
});
}
// 1. Store the data in config
private void InitializeDatabase(IApplicationBuilder app)
{
using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
serviceScope.ServiceProvider.GetService<PersistedGrantDbContext>().Database.Migrate();
var context = serviceScope.ServiceProvider.GetService<ConfigurationDbContext>();
context.Database.Migrate();
if(! context.Clients.Any()) {foreach (var client in Config.GetClients())
{
context.Clients.Add(client.ToEntity());
}
context.SaveChanges();
}
if(! context.IdentityResources.Any()) {foreach (var resource in Config.Ids)
{
context.IdentityResources.Add(resource.ToEntity());
}
context.SaveChanges();
}
if(! context.ApiResources.Any()) {foreach (var resource inConfig.GetApiResources()) { context.ApiResources.Add(resource.ToEntity()); } context.SaveChanges(); }}}// 2. Store the user's data
private void InitializeUserDatabase(IApplicationBuilder app)
{
using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>().CreateScope())
{
var context = serviceScope.ServiceProvider.GetService<IdentityServerUserDbContext>();
context.Database.Migrate();
var userManager = serviceScope.ServiceProvider.GetRequiredService<UserManager<IdentityUser>>();
var idnetityUser = userManager.FindByNameAsync("tony").Result;
if (idnetityUser == null)
{
idnetityUser = new IdentityUser
{
UserName = "tony",
Email = "[email protected]"
};
var result = userManager.CreateAsync(idnetityUser, "123456").Result;
if(! result.Succeeded) {throw new Exception(result.Errors.First().Description);
}
result = userManager.AddClaimsAsync(idnetityUser, new Claim[] {
new Claim(JwtClaimTypes.Name, "tony"),
new Claim(JwtClaimTypes.GivenName, "tony"),
new Claim(JwtClaimTypes.FamilyName, "tony"),
new Claim(JwtClaimTypes.Email, "[email protected]"),
new Claim(JwtClaimTypes.EmailVerified, "true", ClaimValueTypes.Boolean),
new Claim(JwtClaimTypes.WebSite, "http://tony.com")
}).Result;
if(! result.Succeeded) {throw new Exception(result.Errors.First().Description);
}
}
}
}
}
Copy the code
The gateway layer
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
webBuilder.ConfigureAppConfiguration((hostingContext, config) =>
{
// Load the Ocelot configuration file
// config.AddJsonFile("ocelot.json");
config
.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath)
.AddJsonFile("appsettings.json".true.true)
.AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json".true.true)
.AddJsonFile("ocelot.json".true.true) // Dynamic routing configuration
// .AddOcelot(hostingContext.HostingEnvironment)
.AddEnvironmentVariables();
});
});
}
Copy the code
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
// 1
var identityServerOptions = new IdentityServerOptions();
Configuration.Bind("IdentityServerOptions", identityServerOptions);
services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(identityServerOptions.IdentityScheme, options =>
{
options.Authority = identityServerOptions.AuthorityAddress; // 1. Address of authorization center
options.ApiName = identityServerOptions.ResourceName; // 2. API name (project name)
options.RequireHttpsMetadata = false; // 3. HTTPS metadata is not required
});
// add gateway Ocelot to ioc container
services.AddOcelot().AddConsul().AddPolly();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// 2
app.UseOcelot((build, config) =>
{
build.BuildCustomeOcelotPipeline(config); 2.1 Customization of Ocelot middleware is complete
}).Wait();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/".async context =>
{
await context.Response.WriteAsync("Hello World!"); }); }); }}Copy the code
{
"ReRoutes": []."Aggregates": []."DynamicReRoutes": [{"ServiceName": "TeamService"."RateLimitRule": {
"ClientWhitelist": []."EnableRateLimiting": true."Period": "1s"."PeriodTimespan": 3000."Limit": 3}}]."GlobalConfiguration": {
"RequestIdKey": null."ServiceDiscoveryProvider": {
"Host": "localhost"."Port": 8500."Type": "Consul"."Token": null."ConfigurationKey": null
},
"RateLimitOptions": {
"ClientIdHeader": "ClientId"."QuotaExceededMessage": "3123123"."RateLimitCounterPrefix": "ocelot"."DisableRateLimitHeaders": false."HttpStatusCode": 429
},
"QoSOptions": {
"ExceptionsAllowedBeforeBreaking": 0."DurationOfBreak": 0."TimeoutValue": 0
},
"BaseUrl": null."LoadBalancerOptions": {
"Type": "LeastConnection"."Key": null."Expiry": 0
},
"DownstreamScheme": "https"."HttpHandlerOptions": {
"AllowAutoRedirect": false."UseCookieContainer": false."UseTracing": false
},
"AuthenticationOptions": {
"AuthenticationProviderKey": "OcelotKey"."AllowedScopes": ["memberservice"."teamserver"]}}}Copy the code
/// <summary>
///Register ocelot middleware
/// </summary>
public static class DemoOcelotExtension
{
public static IOcelotPipelineBuilder UseDemoResponseMiddleware(this IOcelotPipelineBuilder builder)
{
returnbuilder.UseMiddleware<DemoResponseMiddleware>(); }}Copy the code
/// <summary>
///Custom Ocelot middleware
/// </summary>
public class DemoResponseMiddleware : OcelotMiddleware
{
private readonly OcelotRequestDelegate _next;
public DemoResponseMiddleware(OcelotRequestDelegate next, IOcelotLoggerFactory loggerFactory)
: base(loggerFactory.CreateLogger<DemoResponseMiddleware>())
{
_next = next;
}
public async Task Invoke(DownstreamContext context)
{
if(! context.IsError && context.HttpContext.Request.Method.ToUpper() ! ="OPTIONS")
{
Console.WriteLine("Custom Middleware");
Console.WriteLine("Custom business logic processing");
// 1
// resutList resultMap
// 2
// 3. Perform link monitoring
// 4. Performance monitoring
// 5. Traffic statistics
}
else
{
await_next.Invoke(context); }}}Copy the code
public static class OcelotPipelineExtensions
{
public static OcelotRequestDelegate BuildCustomeOcelotPipeline(this IOcelotPipelineBuilder builder,
OcelotPipelineConfiguration pipelineConfiguration)
{
// This is registered to catch any global exceptions that are not handled
// It also sets the Request Id if anything is set globally
builder.UseExceptionHandlerMiddleware();
// If the request is for websockets upgrade we fork into a different pipeline
builder.MapWhen(context => context.HttpContext.WebSockets.IsWebSocketRequest,
app =>
{
app.UseDownstreamRouteFinderMiddleware();
app.UseDownstreamRequestInitialiser();
app.UseLoadBalancingMiddleware();
app.UseDownstreamUrlCreatorMiddleware();
app.UseWebSocketsProxyMiddleware();
});
// Allow the user to respond with absolutely anything they want.
builder.UseIfNotNull(pipelineConfiguration.PreErrorResponderMiddleware);
// This is registered first so it can catch any errors and issue an appropriate response
builder.UseResponderMiddleware();
// Then we get the downstream route information
builder.UseDownstreamRouteFinderMiddleware();
// This security module, IP whitelist blacklist, extended security mechanism
builder.UseSecurityMiddleware();
//Expand other branch pipes
if(pipelineConfiguration.MapWhenOcelotPipeline ! =null)
{
foreach (var pipeline inpipelineConfiguration.MapWhenOcelotPipeline) { builder.MapWhen(pipeline); }}// Now we have the ds route we can transform headers and stuff?
builder.UseHttpHeadersTransformationMiddleware();
// Initialises downstream request
builder.UseDownstreamRequestInitialiser();
// We check whether the request is ratelimit, and if there is no continue processing
builder.UseRateLimiting();
// This adds or updates the request id (initally we try and set this based on global config in the error handling middleware)
// If anything was set at global level and we have a different setting at re route level the global stuff will be overwritten
// This means you can get a scenario where you have a different request id from the first piece of middleware to the request id middleware.
builder.UseRequestIdMiddleware();
// Allow pre authentication logic. The idea being people might want to run something custom before what is built in.
builder.UseIfNotNull(pipelineConfiguration.PreAuthenticationMiddleware);
// Now we know where the client is going to go we can authenticate them.
// We allow the ocelot middleware to be overriden by whatever the
// user wants
if (pipelineConfiguration.AuthenticationMiddleware == null)
{
builder.UseAuthenticationMiddleware();
}
else
{
builder.Use(pipelineConfiguration.AuthenticationMiddleware);
}
// The next thing we do is look at any claims transforms in case this is important for authorisation
builder.UseClaimsToClaimsMiddleware();
// Allow pre authorisation logic. The idea being people might want to run something custom before what is built in.
builder.UseIfNotNull(pipelineConfiguration.PreAuthorisationMiddleware);
// Now we have authenticated and done any claims transformation we
// can authorise the request
// We allow the ocelot middleware to be overriden by whatever the
// user wants
if (pipelineConfiguration.AuthorisationMiddleware == null)
{
builder.UseAuthorisationMiddleware();
}
else
{
builder.Use(pipelineConfiguration.AuthorisationMiddleware);
}
// Now we can run the claims to headers transformation middleware
builder.UseClaimsToHeadersMiddleware();
// Allow the user to implement their own query string manipulation logic
builder.UseIfNotNull(pipelineConfiguration.PreQueryStringBuilderMiddleware);
// Now we can run any claims to query string transformation middleware
builder.UseClaimsToQueryStringMiddleware();
// Get the load balancer for this request
builder.UseLoadBalancingMiddleware();
// This takes the downstream route we retrieved earlier and replaces any placeholders with the variables that should be used
builder.UseDownstreamUrlCreatorMiddleware();
// Not sure if this is the best place for this but we use the downstream url
// as the basis for our cache key.
builder.UseOutputCacheMiddleware();
//We fire off the request and set the response on the scoped data repo
builder.UseHttpRequesterMiddleware();
// Add custom test middleware
builder.UseDemoResponseMiddleware();
return builder.Build();
}
private static void UseIfNotNull(this IOcelotPipelineBuilder builder,
Func<DownstreamContext, Func<Task>, Task> middleware)
{
if(middleware ! =null) { builder.Use(middleware); }}}Copy the code
/// <summary>
///Configuration options for IdentityServer
/// </summary>
public class IdentityServerOptions
{
/// <summary>
///Address of the authorization server
/// </summary>
public string AuthorityAddress { get; set; }
/// <summary>
///Type of access_token. The token_type value in the access_token parameter is the same
/// </summary>
public string IdentityScheme { get; set; }
/// <summary>
///The resource name is the same as the resource list registered by the authentication service.
/// </summary>
public string ResourceName { get; set; }}Copy the code
The end of the service
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// add authentication
// We use Cookies to log in users locally (through "Cookies" as DefaultScheme) and set DefaultChallengeScheme to OIDC. Because when we need users to log in, we will use the OpenID Connect protocol.
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc"; // openid connect
})
// Add a handler that can handle cookies
.AddCookie("Cookies")
// Used to configure the handler that executes the OpenID Connect protocol
.AddOpenIdConnect("oidc", options =>
{
// 1. Generate id_token
options.Authority = "http://localhost:5005"; // Address of trusted token service
options.RequireHttpsMetadata = false;
options.ClientId = "client-code";
options.ClientSecret = "secret";
options.ResponseType = "code";
options.SaveTokens = true; // Used to keep tokens from IdentityServer in cookies
// Add access_token to access_token API
options.Scope.Add("TeamService");
options.Scope.Add("offline_access");
});
services.AddControllersWithViews();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
// 1
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication(); // 1. Add authentication
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id? }").RequireAuthorization(); }); }}Copy the code
/// <summary>
///MVC client
/// </summary>
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}
public async Task<IActionResult> Index()
{
#regionToken mode
{
String access_token = await GetAccessToken(); access_token = await GetAccessToken(); access_token = await GetAccessToken(); String result = await UseAccessToken(access_token); // Add("Json", result); * /
}
#endregion
#regionOpenid connect agreement
{
// 1, obtain the token(id_token, access_token,refresh_token)
var accessToken = await HttpContext.GetTokenAsync("access_token"); // ("id_token")
Console.WriteLine($"accessToken:{accessToken}");
// var refreshToken =await HttpContext.GetTokenAsync("refresh_token");
var client = new HttpClient();
client.SetBearerToken(accessToken);
/*client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken); * /
// 2
var result = await client.GetStringAsync("https://localhost:5004/TeamService/teams");// ==== https://localhost:5001/teams
// 3. Return the result to the page
ViewData.Add("Json", result);
}
#endregion
return View();
}
/// <summary>
///1. Generate tokens
/// </summary>
/// <returns></returns>
public static async Task<string> GetAccessToken()
{
// Create a connection
HttpClient client = new HttpClient();
DiscoveryDocumentResponse disco = await client.GetDiscoveryDocumentAsync("http://localhost:5005");
if (disco.IsError)
{
Console.WriteLine($"[DiscoveryDocumentResponse Error]: {disco.Error}");
}
Access AccessToken from the client
/* TokenResponse tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest { AccessToken center ClientId = "client", Scope = "TeamService" Scope = "TeamService" Scope = "TeamService"}); * /
1.2 Obtaining AccessToken using the client user password
TokenResponse tokenResponse = await client.RequestPasswordTokenAsync(new PasswordTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = "client-password",
ClientSecret = "secret",
Scope = "TeamService",
UserName = "tony",
Password = "123456"
});
// 1.3 Obtaining AccessToken with Authorization Code [login required]
/* TokenResponse tokenResponse = await client.RequestAuthorizationCodeTokenAsync (new AuthorizationCodeTokenRequest { Address = disco.TokenEndpoint, ClientId = "client-code", ClientSecret = "secret", Code = "12", RedirectUri = "http://localhost:5005" }); * /
if (tokenResponse.IsError)
{
//ClientId and ClientSecret error: invalid_client
Scope error: invalid_scope
//UserName and Password error: invalid_grant
string errorDesc = tokenResponse.ErrorDescription;
if (string.IsNullOrEmpty(errorDesc)) errorDesc = "";
if (errorDesc.Equals("invalid_username_or_password"))
{
Console.WriteLine("Wrong username or password, please re-enter!");
}
else
{
Console.WriteLine($"[TokenResponse Error]: {tokenResponse.Error}, [TokenResponse Error Description]: {errorDesc}"); }}else
{
Console.WriteLine($"Access Token: {tokenResponse.Json}");
Console.WriteLine($"Access Token: {tokenResponse.RefreshToken}");
Console.WriteLine($"Access Token: {tokenResponse.ExpiresIn}");
}
return tokenResponse.AccessToken;
}
/// <summary>
///2. Use tokens
/// </summary>
public static async Task<string> UseAccessToken(string AccessToken)
{
HttpClient apiClient = new HttpClient();
apiClient.SetBearerToken(AccessToken); // set token to request header
HttpResponseMessage response = await apiClient.GetAsync("https://localhost:5001/teams");
if(! response.IsSuccessStatusCode) { Console.WriteLine($"API Request Error, StatusCode is : {response.StatusCode}");
}
else
{
string content = await response.Content.ReadAsStringAsync();
Console.WriteLine("");
Console.WriteLine($"Result: {JArray.Parse(content)}");
// 3. Output the result to the page
return JArray.Parse(content).ToString();
}
return "";
}
public IActionResult Privacy()
{
return View();
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
}
Copy the code
The service side
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// register the context to the IOC container
services.AddDbContext<TeamContext>(options => {
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
});
// 2
services.AddScoped<ITeamService, TeamServiceImpl>();
// 3. Register team storage
services.AddScoped<ITeamRepository, TeamRepository>();
// add a mapping
//services.AddAutoMapper();
// 5. Add a service registration condition
services.AddConsulRegistry(Configuration);
// 6. Verify AccessToken from the identity verification center
/*services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme) .AddIdentityServerAuthentication(options => { options.Authority = "http://localhost:5005"; Options. ApiName = "TeamService"; / / 2, name of the API (project name) options. RequireHttpsMetadata = false; }); * /
services.AddControllers();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
} // 1. Consul service registration
app.UseConsulRegistry();
app.UseRouting();
// app.UseAuthentication(); // enable authentication
app.UseAuthorization();// 2app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }}Copy the code
/// <summary>
///Team microservices API
/// </summary>
[Route("Teams")]
// [Authorize] // 1
[ApiController]
public class TeamsController : ControllerBase
{
private readonly ITeamService teamService;
public TeamsController(ITeamService teamService)
{
this.teamService = teamService;
}
// GET: api/Teams
[HttpGet]
public ActionResult<IEnumerable<Team>> GetTeams()
{
// Thread.Sleep(10000000);
// 1
Console.WriteLine($" Query team information");
return teamService.GetTeams().ToList();
}
// GET: api/Teams/5
[HttpGet("{id}")]
public ActionResult<Team> GetTeam(int id)
{
Team team = teamService.GetTeamById(id);
if (team == null)
{
return NotFound();
}
return team;
}
// PUT: api/Teams/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see https://aka.ms/RazorPagesCRUD.
[HttpPut("{id}")]
public IActionResult PutTeam(int id, Team team)
{
if(id ! = team.Id) {return BadRequest();
}
try
{
teamService.Update(team);
}
catch (DbUpdateConcurrencyException)
{
if(! teamService.TeamExists(id)) {return NotFound();
}
else
{
throw; }}return NoContent();
}
// POST: api/Teams
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see https://aka.ms/RazorPagesCRUD.
[HttpPost]
public ActionResult<Team> PostTeam(Team team)
{
teamService.Create(team);
return CreatedAtAction("GetTeam".new { id = team.Id }, team);
}
// DELETE: api/Teams/5
[HttpDelete("{id}")]
public ActionResult<Team> DeleteTeam(int id)
{
var team = teamService.GetTeamById(id);
if (team == null)
{
return NotFound();
}
teamService.Delete(team);
returnteam; }}Copy the code