Add a sample demonstrating Antiforgery with AJAX

This commit is contained in:
Ryan Nowak 2015-12-15 13:49:32 -08:00
Родитель bf6406bc2a
Коммит 1c0996c625
14 изменённых файлов: 241 добавлений и 87 удалений

Просмотреть файл

@ -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;
}
}