I use Shiro 1.2.3 in a JSF2 project. I couldn't find a way to update the principal of an authenticated subject without a logout. I need this when a logged-in user wants to update his/her profile info. I store a userBean as principal and it should be updated along with the profile info. Doing a programmatically login can be a solution but this time password of the user is needed to create a token.
Some time has past but I hope this could be useful to others too.
When you login with Shiro a Session is created and a token is sent to the client for further requests.
For each request Shiro intercept the HTTP request, verifies the token as valid then creates a new Subject using the org.apache.shiro.web.mgt.DefaultWebSubjectFactory.createSubject(SubjectContext context) method which contains an important line
PrincipalCollection principals = wsc.resolvePrincipals();
which finally bring us to the solution method org.apache.shiro.subject.support.DefaultSubjectContext.resolvePrincipals() which contains the critical lines
Session session = resolveSession();
if (session != null) {
principals = (PrincipalCollection) session.getAttribute(PRINCIPALS_SESSION_KEY);
}
That is to update your Subject's Principal without logout you need to update his/her session's PRINCIPALS_SESSION_KEY attribute. To summarize your code could be as simple as
PrincipalCollection pc = (PrincipalCollection) getSubject().getSession().getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY;
// do something in here
getSubject().getSession().setAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY, pc);
Related
I used the my Azure Active Directory to protect my web API and I create a native application in the Azure management portal. This native application is basically a MVC web application and I use the ADAL library to get the token and call the api with that token. The code I used to get the token is shown below:
AuthenticationContext ac = new AuthenticationContext(authority);
AuthenticationResult ar = ac.AcquireToken(resourceID, clientID, redirectURI);
string accessToken = ar.AccessToken;
Now I need to logout and switch to another user but somehow the user credentials are remembered by the system. I clear the token cache in the authentication context and post logout api request as follows where *** is my tenant ID.
//Log out after api call
ac.TokenCache.Clear();
string requestUrl = "https://login.windows.net/***/oauth2/logout";
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
var response = await client.SendAsync(request);
The api call succeeds but the logout doesn't work.
What should I do to logout and switch to another user?
I don't think this would work. You would need to redirect the user to logout URL for logout to work.
Here's how you can create a logout URI:
https://login.microsoftonline.com/{0}/oauth2/logout?post_logout_redirect_uri={1}
Where:
{0} - Fully qualified name of your Azure Active Directory e.g. yourad.onmicrosoft.com or tenant id.
{1} - The URL of your application where a user must be redirected back after the logout is complete. This should be properly URL encoded.
If you goal is to sign in a s a different user, you don't strictly need to log out the first user from its session with Azure AD. You can pass PrompBehavior.Always in your AcquireToken call, so that you will be guaranteed to prompt the user with a clean credential gathering UX.
Note: if you want to wipe every trace of the first user from the app you can keep the cache cleanup code you have. ADAL allows you to keep tokens for multiple users tho, hence if your app as multi-user functions this might be useful - the catch is that if you do so, at every AcquireToken you'll have to also specify which user you want a token for or ADAL won't know which one to return. If you don't need multiple users at once, the cache cleanup + PromptBehavior.Always remains the easiest path.
You can do this for clear cache :
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.removeAllCookie();
CookieSyncManager.getInstance().sync();
mAuthContext.getCache().removeAll();
Little prehistory:
I develop RESTful services. That services receives requests from the web frontend and resends it to another server with the actual business logic. I use Shiro to protect my services. Problem is that some business logic functions require a user password. Of course, I can store password in my principal, but I think it is not correct to store credentials there.
Question
So, what is the conceptual right place where I should store credentials to have access inside my REST services?
Update
Ok, I can also store passwords in Shiro sessions, but i don't think that it is the correct place.
Normally, the info is kept in an implementation of AuthenticationToken. This interface has two method: getPrincipal (for example login or email) and getCredentials(). The last is usually used to store a password.
If you look at class UsernamePasswordToken, which is an implementation of this interface, you see that the two are indeed used for username and password.
Now what we did is extend the class AuthorizingRealm for our own authentication mechanism and in the authentication method we store the token in the principal.
#Override
public AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
... authentication logic
SimplePrincipalCollection principalCollection = new SimplePrincipalCollection(login, realmName);
principalCollection.add(token, realmName);
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principalCollection, login.getPasswordHash());
return simpleAuthenticationInfo;
}
Now you can get the token later:
PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals();
AuthenticationToken token = principals.oneByType(AuthenticationToken.class);
I have a JSF web application that uses cookies for automatic authentication without prompting for username & password. It uses a cookie with username and a random UUID, and uses a WebFilter for redirection.
When there are no cookies on the client side, the authentication is done through HttpServletRequest #login(String username, String password). Behind the scenes, this approach uses JAAS authentication, and uses a LDAP server behind.
My problem comes when my application recognizes the user through the cookies holding the userid and the UUID. In this situation,
the application doesn't know the password, so the method HttpServletRequest #login(String username, String password) cannot be used.
Should I ask the password to the LDAP server through JNDI? This doesn't seem to be possible at a first glance
Alternatively, I could store the password in my db. But this would mean duplication of information, and I don't like it.
I have seen around people simply setting the attribute "role" to the session, but this doesn't seem to be equivalent to a JAAS login. With "equivalent" I mean being able to use isUserInRole() and getUserPrincipal() methods.
So, the question is: how am I supposed to log in the user in this case? I hope that the question is clearer now.
EDIT
In order to let the code speak, I add a simplified version of the Managed Bean:
#ManagedBean
#SessionScoped
public class loginBean() {
private String username = null;
private String password = null;
private UUID uuid = null;
private boolean rememberMe = false;
public void doLogin() {
checkCookies(); // this method sets the property values after checking if
// username & uuid match the ones saved previously
if (username != null && uuid != null && rememberMe) {
// authenticate automatically. Here I don't know how to proceed, because
// I don't have the password, unless I have saved it in the application's db,
// duplicating it because it's already in LDAP server.
} else {
httpServletRequest.login(username, password); // this uses LDAP behind JAAS
createCookies(); // this method also saves username & uuid in the app's db
}
}
To do an actual container login in a custom way (in your case via an cookie and UUID instead of the password), you need to create your own login module.
The dedicated API in Java EE for this is JASPI/JASPIC (people can never quite agree on the name, complicating eg google queries).
A login module is in full control and does not have to authenticate with the ldap server (if your app can locally verify with 100% certainty that the cookie is valid). You probably do have to authorize the user (ask the ldap server for the roles/groups the user has).
As an alternative to JASPI/JASPIC you can also look at the proprietary login module mechanism that your server is using.
Using an LDAP entry for this case is equivalent to requesting that the directory server authenticate a connection using information provided by the application. In terms of LDAP, authenticate means that an existing LDAP session, that is, a connection to a directory server, has had its authentication state changed by a successful BIND request.
The web application should request appropriate information from the user to be authenticated and present this information to the directory server as a BIND request. The information required varies according to the authentication method used by the web application (LDAP client):
A simple BIND request requires a distinguished name and a password. This distinguished name and password should be transmitted to the directory server as a simple BIND request over a secure connection.
A SASL BIND request using a predefined SASL mechanism. The mechanisms vary per server and range from GSSAPI to PLAIN.
Upon receipt of the BIND request, the directory server will immediately change the authentication state of the connection to anonymous and process the BIND request. If the request can be successfully processed, the directory server responds to the LDAP with a BIND response including an integer result code of zero (0). This indicates that the distinguished name or username was able to successfully authenticate.
The web application should use some mechanism to maintain an authentication state and issue a BIND request to the directory server when the authentication state changes. This could be a session timeout, or some other mechanism. The method that is chosen should not be changeable by the user.
In summary, use the directory server to check authentication credentials and use the session framework to manage authentication state.
Edit:
Seems this was a controversial answer.
Using cookies does not handle the case of the browser having cookies disabled, and cookies are not necessary to maintain an authentication state when using sessions.
The session does not need the password, nor should it store any sensitive information like passwords in memory or sessions. The application should request the password when the authentication state expires (if ever) or the session expires (if ever).
Following the steps in this guide Using Azure ACS I have a working Azure ACS service configured & authenticating via Facebook, redirecting back to a website running on my development server.
On authentication success Azure ACS redirects back to my local development website and the IsAuthenticated flag is true, however I want to set the IsAuthenticated flag to true only if the email from the claim also exists in my local database, via a check/call to a custom MembershipProvider. If the email from the claim does not exist I want to redirect the client to a register page. Once registered and authenticated I would like to set the IsAuthenticated flag to true.
Currently once authenticated with Facebook and AzureACS, a user can request a secure page such as ViewAccountBalance.aspx, even though the account does not exist since out of the box IsAuthenticated flag to true. Interested to hear what others have done and what the best practice is.
You'll need to make a clear difference between authentication and authorization. Since the user logged in through Facebook it means he's authenticated (you know who he is and where he comes from).
Now, if you want to restrict parts of the application based on a specific condition you're actually talking about authorization. You might consider combining roles with a simple HttpModule. Example: your HttpModule could verify which page the user is browsing. If the user accesses a page that requires an active profile, you could use the following code:
public class RequiresProfileHttpModule : IHttpModule
{
public void Dispose()
{
}
public void Init(HttpApplication context)
{
context.AuthorizeRequest += new EventHandler(OnAuthorize);
}
private void OnAuthorize(object sender, EventArgs e)
{
HttpApplication app = sender as HttpApplication;
if (app.Request.Url.ToString().Contains("bla") && !app.Context.User.IsInRole("UsersWithProfile"))
app.Response.Redirect("http://myapp/register.aspx");
}
}
The only thing you'll need to take care of is to update the principal to make sure it has the role UsersWithProfile if the user filled in his email address.
This is just one of many possible solutions. If you're using ASP.NET MVC you could achieve the same result with global ActionFilters. Or, you could also try to work with the IClaimsPrincipal (add a claim if the user has a profile).
Sandrino is correct. You can use role based authorization (or more generally, claim based authorization). By default, ACS simply returns the claims issued by the identity providers to your relying party. For Facebook, it will return an email claim. However, you can configure ACS to create additional rules. For example, you can map particular users to a role whose value is administrator. Then ACS will also return this role claim to your relying party. Then you can use Sandrino’s suggestion to use role based authorization. You can also refer to http://msdn.microsoft.com/en-us/library/windowsazure/gg185915.aspx for more information.
I have succesfully created a REST web service with Jersey and secured it via java security annotations.
It looks something like this
GET /users/ // gives me all users
GET /users/{id} // gives the user identified by {id}
POST /users/ // creates user
PUT /users/{id} // updates user identified by {id}
DELETE /users/{id} // delete user
I also have setup a realm with two roles: user and admin
I secured all methods so that only admins can access them.
Now i want to give free the PUT /users/{id} and GET /users/{id} methods, so that users can access their own and only their own resources.
Example:
// user anna is logged in and uses the following methods
GET /users/anna // returns 200 OK
GET /users/pete // returns 401 UNAUTHORIZED
Since i could not find a way to configure this through annotations, I am thinking of passing the HTTP request to the corresponding method to check if the user is allowed to access the resource.
It would look something like this for the GET /users/{id} method:
#GET
#Path("/users/{id}")
#RolesAllowed({"admin","user"})
#Produces(MediaType.APPLICATION_JSON)
public Response getUser(
#PathParam("id") String id,
#Context HttpServletRequest req
) {
HttpSession session = request.getSession(false);
if (session != null && session.getValue("userID").equals(id))
return getObject(User.class, id);
return Response.status(Status.UNAUTHORIZED).build();
}
I don't like this aproach because i think i would have to add the userID manualy to the session.
Do you know a more elegant way to solve this?
If not how do you add the userid to the session while using form authentication?
EDIT
Thank you Will and Pavel :) Here is my final solution:
#Context
private SecurityContext security;
// ...
#GET
#Path("/users/{id}")
#RolesAllowed({"admin","user"})
#Produces(MediaType.APPLICATION_JSON)
public Response getUser(#PathParam("id") String id){
if (security.isUserInRole("user"))
if (security.getUserPrincipal().getName().equals(id))
return getObject(User.class, id);
else
return Response.status(Status.UNAUTHORIZED).build();
else
return getObject(User.class, id);
}
In the HttpServletRequest, you can call getRemoteUser() or getUserPrincipal() to get the identity of the logged in user. You would then continue like you are doing in specifically allowing or denying them access to the particular resource.
Blessed Geek is referring more specifically to the aspect of REST regarding stateless transactions and the use of HTTP authentication. While this is an important point in the larger scope of a REST architecture, it's less relevant to your specific question since you don't specify the type of authentication mechanism you're using against your Java EE app, especially since authentication is a container issue in Java EE, not an application issue.
If you're using basic authentication, then you are using HTTP headers to manage authentication and authorization. If you're using form based authentication, then the container is managing this for you via the servlet session, making the service stateful (since sessions are a stateful artifact).
But this has no bearing on your specific question.
One of the most important aspects of deploying REST is understanding the role of http headers and cookies.
For REST to be practical, you need to deploy an authentication framework.
Read
GWT and Google Docs API.
GWT-Platform login + session management
Read up on Google Federated Login, OAuth and OpenID.
Some of my explanations may be outdated, if they were posted before OAuth 2.0.