Проблема с токеном защиты от подделки (MVC 5)

122

У меня проблема с токеном защиты от подделки :( Я создал свой собственный класс User, который работал нормально, но теперь я получаю сообщение об ошибке всякий раз, когда перехожу на страницу / Account / Register . Ошибка:

Заявление типа http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier или http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider было отсутствует в предоставленных ClaimsIdentity. Чтобы включить поддержку токенов защиты от подделки с проверкой подлинности на основе утверждений, убедитесь, что настроенный поставщик утверждений предоставляет оба этих утверждения для создаваемых им экземпляров ClaimsIdentity. Если настроенный поставщик утверждений вместо этого использует другой тип утверждения в качестве уникального идентификатора, его можно настроить, задав статическое свойство AntiForgeryConfig.UniqueClaimTypeIdentifier.

Я нашел эту статью:

http://stack247.wordpress.com/2013/02/22/antiforgerytoken-a-claim-of-type-nameidentifier-or-identityprovider-was-not-present-on-provided-claimsidentity/

поэтому я изменил свой метод Application_Start на это:

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();

    FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
    RouteConfig.RegisterRoutes(RouteTable.Routes);
    BundleConfig.RegisterBundles(BundleTable.Bundles);

    AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.Email;
}

но когда я это сделаю, я получаю эту ошибку:

Требование типа " http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress " отсутствовало в предоставленном ClaimsIdentity.

Кто-нибудь сталкивался с этим раньше? Если да, то знаете, как ее решить?

Приветствую заранее,
r3plica

Обновление 1

Вот мой пользовательский класс:

public class Profile : User, IProfile
{
    public Profile()
        : base()
    {
        this.LastLoginDate = DateTime.UtcNow;
        this.DateCreated = DateTime.UtcNow;
    }

    public Profile(string userName)
        : base(userName)
    {
        this.CreatedBy = this.Id;

        this.LastLoginDate = DateTime.UtcNow;
        this.DateCreated = DateTime.UtcNow;

        this.IsApproved = true;
    }

    [NotMapped]
    public HttpPostedFileBase File { get; set; }

    [Required]
    public string CompanyId { get; set; }

    [Required]
    public string CreatedBy { get; set; }
    public string ModifiedBy { get; set; }

    public DateTime DateCreated { get; set; }
    public DateTime? DateModified { get; set; }
    public DateTime LastLoginDate { get; set; }

    [Required(ErrorMessageResourceType = typeof(Resources.Resources), ErrorMessageResourceName = "RequiredTitle")]
    public string Title { get; set; }
    [Required(ErrorMessageResourceType = typeof(Resources.Resources), ErrorMessageResourceName = "RequiredFirstName")]
    public string Forename { get; set; }
    [Required(ErrorMessageResourceType = typeof(Resources.Resources), ErrorMessageResourceName = "RequiredLastName")]
    public string Surname { get; set; }

    [Required(ErrorMessageResourceType = typeof(Resources.Resources), ErrorMessageResourceName = "RequiredEmail")]
    public string Email { get; set; }
    public string JobTitle { get; set; }
    public string Telephone { get; set; }
    public string Mobile { get; set; }
    public string Photo { get; set; }
    public string LinkedIn { get; set; }
    public string Twitter { get; set; }
    public string Facebook { get; set; }
    public string Google { get; set; }
    public string Bio { get; set; }

    public string CompanyName { get; set; }

    [Required(ErrorMessageResourceType = typeof(Resources.Resources), ErrorMessageResourceName = "RequiredCredentialId")]
    public string CredentialId { get; set; }
    [Required(ErrorMessageResourceType = typeof(Resources.Resources), ErrorMessageResourceName = "RequiredSecurityCode")]
    public bool IsLockedOut { get; set; }
    public bool IsApproved { get; set; }

    [Display(Name = "Can only edit own assets")]
    public bool CanEditOwn { get; set; }
    [Display(Name = "Can edit assets")]
    public bool CanEdit { get; set; }
    [Display(Name = "Can download assets")]
    public bool CanDownload { get; set; }
    [Display(Name = "Require approval to upload assets")]
    public bool RequiresApproval { get; set; }
    [Display(Name = "Can approve assets")]
    public bool CanApprove { get; set; }
    [Display(Name = "Can synchronise assets")]
    public bool CanSync { get; set; }

    public bool AgreedTerms { get; set; }
    public bool Deleted { get; set; }
}

public class ProfileContext : IdentityStoreContext
{
    public ProfileContext(DbContext db)
        : base(db)
    {
        this.Users = new UserStore<Profile>(this.DbContext);
    }
}

public class ProfileDbContext : IdentityDbContext<Profile, UserClaim, UserSecret, UserLogin, Role, UserRole>
{
}

Профиль I просто для моих репозиториев выглядит так:

public interface IProfile
{
    string Id { get; set; }
    string CompanyId { get; set; }

    string UserName { get; set; }
    string Email { get; set; }

    string CredentialId { get; set; }
}

а класс User - это класс Microsoft.AspNet.Identity.EntityFramework.User . Мой AccountController выглядит так:

[Authorize]
public class AccountController : Controller
{
    public IdentityStoreManager IdentityStore { get; private set; }
    public IdentityAuthenticationManager AuthenticationManager { get; private set; }

    public AccountController() 
    {
        this.IdentityStore = new IdentityStoreManager(new ProfileContext(new ProfileDbContext()));
        this.AuthenticationManager = new IdentityAuthenticationManager(this.IdentityStore);
    }

    //
    // GET: /Account/Register
    [AllowAnonymous]
    public ActionResult Register()
    {
        return View();
    }

    //
    // POST: /Account/Register
    [HttpPost]
    [AllowAnonymous]
    public async Task<ActionResult> Register(RegisterViewModel model)
    {
        if (ModelState.IsValid)
        {
            try
            {
                // Create a profile, password, and link the local login before signing in the user
                var companyId = Guid.NewGuid().ToString();
                var user = new Profile(model.UserName)
                {
                    CompanyId = companyId,
                    Title = model.Title,
                    Forename = model.Forename,
                    Surname = model.Surname,
                    Email = model.Email,
                    CompanyName = model.CompanyName,
                    CredentialId = model.CredentialId
                };

                if (await IdentityStore.CreateLocalUser(user, model.Password))
                {
                    //Create our company
                    var company = new Skipstone.Web.Models.Company()
                    {
                        Id = companyId,
                        CreatedBy = user.Id,
                        ModifiedBy = user.Id,
                        Name = model.CompanyName
                    };

                    using (var service = new CompanyService())
                    {
                        service.Save(company);
                    }

                    await AuthenticationManager.SignIn(HttpContext, user.Id, isPersistent: false);
                    return RedirectToAction("Setup", new { id = companyId });
                }
                else
                {
                    ModelState.AddModelError("", "Failed to register user name: " + model.UserName);
                }
            }
            catch (IdentityException e)
            {
                ModelState.AddModelError("", e.Message);
            }
        }

        // If we got this far, something failed, redisplay form
        return View(model);
    }

    //
    // POST: /Account/Setup
    public ActionResult Setup(string id)
    {
        var userId = User.Identity.GetUserId();
        using (var service = new CompanyService())
        {
            var company = service.Get(id);
            var profile = new Profile()
            {
                Id = userId,
                CompanyId = id
            };

            service.Setup(profile);

            return View(company);
        }
    }
}

Раньше его украшал атрибут [ValidateAntiForgeryToken] , но именно здесь он перестал работать.

Надеюсь, кода хватит :)

r3plica
источник
Можете ли вы показать нам собственный класс User и как вы его использовали?
LostInComputer
Я добавил собственный класс User, а также то, как я его использую.
r3plica
Вы используете бета-версию. Я предлагаю вам перейти на версию выпуска, а затем посмотреть, сохраняется ли проблема.
LostInComputer

Ответы:

230

Попробуйте установить (в global.cs):

AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier;
Алекс Филиповичи
источник
33
Я думаю, важно отметить, почему это работает: это говорит AntiForgeryклассу использовать NameIdentifier(это строка идентификатора пользователя, найденная GetUserId). Спасибо Майку Гудвину, который помог мне узнать это!
Matt DeKrey
Я пробовал AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.Name; и получил эту ошибку «Последовательность содержит более одного совпадающего элемента», в моем случае есть несколько утверждений (имя, роль и адрес электронной почты). Как я могу в этом разобраться?
Dhanuka777
9
Я установил это в Global.asax.cs
Майк Таверн
6
Это также решение, если вы используете OpenId (например, Azure ActiveDirectory) в качестве вашей аутентификации.
Guyherman
6
Полные пространства имен ... Мне пришлось немного покопаться, чтобы узнать, где находится ClaimTypes. System.Web.Helpers.AntiForgeryConfig.UniqueClaimTypeIdentifier = System.Security.Claims.ClaimTypes.NameIdentifier;
Марк Роу
65

Вы знаете, какие претензии вы получаете в своем ClaimsIdentity? Если не:

  1. Удалить [ValidateAntiForgeryToken]атрибут
  2. Поместите точку останова где-нибудь в вашем контроллере и сломайте ее
  3. Затем посмотрите на текущий ClaimsIdentityи изучите претензии
  4. Найдите тот, который, по вашему мнению, однозначно идентифицирует вашего пользователя
  5. Установите AntiForgeryConfig.UniqueClaimTypeIdentifierдля этого типа утверждения
  6. Верните [ValidateAntiForgeryToken]атрибут
Майк Гудвин
источник
3
Это больше, чем просто ответ на вопрос о кормлении с ложки, он рассказывает предысторию и дает возможность самопознания. :) Большое спасибо
NitinSingh
2
6. [ValidateAntiForgeryToken]
Скотт Фрэйли
1
это действительно помогло мне. Оказалось, что у меня есть претензия к другому приложению, работающему на моем локальном хосте, в моем приложении, где претензии не используются (поэтому претензии кажутся мне странными). Поэтому, когда я вышел из другого приложения, претензии исчезли, как и ошибка. В среде live-test эти сайты более разделены. Поэтому я думаю, что мне нужно вышеупомянутое решение, но только для локальной разработки.
Мишель
26

Просто поместите это в global.asax.cs

AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimsIdentity.DefaultNameClaimType;
МЖК
источник
Спасибо. Я не понимаю, почему мне пришлось внести это изменение, я исправил несколько разных проблем, которые возникли с моим кодом вчера вечером, и все работало нормально. Ничего не меняя, я протестировал это на другой машине, и все это работало всего несколько минут назад.
Artorias2718
14

Попробуйте открыть ссылку в окне инкогнито или удалить cookie из этого домена (например, localhost).

Гурген Саркисян
источник
Почему это работает и в чем причина проблемы?
mok
Это работает, потому что, когда у вас есть файл cookie сеанса с недопустимым идентификатором имени, сервер пытается использовать недопустимый идентификатор без перенаправления пользователя на страницу входа и получения правильного идентификатора имени.
Rawel
3

Изменить: Имея лучшее понимание этой проблемы на данный момент, вы можете проигнорировать мой ответ ниже.

Установка AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier;в Application_Start () Global.asax.cs исправила это для меня. Несмотря на то, что у меня есть заявка http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier, я получаю ту же ошибку, что и в исходном вопросе. Но указание на это, как указано выше, как-то работает.



Начиная с MVC4, токен защиты от подделки не используется User.Identity.Nameв качестве уникального идентификатора. Вместо этого он ищет два утверждения, указанные в сообщении об ошибке.

ПРИМЕЧАНИЕ об обновлении: в этом нет необходимости. Вы можете добавить недостающие утверждения в свой ClaimsIdentity, когда пользователь входит в систему, например:

string userId = TODO;
var identity = System.Web.HttpContext.Current.User.Identity as ClaimsIdentity;
identity.AddClaim(new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider", userId));
identity.AddClaim(new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", userId));

Обратите внимание, что одна из претензий уже могла быть там раньше, и вы получите сообщение об ошибке с повторяющимися претензиями, если добавите обе. Если да, просто добавьте недостающий.

cederlof
источник
1
Я понимаю, почему вы используете userId как «/ nameidentifier», но почему вы вводите userId как «/ identityprovider»?
AaronLS
2

В Global.asax.cs,

1. Добавьте эти пространства имен

using System.Web.Helpers;
using System.Security.Claims;

2. Добавьте эту строку в метод Application_Start:

 protected void Application_Start()
 {
       .......
       AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimsIdentity.DefaultNameClaimType;
 } 
Киран Чаудхари
источник
Как это добавляет больше ценности, чем те, что были
указаны
Спасибо за добавление использования. @NitinSingh Я думаю, что это добавляет больше ценности, потому что я не знал, какое из трех потенциальных пространств имен в моем проекте использовать.
Keisha W
Каждый раз, когда вы добавляете новую функцию, она запрашивает правильные ссылки. После компиляции вы должны удалить неиспользуемые через меню Refactor, щелкнув правой кнопкой мыши
NitinSingh
0
AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.Email;

работает в моем случае, я использую аутентификацию ADFS.

Ашу
источник