What is best practise in Spring when creating a new user with custom attributes...to extend org.springframework.security.core.userdetails.User or to create the User in the UserDetailsService (this is the approach taken in the IceFaces tutorial).
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException {
AppUser user = userDAO.findUser(username);
if (user == null)
throw new UsernameNotFoundException("User not found: " + username);
else {
return makeUser(user);
}
}
private User makeUser(AppUser user) {
return new User(user.getLogin(), user
.getPassword(), true, true, true, true,
makeGrantedAuthorities(user));
}
If your user permissions fit into the context of the Spring Security User class then simply writing your own UserDetails service is fine. But if your Users have other attributes that are not encapsulated in the available fields in the default Userclass then you will need to do both. There's not really a "best practice" - both are fine options it really depends on your use case.
Related
I would like to create a clearer picture in my mind as to what the differences are between roles and scopes in .NET Web API projects. This is more of a best-approach question than anything else, and I am finding myself to be a little confused as to how best authorize users that want to access my API. I come from a .NET MVC background, so I am familiar with roles and I am wondering if the same approaches apply to the web API framework. I am having difficulties putting scopes in the picture and how I should use them to allow access for a user using a particular client ID. Are scopes similar to access permissions? To illustrate my confusion, let's use this example:
Client A
Native app: displays event calendar
Role: Event
User login required? No
Allowed scopes: Read events
Client B
Web app: shows next upcoming event, displays registrant names
Role: Event
User login required? Yes
Allowed scopes: Read events, read registrants
Client C
Native app: registers a person for an event
Role: Registrant
User login required? Yes
Allowed scopes: Read events, read registrants, write registrants
Basically I would like to know if my above use of scopes is correct and what the best approach would be to grant resource owner credentials. I am using the token based authentication as outlined in Taiseers tutorial. Below is my current incomplete code snippet that will take care of validating requested client and scope:
public override Task ValidateClientAuthentication(OAuthValidateClientAuthenticationContext context)
{
ApiClient client = null;
string clientId = string.Empty;
string clientSecret = string.Empty;
if (!context.TryGetBasicCredentials(out clientId, out clientSecret))
context.TryGetFormCredentials(out clientId, out clientSecret);
if (context.ClientId == null)
{
context.Validated();
context.SetError("invalid_clientId", "ClientId should be sent.");
return Task.FromResult<object>(null);
}
using (ApiClientRepo _clientRepo = context.OwinContext.GetUserManager<ApiClientRepo>())
{
client = _clientRepo.FindClient(context.ClientId);
}
if (client == null)
{
context.SetError("invalid_clientId", string.Format("Client '{0}' is not registered in the system.", context.ClientId));
return Task.FromResult<object>(null);
}
// Validate client secret
if (string.IsNullOrWhiteSpace(clientSecret))
{
context.SetError("invalid_secret", "Client secret should be sent.");
return Task.FromResult<object>(null);
}
else
{
WPasswordHasher passwordHasher = new WPasswordHasher();
PasswordVerificationResult passwordResult = passwordHasher.VerifyHashedPassword(client.SecretHash, clientSecret);
if (passwordResult == PasswordVerificationResult.Failed)
{
context.SetError("invalid_secret", "Client secret is invalid.");
return Task.FromResult<object>(null);
}
}
if (!client.Active)
{
context.SetError("invalid_clientId", "Client is inactive.");
return Task.FromResult<object>(null);
}
context.OwinContext.Set<int>("as:clientRepoId", client.Id);
context.OwinContext.Set<string>("as:clientAllowedOrigin", client.AllowedOrigin);
context.OwinContext.Set<string>("as:clientRefreshTokenLifeTime", client.RefreshTokenLifeTime.ToString());
context.Validated();
return Task.FromResult<object>(null);
}
public override async Task GrantResourceOwnerCredentials(OAuthGrantResourceOwnerCredentialsContext context)
{
IApiUser user = null;
string scope = null;
// Get parameters sent in body
Dictionary<string, string> body = context.Request.GetBodyParameters();
// Get API scope
body.TryGetValue("scope", out scope);
if (scope == null)
{
context.Validated();
context.SetError("invalid_scope", "Invalid requested scope.");
return;
}
var allowedOrigin = context.OwinContext.Get<string>("as:clientAllowedOrigin");
context.OwinContext.Response.Headers.Add("Access-Control-Allow-Origin", new[] { allowedOrigin });
// At this point I got the requested scope.
// What should I do with it?
if (user == null)
{
context.SetError("invalid_grant", "The user name or password is incorrect.");
return;
}
// create claims identity based on user info
ClaimsIdentity identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, user.FirstName + " " + user.LastName));
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Username));
identity.AddClaim(new Claim(ClaimTypes.Role, scope));
var props = new AuthenticationProperties(new Dictionary<string, string>
{
{
"as:client_id", (context.ClientId == null) ? string.Empty : context.ClientId
},
{
"userName", context.UserName
}
});
var ticket = new AuthenticationTicket(identity, props);
context.Validated(ticket);
}
Thanks ahead for all thoughts, suggestions and ideas!
In my perspective scopes define the resources.
Basically the request challenge is "may client (=application) access resource x on your behalf"?
Where x is a any resource your API serves.
I've used a conveniention in a project where a scope can be specific for a CRUD action on a resource. For example scope = tweets.read or tweets.create.
Having a token for a scope doesn't give a client the permission. The permission is based on the fact that the user has permission to preform the action and has the client the correct resource scope in its token. Of course a users permission can be based on a role like guest or admin etc.
So in theory the user can grant access to scopes (resources) it has no permissions at.
A token has a lifetime of let's say 20 min, if you base a permission on any value in the access token, the permission cannot be revoked or changed within the token lifetime.
I generated Role, User and UserRole class using the Spring Security Core Plugin. I want to set the users role directly in the user-creation-process. I added a "Role" field in User but don't know how and where I should set the entry in UserRole.
Is there anything else to implement like reauthentication to update a users role afterwards?
You should delete link to Role from User and use next code, after creating User and Role:
UserRole.create(user,role,true)
Where user your created user, role your created role, and true is indicated that userRole should create with flush:true
Good luck!
Yes its works!!! thanks, this is my code in a Service:
public String updateUser(long userId, String username, String password, long roleId){
Object[] args = [messageSource.getMessage('spring.security.ui.login.username',null, null),username];
def user = User.get(userId);
def userTemp = User.findAllByUsername(username);
if(userTemp.isEmpty() || userTemp.get(0).id == userId){
def role = Role.get(roleId);
user.username = username;
user.roleId = roleId;
if (password != ''){
user.password = password;
}
user.save(flush:true);
UserRole.create(user,role,true);
return "<span class='successMessage'><strong>" + messageSource.getMessage("message.common.record.saved.successfully", args, null) + "</strong></span>";
} else {
return "<span class='warnMessage'><strong>" + messageSource.getMessage("message.common.register.exist", args,null) + "</strong></span>";
}
}
My ASP.NET webapp will be protected by third party agent(SM). SM will intercept every call to the webapp, authenticate the user as valid system user, add some header info ex username and redirect it to my webapp. I then need to validate that the user is an active user of my website.
Currently I am authenticating the user by implementing the Application_AuthenticateRequest method in the Global.asax.cs file. I have a custom membership provider whose ValidateUser method, checks if the user exists in the users table of my database.
Just wanted to get comments if this was a good approach or not.
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
//if user is not already authenticated
if (HttpContext.Current.User == null)
{
var smcred = ParseAuthorizationHeader(Request);
//validate that this user is a active user in the database via Custom Membership
if (Membership.ValidateUser(smcred.SMUser, null))
{
//set cookie so the user is not re-validated on every call.
FormsAuthentication.SetAuthCookie(smcred.SMUser, false);
var identity = new GenericIdentity(smcred.SMUser);
string[] roles = null;//todo-implement role provider Roles.Provider.GetRolesForUser(smcred.SMUser);
var principal = new GenericPrincipal(identity, roles);
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null)
{
HttpContext.Current.User = principal;
}
}
}
}
protected virtual SMCredentials ParseAuthorizationHeader(HttpRequest request)
{
string authHeader = null;
var smcredential = new SMCredentials();
//here is where I will parse the request header for relevant tokens ex username
//return smcredential;
//mockup below for username henry
return new SMCredentials() { SMUser = "henry", FirstName = "", LastName = "", EmailAddr = "" };
}
I would go with the Attribute approach to keep it more MVC like. It would also allow you more flexibility, you could potentially have different Membership Providers for different controllers/actions.
I am using spring security core plugin (1.2.7) with grails 2.0
Let's say that I have controller with a method that uses #Secured annotation.
class ArticleController {
def springSecurityService
#Secured(['ROLE_PREMIUM_USER'])
def listPremium() {
render 'premium content'
}
}
in my unit test I would like to test if a user with role 'ROLE_PREMIUM_USER' can see content of listPremium method. How can I do this?
I know that it should start as follows:
#TestFor(ArticleController)
#Mock([SpringSecurityService])
class ArticleControllerTests {
void testListPremium() {
defineBeans {
springSecurityService(SpringSecurityService)
}
//but how to login the user here in order to see premium content?
controller.listPremium()
assert response.text() == 'premium content'
}
}
I am not sure how can I authenticate user or mock action that checks ROLE_PREMIUM_USER. Any help?
You may be able to use
SpringSecurityUtils.reauthenticate username, null
We created our custom AuthenticationHelper:
public final class AuthenticationHelper {
public static Authentication authenticate(UserDetailsService userDetailsServiceImpl, String userName) {
UserDetails userDetails = userDetailsServiceImpl.loadUserByUsername(userName);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword());
UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(userDetails, token.getCredentials(), userDetails.getAuthorities());
result.setDetails(token.getDetails());
Authentication auth = result;
SecurityContextHolder.getContext().setAuthentication(auth);
auth = SecurityContextHolder.getContext().getAuthentication();
Assert.assertTrue(auth.isAuthenticated());
return auth;
}
}
The important part is:
SecurityContextHolder.getContext().setAuthentication(auth);
We use MVC 3. The default user management is not usable for us as our account info is stored in our own data-store and access goes via our own repository classes.
I'm trying to assign a principal add roles to the HttpContext.User and give out an authorization cookie.
Based on a code snipped I found I tried something like this:
if (UserIsOk(name, password))
{
HttpContext.User =
new GenericPrincipal(
new GenericIdentity(name, "Forms"),
new string[] { "Admin" }
);
FormsAuthentication.SetAuthCookie(name, false);
return Redirect(returnUrl);
}
When the next request is done, the user is authenticated, but he is not in the "Admin" role.
What am I missing?
I think you should implement FormsAuthenticationTicket.
More info here : http://msdn.microsoft.com/en-us/library/aa289844(v=vs.71).aspx
In Mvc it is quite similar.
I have a class called UserSession that is injected into LoginController and that I use in LogOn action :
[HttpPost, ValidateAntiForgeryToken]
public ActionResult Index(LoginInput loginInput, string returnUrl)
{
if (ModelState.IsValid)
{
return (ActionResult)_userSession.LogIn(userToLog, loginInput.RememberMe, CheckForLocalUrl(returnUrl), "~/Home");
}
}
Here's my UserSession LogIn implementation (notice I put the "Admin" role hard coded for the example, but you could pass it as argument) :
public object LogIn(User user, bool isPersistent, string returnUrl, string redirectDefault)
{
var authTicket = new FormsAuthenticationTicket(1, user.Username, DateTime.Now, DateTime.Now.AddYears(1), isPersistent, "Admin", FormsAuthentication.FormsCookiePath);
string hash = FormsAuthentication.Encrypt(authTicket);
var authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, hash);
if (authTicket.IsPersistent) authCookie.Expires = authTicket.Expiration;
HttpContext.Current.Response.Cookies.Add(authCookie);
if (!String.IsNullOrEmpty(returnUrl))
return new RedirectResult(HttpContext.Current.Server.UrlDecode(returnUrl));
return new RedirectResult(redirectDefault);
}
Then in the base controller I've overriden OnAuthorization method to get the cookie :
if (filterContext.HttpContext.Current.User != null)
{
if (filterContext.HttpContext.Current.User.Identity.IsAuthenticated)
{
if( filterContext.HttpContext.Current.User.Identity is FormsIdentity )
{
FormsIdentity id = filterContext.HttpContext.Current.User.Identity as FormsIdentity;
FormsAuthenticationTicket ticket = id.Ticket;
string roles = ticket.UserData;
filterContext.HttpContext.Current.User = new GenericPrincipal(id, roles);
}
}
}
I hope this helps. Let me know.
You sure, that roles are enabled, and there is such role?
If not, do following:
In Visual Studio:
Project -> ASP.NET Configuration
Then choose Security, enable roles. Create role "Admin".
Then try your approach