问题
I have an issue with public access in Umbraco 7.
I use a custom membership provider to authenticate the users by my CRM database. I set a rule to let access to authenticated (front-end) users only and I used a custom role provider to define authenticated users have the visitors role. If they are not authenticated, they are redirected to the login page.
When I debug the website the user has the role :
I am authenticated and the role for the current user is the good one.
But I am still redirected to the login page ! I don't understand.
My role provider:
public class CustomRoleProvider : Umbraco.Web.Security.Providers.MembersRoleProvider
{
const int SITE_ID = 6;
public override string ApplicationName
{
get
{
return "Site";
}
}
public override string[] GetAllRoles()
{
return new[] { Const.VISITORS_LABEL };
}
public override string[] GetRolesForUser(string username)
{
return new[] { Const.VISITORS_LABEL };
}
/// <summary>
///
/// </summary>
/// <param name="username"></param>
/// <param name="roleName"></param>
/// <returns></returns>
public override bool IsUserInRole(string username, string roleName)
{
//every user is a visitor
if(roleName == Const.VISITORS_LABEL)
{
return true;
}
else
{
return base.IsUserInRole(username, roleName);
}
}
public override string[] GetUsersInRole(string roleName)
{
if(roleName == Const.VISITORS_LABEL)
{
using (var db = new CRMEntities())
{
var usersEmails = db.Customer_View.Where(x => x.SiteID == SITE_ID).Select(x=>x.Email).ToArray();
return usersEmails;
}
}
else
{
return base.GetUsersInRole(roleName);
}
}
}
Here is the controller I use for authentication:
public class MemberLoginSurfaceController : Umbraco.Web.Mvc.SurfaceController
{
// The MemberLogin Action returns the view, which we will create later. It also instantiates a new, empty model for our view:
[HttpGet]
[ActionName("MemberLogin")]
public ActionResult MemberLoginGet()
{
return PartialView("MemberLogin", new MemberLoginModel());
}
// The MemberLogout Action signs out the user and redirects to the site home page:
[HttpGet]
public ActionResult MemberLogout()
{
Session.Clear();
FormsAuthentication.SignOut();
return Redirect("/");
}
// The MemberLoginPost Action checks the entered credentials using the standard Asp Net membership provider and redirects the user to the same page. Either as logged in, or with a message set in the TempData dictionary:
[HttpPost]
[ActionName("MemberLogin")]
public ActionResult MemberLoginPost(MemberLoginModel model)
{
if (Membership.ValidateUser(model.Username, model.Password))
{
FormsAuthentication.SetAuthCookie(model.Username, model.RememberMe);
return RedirectToCurrentUmbracoPage();
}
else
{
TempData["Status"] = "Invalid username or password";
return RedirectToCurrentUmbracoPage();
}
}
}
My role provider is in the web.config and the Visitors role is detected as role in the administration panel.
<roleManager enabled="true" defaultProvider="CustomRoleProvider">
<providers>
<clear />
<add name="UmbracoRoleProvider" type="Umbraco.Web.Security.Providers.MembersRoleProvider" />
<add name="CustomRoleProvider" type="*.UI.Helpers.CustomRoleProvider" />
</providers>
</roleManager>
EDIT: I forgot the membership provider :
public class MyMembershipProvider : Umbraco.Web.Security.Providers.MembersMembershipProvider
{
const int SITE_ID = 6;
//we dont let user change their password using RC website
public override bool AllowManuallyChangingPassword
{
get
{
return false;
}
}
public override bool EnablePasswordReset
{
get
{
return false;
}
}
public override bool EnablePasswordRetrieval
{
get
{
return false;
}
}
public override bool ValidateUser(string username, string password)
{
Customer_View user;
//just to avoid errors with uppercase letters
username = username.ToLowerInvariant();
using (var db = new CRMEntities())
{
user = db.Customer_View.SingleOrDefault(x => x.Email == username && x.SiteID == SITE_ID);
//no user with this email
if (user == null)
return false;
//check if password is same
return user.Password == password;
}
}
public override MembershipUser GetUser(string username, bool userIsOnline)
{
//just to avoid errors with uppercase letters
username = username.ToLowerInvariant();
MembershipUser toReturn;
using (var db = new CRMEntities())
{
Customer_View user = db.Customer_View.SingleOrDefault(x => x.Email == username && x.SiteID == SITE_ID);
toReturn = user != null ? new MembershipUser(
//provider name
"MyMembershipProvider", string.Format("{0} {1}", user.FirstName, user.LastName),
username, username, string.Empty, string.Empty, true, true, user.CreateDate, new DateTime(), new DateTime(), new DateTime(), new DateTime()) :
null;
}
return toReturn;
}
public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
{
return this.GetUser(providerUserKey as string, userIsOnline);
}
}
Every time I try to access a page that has specific access I get redirected to the login page even if I am authenticated:
How can I solve it ?
回答1:
Why do you want to add custom role provider. Just keep it simple. If you have a registration page for the site you can assign member type and member role programmatically
And if you add member from backend, you can add "Visitor" role easily.
So in both cases "Visitor" role can be applied easily to all members and you can keep your page behind login for visitor role(all authenticated) easily.
EDIT: I have removed the code of adding role to user programmatically as that is not needed by you and solution is as below:
As you know for custom role provider and custom membership provider go hand in hand. You have added the custom membership provider and overrided ValidateUser
method, but for all this to work you need to override two more methods of GetUser
See the code for custom provider as below and it would work
MemberShipProvider
public class MyMembershipProvider : MembersMembershipProvider
{
public override bool ValidateUser(string username, string password)
{
if (base.ValidateUser(username,password))
{
//if this is umbraco user validate by base method
return true;
}
else
{
var allow = //add your validation code for CRM, I have checked if username is "tester" and allowed for testing purpose.
return allow;
}
}
// These two methods below which you have not overridden and need to override for public access to work
public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
{
if(base.GetUser(providerUserKey, false)!=null)
//if this is umbraco user add it as is.
return base.GetUser(providerUserKey,userIsOnline);
else
//Add your CRM user, I do not have database, so added test user
return new MembershipUser("UmbracoMembershipProvider", "tester", 1233, "tester@test.com", null, null, true, false, DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now);
}
public override MembershipUser GetUser(string username, bool userIsOnline)
{
if (true)//check if this is CRM user here
{
return new MembershipUser("UmbracoMembershipProvider", "tester", 1233, "tester@test.com", null, null, true, false, DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now, DateTime.Now);
//I am adding test user, you should create user from your CRM database
}
else
return base.GetUser(username,false);
}
}
EDIT2
Okay, I have debugged every bit of code in roleprovider and membershipprovider, after the user is logged in when user tries to access protected page, first GetUser(string username, bool userIsOnline)
gets called which, if failed, returns login page, after this is success GetUser(object providerUserKey, bool userIsOnline)
gets a call. If this returns null, Insufficient access page is shown else GetRolesForUser(string username)
gets called. and upon all success page is shown. I have set break point on each method of both files, so only these 3 methods are involved. as you see login page, my guess is GetUser(string username, bool userIsOnline)
this first call is failing somewhere.
Below are my files if it helps
roles config part
<roleManager enabled="true" defaultProvider="UmbracoRoleProvider">
<providers>
<clear />
<!--<add name="UmbracoRoleProvider" type="Umbraco.Web.Security.Providers.MembersRoleProvider" />-->
<add name="UmbracoRoleProvider" type="Assembly.Providers.MyRolesProvider" />
</providers>
</roleManager>
membership config part
<membership defaultProvider="UmbracoMembershipProvider" userIsOnlineTimeWindow="15">
<providers>
<clear />
<add name="UmbracoMembershipProvider" type="Assembly.Providers.MyMemberShipProvider, Assembly" enablePasswordRetrieval="false" enablePasswordReset="false" requiresQuestionAndAnswer="false" defaultMemberTypeAlias="Member" passwordFormat="Hashed" />
<!--<add name="UsersMembershipProvider" type="Umbraco.Web.Security.Providers.UsersMembershipProvider, Umbraco" minRequiredNonalphanumericCharacters="0" minRequiredPasswordLength="4" useLegacyEncoding="true" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" passwordFormat="Hashed" />-->
<add name="UsersMembershipProvider" type="Assembly.Providers.MyUserMembershipProvider, Assembly" minRequiredNonalphanumericCharacters="0" minRequiredPasswordLength="4" useLegacyEncoding="true" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" passwordFormat="Hashed" />
</providers>
</membership>
AccountController
public class AccountSurfaceController : Umbraco.Web.Mvc.SurfaceController
{
[HttpPost]
public ActionResult LoginForm(LoginModel model)
{
//model not valid, do not save, but return current umbraco page
if (!ModelState.IsValid)
{
//Perhaps you might want to add a custom message to the TempData or ViewBag
//which will be available on the View when it renders (since we're not
//redirecting)
return CurrentUmbracoPage();
}
// Login
if (Membership.ValidateUser(model.Username, model.Password))
{
FormsAuthentication.SetAuthCookie(model.Username, false);
return RedirectToCurrentUmbracoUrl();
}
else
{
ModelState.AddModelError("Username", "Username is not valid");
return CurrentUmbracoPage();
}
}
}
public class LoginModel
{
[Required]
public string Username { get; set; }
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
}
RolesProvider
public class MyRolesProvider : MembersRoleProvider
{
const string VISITORS_LABEL = "Visitor";
public override string[] GetAllRoles()
{
var roles = base.GetAllRoles().ToList();
roles.Add(VISITORS_LABEL);
return roles.ToArray();
}
public override string[] FindUsersInRole(string roleName, string usernameToMatch)
{
if(roleName== VISITORS_LABEL)
{
var users = ApplicationContext.Current.Services.MemberService
.GetAllMembers().Select(m => m.Email).ToList();
users.Add("tester@test.com");
return users.ToArray();
}
return base.FindUsersInRole(roleName, usernameToMatch);
}
public override bool RoleExists(string roleName)
{
if(roleName == VISITORS_LABEL)
{
return true;
}
return base.RoleExists(roleName);
}
public override string[] GetRolesForUser(string username)
{
var roles = base.GetRolesForUser(username).ToList();
roles.Add(VISITORS_LABEL);
return roles.ToArray();
}
public override bool IsUserInRole(string username, string roleName)
{
if(roleName == VISITORS_LABEL)
{
return true;
}
return base.IsUserInRole(username, roleName);
}
public override string[] GetUsersInRole(string roleName)
{
if(roleName == VISITORS_LABEL)
{
var list = ApplicationContext.Current.Services.MemberService
.GetAllMembers().Select(m => m.Email).ToList();
list.Add("tester@test.com");
return list.ToArray();
}
return base.GetUsersInRole(roleName);
}
}
EDIT3:
I reproduced your scenario, it is related to web.config configuration
when I kept web config for membership as below it did not hit my provider unless i called
<membership defaultProvider="MyMembershipProvider" userIsOnlineTimeWindow="15">
<providers>
<clear />
<add name="UmbracoMembershipProvider" type="Umbraco.Web.Security.Providers.MembersMembershipProvider, Umbraco" minRequiredNonalphanumericCharacters="0" minRequiredPasswordLength="4" useLegacyEncoding="true" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" defaultMemberTypeAlias="Member" passwordFormat="Hashed" />
<add name="MyMembershipProvider" type="Umbraco724.Providers.MyMembersMembershipProvider, Umbraco724" />
<add name="UsersMembershipProvider" type="Umbraco.Web.Security.Providers.UsersMembershipProvider, Umbraco" minRequiredNonalphanumericCharacters="0" minRequiredPasswordLength="4" useLegacyEncoding="true" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" passwordFormat="Hashed" />
</providers>
</membership>
But when changed it to as given below it worked. please check the difference in config carefully.
<membership defaultProvider="UmbracoMembershipProvider" userIsOnlineTimeWindow="15">
<providers>
<clear />
<add name="UmbracoMembershipProvider" type="Umbraco724.Providers.MyMembersMembershipProvider, Umbraco724" minRequiredNonalphanumericCharacters="0" minRequiredPasswordLength="4" useLegacyEncoding="true" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" defaultMemberTypeAlias="Visitor" passwordFormat="Hashed" />
<add name="UsersMembershipProvider" type="Umbraco.Web.Security.Providers.UsersMembershipProvider, Umbraco" minRequiredNonalphanumericCharacters="0" minRequiredPasswordLength="4" useLegacyEncoding="true" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" passwordFormat="Hashed" />
</providers>
</membership>
I think umbraco only takes one provider for members as well as users. Also when its name should be UmbracoMembershipProvider only. when I kept that different then also there was error.
回答2:
With Umbraco's public access, when a user is logged out and they try to access a protected page they'll be presented with the login page. However, the URL in their address bar will be that of the page that they tried to access.
Upon validating the user RedirectToCurrentUmbracoPage()
will actually perform a full redirect to the login page and the URL in the address bar will update accordingly. What you actually want to do is to redirect them to the current URL. You can do this by replacing the first RedirectToCurrentUmbracoPage()
in your MemberLoginPost
method with RedirectToCurrentUmbracoUrl()
.
You're also using RedirectToCurrentUmbracoPage()
for when the user's credentials are incorrect, which will also cause a full redirect to the login page. If you simply return the CurrentUmbracoPage()
to the user then everything should work properly. See the updated method below:
[HttpPost]
[ActionName("MemberLogin")]
public ActionResult MemberLoginPost(MemberLoginModel model)
{
if (Membership.ValidateUser(model.Username, model.Password))
{
FormsAuthentication.SetAuthCookie(model.Username, model.RememberMe);
return RedirectToCurrentUmbracoUrl();
}
else
{
TempData["Status"] = "Invalid username or password";
return CurrentUmbracoPage();
}
}
来源:https://stackoverflow.com/questions/30173241/umbraco-public-access-error-when-authenticated