asp.net – .NET Core WebAPI OpenIdDict(凭据流)和Angular2客户
我正在尝试使用OpenIdDict创建一个消耗.NET Core Web API的Angular2 SPA,其中包含凭据流.在为这个问题创建一个repro解决方案时,我还详细介绍了自述文件中的所有步骤,所以希望这篇文章对像我这样的新手有用.请在这些存储库中找到完整的repro解决方案:
>服务器端(.NET Core OpenIdDict),包含构建您自己的详细说明:https://github.com/Myrmex/repro-oidang 至于服务器端,我按照OpenIdDict提供的关于此流程的示例(https://github.com/openiddict/openiddict-samples/blob/master/samples/PasswordFlow).以下是Startup中最相关的位: public void ConfigureServices(IServiceCollection services) { services.AddCors(); services.AddEntityFrameworkSqlServer() .AddDbContext<CatalogContext>(options => options.UseSqlServer(Configuration.GetConnectionString("Catalog"))) .AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("Catalog"))); services.AddIdentity<ApplicationUser,ApplicationRole>() .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders(); services.AddOpenIddict<ApplicationDbContext>() .DisableHttpsRequirement() .EnableTokenEndpoint("/connect/token") .EnableLogoutEndpoint("/connect/logout") .EnableUserinfoEndpoint("/connect/userinfo") .AllowPasswordFlow() .AllowRefreshTokenFlow() .AddEphemeralSigningKey(); services.AddMvc() .AddJsonOptions(options => { options.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.CamelCasePropertyNamesContractResolver(); }); // add my services // ... services.AddTransient<IDatabaseInitializer,DatabaseInitializer>(); services.AddSwaggerGen(); } public void Configure(IApplicationBuilder app,IHostingEnvironment env,ILoggerFactory loggerFactory,IDatabaseInitializer databaseInitializer) { loggerFactory.AddConsole(Configuration.GetSection("Logging")); loggerFactory.AddDebug(); loggerFactory.AddNLog(); app.UseDefaultFiles(); app.UseStaticFiles(); app.UseCors(builder => builder.WithOrigins("http://localhost:4200") .AllowAnyHeader() .AllowAnyMethod()); app.USEOAuthValidation(); app.USEOpenIddict(); app.UseMvc(); databaseInitializer.Seed().GetAwaiter().GetResult(); env.ConfigureNLog("nlog.config"); app.UseSwagger(); app.UseSwaggerUi(); } 如果我用Fiddler测试它,它工作正常:令牌请求获取令牌,然后我可以将它包含在Authorization标头中以访问任何受保护的API,它按预期返回JSON数据. 示例令牌请求: POST http://localhost:51346/connect/token Content-Type: application/x-www-form-urlencoded grant_type=password&scope=offline_access profile email roles&resource=http://localhost:4200&username=...&password=... 样本资源请求: GET http://localhost:51346/api/values Content-Type: application/json Authorization: Bearer ...received token here... 然而,在客户端,每当我尝试相同的请求时,我都会收到401错误;看着日志,似乎Angular2 Http服务根本没有发送所需的头,因为我收到错误身份验证被跳过,因为没有收到持有者令牌(请参阅下面的更多日志条目). 检索一些资源的服务是这样的: import { Injectable } from '@angular/core'; import { Http,Response } from '@angular/http'; import { Observable } from 'rxjs/Observable'; import { SettingsService } from './settings.service'; import { AuthenticationService } from './authentication.service'; export interface ICategory { id: string; name: string; } @Injectable() export class CategoryService { constructor( private _http: Http,private _settings: SettingsService,private _authService: AuthenticationService) { } public getCategories(): Observable<ICategory[]> { let url = this._settings.apiBaseUrl + 'categories'; let options = { headers: this._authService.createAuthHeaders({ 'Content-Type': 'application/json' }) }; return this._http.get(url,options).map((res: Response) => res.json()) .catch((error: any) => Observable.throw(error.json().error || 'server error')); } } 帮助程序createAuthHeaders只获取表示Header(https://angular.io/docs/ts/latest/api/http/index/Headers-class.html)条目的某些属性,检索存储的标记,将Authentication条目附加到标题,然后返回: public createAuthHeaders(headers?: { [name: string]: any }): Headers { let auth = new Headers(); if (headers) { for (let key in headers) { if (headers.hasOwnProperty(key)) { auth.append(key,headers[key]); } } } let tokenResult = this._localStorage.retrieve(this._settings.tokenStorageKey,true); if (tokenResult) { auth.append('Authentication','Bearer ' + tokenResult.access_token); } return auth; } 然而,当尝试将响应映射到JSON对象(JSON输入的意外结束)时,此请求获得401响应然后Angular抛出. 我必须补充说,一旦客户端获得令牌,它就会发出另一个请求,以检索用户信息,这样可以正常工作;这是它(获取用户信息后的代码): public login(name: string,password: string) { let body = 'grant_type=password&scope=offline_access profile email roles' + `&resource=${this._settings.appBaseUrl}&username=${name}&password=${password}`; this._http.post( this._settings.authBaseUrl + `token`,body,{ headers: new Headers({ 'Content-Type': 'application/x-www-form-urlencoded' }) }).map(res => res.json()) .subscribe( (token: ITokenResult) => { if (token.expires_in) { token.expires_on = this.calculateExpirationDate(+token.expires_in); } this._localStorage.store(this._settings.tokenStorageKey,token,true); // get user info this._http.get(this._settings.authBaseUrl + 'userinfo',{ headers: new Headers({ 'Content-Type': 'application/json','Authorization': 'Bearer ' + token.access_token }) }).map(res => res.json()) .subscribe((info: IUserInfoResult) => { let user: IUser = { id: info.name,email: info.email,name: info.name,firstName: info.given_name,lastName: info.family_name,role: info.role,verified: info.email_verified }; this._localStorage.store(this._settings.userStorageKey,user,true); this.userChanged.emit(user); },error => { console.log(error); }); },error => { console.log(error); }); } 但是,使用上述服务构建的任何其他请求都会失败.使用引用函数构建的标题有什么问题? 以下是服务器端的一些日志条目: 2016-11-18 20:41:31.9815|0|AspNet.Security.OAuth.Validation.OAuthValidationMiddleware|DEBUG| Authentication was skipped because no bearer token was received. 2016-11-18 20:41:31.9815|0|OpenIddict.Infrastructure.OpenIddictProvider|INFO| The token request validation process was skipped because the client_id parameter was missing or empty. 2016-11-18 20:41:32.0715|0|AspNet.Security.OpenIdConnect.Server.OpenIdConnectServerMiddleware|INFO| No explicit audience was associated with the access token. 2016-11-18 20:41:32.1165|10|AspNet.Security.OpenIdConnect.Server.OpenIdConnectServerMiddleware|INFO| AuthenticationScheme: ASOS signed in. 2016-11-18 20:41:32.1635|3|AspNet.Security.OAuth.Validation.OAuthValidationMiddleware|INFO| HttpContext.User merged via AutomaticAuthentication from authenticationScheme: Bearer. 2016-11-18 20:41:57.7430|0|AspNet.Security.OAuth.Validation.OAuthValidationMiddleware|DEBUG| Authentication was skipped because no bearer token was received. 2016-11-18 20:41:57.7430|0|AspNet.Security.OAuth.Validation.OAuthValidationMiddleware|DEBUG| Authentication was skipped because no bearer token was received. 2016-11-18 20:41:57.8820|12|AspNet.Security.OAuth.Validation.OAuthValidationMiddleware|INFO| AuthenticationScheme: Bearer was challenged. 2016-11-18 20:41:57.9305|12|AspNet.Security.OAuth.Validation.OAuthValidationMiddleware|INFO| AuthenticationScheme: Bearer was challenged. 2016-11-18 20:41:57.9465|0|AspNet.Security.OAuth.Validation.OAuthValidationMiddleware|DEBUG| Authentication was skipped because no bearer token was received. 2016-11-18 20:41:57.9925|12|AspNet.Security.OAuth.Validation.OAuthValidationMiddleware|INFO| AuthenticationScheme: Bearer was challenged. 解决方法
您的不记名令牌使用不正确.
auth.append('Authentication','Bearer ' + tokenResult.access_token) // wrong auth.append('Authorization','Bearer ' + tokenResult.access_token) // right 标题需要是授权.见https://tools.ietf.org/html/rfc6750#section-2.1 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
- ASP.NET:TextBox.Text没有更新的值
- asp.net – JS / CSS包括部分替换,Debug vs Release
- asp.net-core – 在程序集’ef’上找不到’UserSecretsIdAt
- asp.net – 依赖注入:在aspx页面中注入用户控件
- asp.net – 如何将模型从一个局部视图传递到另一个局部视图
- iis-7 – 捆绑不适用于映射的虚拟目录
- asp.net-core – Asp.net核心身份使用AspNetUserClaims或As
- asp.net – 回滚请求身体流
- asp.net-mvc-3 – 使用Magical Unicorn的ASP.NET MVC自定义
- asp.net mvc – asp.net mvc decorate [Authorize()]与多个
- ASP.NET Verses Winforms
- asp.net – 在GridViewTemplate字段TextBox中对齐
- asp.net-mvc – MVC – Asp.Net Identity. HOWTO
- asp.net – 如何在dotnetnuke网站上为global.asc
- asp.net-mvc – 不能删除数据库,因为它目前正在使
- asp.net-mvc – 什么时候项目在MVC架构中有自己的
- asp.net-mvc-4 – 如何在Jquery DataTable中获取
- asp.net-mvc – 会员提供商中的GetAllUsers在哪里
- asp.net – SignalR如何处理重复的连接ID?
- asp.net-mvc-3 – @ Html.ActionLink和@ Html.Di