Add a sample demonstrating Antiforgery with AJAX
This commit is contained in:
Родитель
bf6406bc2a
Коммит
1c0996c625
|
@ -1,66 +0,0 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Antiforgery;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.Extensions.OptionsModel;
|
||||
|
||||
namespace AntiforgerySample
|
||||
{
|
||||
public class FormPostSampleMiddleware
|
||||
{
|
||||
private readonly IAntiforgery _antiforgery;
|
||||
private readonly AntiforgeryOptions _options;
|
||||
private readonly RequestDelegate _next;
|
||||
|
||||
public FormPostSampleMiddleware(
|
||||
RequestDelegate next,
|
||||
IAntiforgery antiforgery,
|
||||
IOptions<AntiforgeryOptions> options)
|
||||
{
|
||||
_next = next;
|
||||
_antiforgery = antiforgery;
|
||||
_options = options.Value;
|
||||
}
|
||||
|
||||
public async Task Invoke(HttpContext context)
|
||||
{
|
||||
if (context.Request.Method == "GET")
|
||||
{
|
||||
var page =
|
||||
@"<html>
|
||||
<body>
|
||||
<form action=""/"" method=""post"">
|
||||
<input type=""text"" name=""{0}"" value=""{1}""/>
|
||||
<input type=""submit"" />
|
||||
</form>
|
||||
</body>
|
||||
</html>";
|
||||
|
||||
var tokenSet = _antiforgery.GetAndStoreTokens(context);
|
||||
await context.Response.WriteAsync(string.Format(page, _options.FormFieldName, tokenSet.RequestToken));
|
||||
}
|
||||
else if (context.Request.Method == "POST")
|
||||
{
|
||||
// This will throw if invalid.
|
||||
await _antiforgery.ValidateRequestAsync(context);
|
||||
|
||||
var page =
|
||||
@"<html>
|
||||
<body>
|
||||
<h1>Everything is fine</h1>
|
||||
<h2><a href=""/"">Try Again</a></h2>
|
||||
</form>
|
||||
</body>
|
||||
</html>";
|
||||
await context.Response.WriteAsync(page);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _next(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +1,15 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.AspNet.Antiforgery;
|
||||
using Microsoft.AspNet.Builder;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Routing;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.OptionsModel;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace AntiforgerySample
|
||||
{
|
||||
|
@ -10,13 +17,57 @@ namespace AntiforgerySample
|
|||
{
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddAntiforgery();
|
||||
services.AddRouting();
|
||||
|
||||
// Angular's default header name for sending the XSRF token.
|
||||
services.AddAntiforgery(options => options.HeaderName = "X-XSRF-TOKEN");
|
||||
|
||||
services.AddSingleton<TodoRepository>();
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app)
|
||||
public void Configure(IApplicationBuilder app, IAntiforgery antiforgery, IOptions<AntiforgeryOptions> options, TodoRepository repository)
|
||||
{
|
||||
app.Use(next => context =>
|
||||
{
|
||||
if (
|
||||
string.Equals(context.Request.Path.Value, "/", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(context.Request.Path.Value, "/index.html", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// We can send the request token as a JavaScript-readable cookie, and Angular will use it by default.
|
||||
var tokens = antiforgery.GetAndStoreTokens(context);
|
||||
context.Response.Cookies.Append("XSRF-TOKEN", tokens.RequestToken, new CookieOptions() { HttpOnly = false });
|
||||
}
|
||||
|
||||
return next(context);
|
||||
});
|
||||
|
||||
app.UseDefaultFiles();
|
||||
app.UseStaticFiles();
|
||||
app.UseMiddleware<FormPostSampleMiddleware>();
|
||||
|
||||
var routes = new RouteBuilder(app);
|
||||
|
||||
routes.MapGet("api/items", (HttpContext context) =>
|
||||
{
|
||||
var items = repository.GetItems();
|
||||
return context.Response.WriteAsync(JsonConvert.SerializeObject(items));
|
||||
});
|
||||
|
||||
routes.MapPost("api/items", async (HttpContext context) =>
|
||||
{
|
||||
// This will throw if the token is invalid.
|
||||
await antiforgery.ValidateRequestAsync(context);
|
||||
|
||||
var serializer = new JsonSerializer();
|
||||
using (var reader = new JsonTextReader(new StreamReader(context.Request.Body)))
|
||||
{
|
||||
var item = serializer.Deserialize<TodoItem>(reader);
|
||||
repository.Add(item);
|
||||
}
|
||||
|
||||
context.Response.StatusCode = 204;
|
||||
});
|
||||
|
||||
app.UseRouter(routes.Build());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace AntiforgerySample
|
||||
{
|
||||
public class TodoItem
|
||||
{
|
||||
[JsonProperty(PropertyName = "name")]
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace AntiforgerySample
|
||||
{
|
||||
public class TodoRepository
|
||||
{
|
||||
private List<TodoItem> _items;
|
||||
|
||||
public TodoRepository()
|
||||
{
|
||||
_items = new List<TodoItem>()
|
||||
{
|
||||
new TodoItem() { Name = "Mow the lawn" },
|
||||
new TodoItem() { Name = "Do the dishes" },
|
||||
};
|
||||
}
|
||||
|
||||
public IEnumerable<TodoItem> GetItems()
|
||||
{
|
||||
return _items;
|
||||
}
|
||||
|
||||
public void Add(TodoItem item)
|
||||
{
|
||||
_items.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "ASP.NET",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"angular": "~1.4.8",
|
||||
"bootstrap-css": "~3.3.4"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
/// <binding Clean='clean' />
|
||||
"use strict";
|
||||
|
||||
var gulp = require("gulp"),
|
||||
bowerFiles = require('main-bower-files');
|
||||
|
||||
var paths = {
|
||||
webroot: "./wwwroot/"
|
||||
};
|
||||
|
||||
paths.bowerFilesDest = paths.webroot + '/bower_components';
|
||||
|
||||
gulp.task("copy:bower", function () {
|
||||
return gulp.src(bowerFiles()).pipe(gulp.dest(paths.bowerFilesDest));
|
||||
});
|
||||
|
||||
gulp.task("default", ["copy:bower"]);
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"name": "ASP.NET",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"devDependencies": {
|
||||
"gulp": "^3.9.0",
|
||||
"gulp-concat": "^2.6.0",
|
||||
"gulp-cssmin": "^0.1.7",
|
||||
"gulp-uglify": "^1.5.1",
|
||||
"main-bower-files": "^2.9.0",
|
||||
"rimraf": "^2.4.4"
|
||||
}
|
||||
}
|
|
@ -1,15 +1,17 @@
|
|||
{
|
||||
{
|
||||
"webroot": "wwwroot",
|
||||
"version": "1.0.0-*",
|
||||
|
||||
"dependencies": {
|
||||
"Microsoft.AspNet.Antiforgery": "1.0.0-*",
|
||||
"Microsoft.AspNet.Http.Extensions": "1.0.0-*",
|
||||
"Microsoft.AspNet.Server.IIS": "1.0.0-*",
|
||||
"Microsoft.AspNet.Server.Kestrel": "1.0.0-*",
|
||||
"Microsoft.AspNet.Server.WebListener": "1.0.0-*",
|
||||
"Microsoft.AspNet.StaticFiles": "1.0.0-*"
|
||||
},
|
||||
"dependencies": {
|
||||
"Microsoft.AspNet.Antiforgery": "1.0.0-*",
|
||||
"Microsoft.AspNet.Http.Abstractions": "1.0.0-rc2-16062",
|
||||
"Microsoft.AspNet.Http.Extensions": "1.0.0-*",
|
||||
"Microsoft.AspNet.Routing.Extensions": "1.0.0-*",
|
||||
"Microsoft.AspNet.Server.Kestrel": "1.0.0-*",
|
||||
"Microsoft.AspNet.Server.WebListener": "1.0.0-*",
|
||||
"Microsoft.AspNet.StaticFiles": "1.0.0-*",
|
||||
"Newtonsoft.Json": "7.0.1"
|
||||
},
|
||||
|
||||
"commands": {
|
||||
"kestrel": "Microsoft.AspNet.Hosting --server Microsoft.AspNet.Server.Kestrel --server.urls http://localhost:5000",
|
||||
|
|
|
@ -1,10 +1,47 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta charset="utf-8" />
|
||||
<title>Antiforgery Sample</title>
|
||||
<title>Todo List Antiforgery Sample</title>
|
||||
<link rel="stylesheet" href="bower_components/bootstrap.min.css" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello, World!</h1>
|
||||
<body ng-app="TODO" ng-controller="todoController">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<h1>Todo List Antiforgery Sample</h1>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr><th colspan="2">TODO List</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="item in itemList">
|
||||
<td>{{$index + 1}}</td>
|
||||
<td>
|
||||
{{item.name}}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<form class="form-inline">
|
||||
<div class="form-group">
|
||||
<label for="name">New Item:</label>
|
||||
<input type="text" class="form-control" id="name" placeholder="(Enter Todo Item)" ng-model="item.name">
|
||||
</div>
|
||||
<button class="btn btn-default" value="Create" ng-click="create(item)">Create</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<script src="bower_components/angular.js"></script>
|
||||
<script src="app.js"></script>
|
||||
<script src="services.js"></script>
|
||||
<script src="controllers.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,4 @@
|
|||
angular.module('TODO', [
|
||||
'TODO.controllers',
|
||||
'TODO.services'
|
||||
]);
|
|
@ -0,0 +1,21 @@
|
|||
angular.module('TODO.controllers', []).
|
||||
controller('todoController', function ($scope, todoApi) {
|
||||
$scope.itemList = [];
|
||||
$scope.item = {};
|
||||
|
||||
$scope.refresh = function (item) {
|
||||
todoApi.getItems().success(function (response) {
|
||||
$scope.itemList = response;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.create = function (item) {
|
||||
todoApi.create(item).success(function (response) {
|
||||
$scope.item = {};
|
||||
$scope.refresh();
|
||||
});
|
||||
};
|
||||
|
||||
// Load initial items
|
||||
$scope.refresh();
|
||||
});
|
|
@ -1 +0,0 @@
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
angular.module('TODO.services', []).
|
||||
factory('todoApi', function ($http) {
|
||||
|
||||
var todoApi = {};
|
||||
|
||||
todoApi.getItems = function () {
|
||||
return $http({
|
||||
method: 'GET',
|
||||
url: '/api/items'
|
||||
});
|
||||
}
|
||||
|
||||
todoApi.create = function (item) {
|
||||
return $http({
|
||||
method: 'POST',
|
||||
url: '/api/items',
|
||||
data: item
|
||||
});
|
||||
};
|
||||
|
||||
return todoApi;
|
||||
});
|
|
@ -33,7 +33,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
return services;
|
||||
}
|
||||
|
||||
public static IServiceCollection ConfigureAntiforgery(
|
||||
public static IServiceCollection AddAntiforgery(
|
||||
this IServiceCollection services,
|
||||
Action<AntiforgeryOptions> setupAction)
|
||||
{
|
||||
|
@ -42,12 +42,13 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||
throw new ArgumentNullException(nameof(services));
|
||||
}
|
||||
|
||||
if (setupAction == null)
|
||||
services.AddAntiforgery();
|
||||
|
||||
if (setupAction != null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(setupAction));
|
||||
services.Configure(setupAction);
|
||||
}
|
||||
|
||||
services.Configure(setupAction);
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче