I'm using jboss as 7.1.1.Final and have configured a security-domain using LdapExtLoginModule. The login works so far.
I now want to access the roles loaded by the module in an SLSB. I know how to access the username. My example uses ejb 3.1 and prints the username to System.out.
I don't know how to access the roles and didn't find anything in the documentation. The EJBContext provides the method isCallerInRole(String) which proves that the context itself knows the roles but I can not find a method which returns a set of roles.
I know that I could write a custom LoginModule which extends the LdapExtLoginModule and sets a custom principal containing the roles. But maybe there is an easier approach using existing functionality. Does anybody know such approach?
SLSB code:
#Stateless
#Remote(IAService.class)
public class AService implements IAService
{
#Resource
private EJBContext context;
#Override
public void printUserData() {
System.out.println("Name: " + context.getCallerPrincipal().getName());
// TODO print roles
}
}
Nobody answered my question so far and because I finally have a working solution I will now share it with you:
The Java EE API does not provide any methods to access the information in server independent way. So I tried the method of writing a login module which sets a custom principal. As it turns out this does not work either.
Inspired by the answer to this question I now use the following code to get the caller roles:
private Group getRoles() {
final Subject subject = (Subject)PolicyContext.getContext("javax.security.auth.Subject.container");
final Set<Group> groups = subject.getPrincipals(Group.class);
for (final Group group : groups) {
if ("Roles".equals(group.getName())) {
return group;
}
}
throw new IllegalStateException("No roles group found");
}
The Group returned by this method contains Principals which have the names of the users groups. This only works on JBoss whose LoginModules set a Group of name "Roles".
Related
I'm currently failing at wrapping my head around claims. I have a ASP.Net Core 3 project with the angular template and users stored in app.
I want to add claims to my users, reading up on I thought it would be easy, just add something along the lines of
await _UserManager.AddClaimAsync(user, new Claim(AccountStatic.ClaimTypes._Claim_Id, user.Id));
When you create the user, and then get it back using the below line once they are logged in again:
User.FindFirst(AccountStatic.ClaimTypes._Claim_Id)?.Value;
This does however not work. I can see the claims being written to AspNetUserClaims table in my database but it's not there in the users claims when they log in. There are a few other claims there, but not the ones I have added.
Do I need to define somewhere which of the users claims get included when they log in?
Edit.
I found a post stating that I need to add claims using a DI AddClaimsPrincipalFactory. So I added this class.
public class UserClaimsPrincipalFactory : UserClaimsPrincipalFactory<ApplicationUser>
{
public UserClaimsPrincipalFactory(UserManager<ApplicationUser> userManager,IOptions<IdentityOptions> optionsAccessor): base(userManager, optionsAccessor)
{}
//https://levelup.gitconnected.com/add-extra-user-claims-in-asp-net-core-web-applications-1f28c98c9ec6
protected override async Task<ClaimsIdentity> GenerateClaimsAsync(ApplicationUser user)
{
var identity = await base.GenerateClaimsAsync(user);
identity.AddClaim(new Claim(AccountStatic.ClaimTypes.Claim_Id, user.Id ?? "[no id]"));
return identity;
}
}
And if I step through the code I can see the claims being added here. But in the Controller my custom claims are not present.
internal string GetUserId()
{
if (User.Identity.IsAuthenticated == false)
return null;
return User.FindFirst(AccountStatic.ClaimTypes.Claim_Id)?.Value;
}
Update. Ok I find this very strange. I have been trying to do what others claim work but for me nothing gets me the users name or id. inspecting the User I get the following. Nothing here contains any reference to the logged in user.
Update 2:
Just noticed that there is actually an Id in there: {http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier: ed107a11-6c62-496b-901e-ed9e6497662a} Seems to be the users id from the database. Not sure how to access it yet though.
These return null.
User.FindFirst(JwtRegisteredClaimNames.NameId)?.Value;
User.FindFirst("nameidentifier")?.Value;
User.FindFirst("NameIdentifier")?.Value;
Another update
I'm using a UserClaimsPrincipalFactory and breakingpointing it and looking at the Claims I can see that all of the ones I want are there. But again, these are not available in my API controllers as seen in the first picture.
I finally understood the problem, in large parts thanks to Ruard van Elburgs comments, and the answer he made in the linked question IdentityServer4 Role Based Authorization.
The problem is that the claims are not added to the access token.
There are two tokens, the access token and the identity token.
- Ruard van Elburg
They key to understanding what was going on was finding out that there are two tokens, and that they contain different claims and have different purposes.
You can force claims from one token to also be included in the other if you deem it necessary.
The solution to my problem was to add this in Startup.ConfigureServices
services
.AddIdentityServer(options => {})
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
{
foreach (var c in options.ApiResources)
{
// the string name of the token I want to include
c.UserClaims.Add(AccountStatic.ClaimTypes.Claim_Id);
}
});
I still have not figured out how to get the Identity token, but as I'm now including the user Id in the access token my problems are solved for the moment.
From JSR-339:
For simplicity, JAX-RS implementations are NOT REQUIRED to support processing groups other than Default.
This severely limits usefulness of validation in JAX-RS because for example for create and update you are usually using the same model object, but for create the ID of the object should not be provided and for update the ID should be provided, which could be easily validated using validation groups. In general all model objects that are used in more than one flow are impossible to validate.
I do not understand the simplicity argument because Bean Validation already supports groups, so the JAX-RS implementation just needs to pass a group to Bean Validation implementation like Hibernate Validator.
So are there any plans to add validation groups to JAX-RS?
It turns out that it does support validation groups. From the same JSR-339:
The presence of #Valid will trigger validation of all the constraint annotations decorating a Java bean class. This validation will take place in the Default processing group unless the #ConvertGroup annotation is present.
For example this is how to validate Account bean in my custom Create or Update groups rather than the Default group:
#POST
#Consumes(MediaType.APPLICATION_JSON)
Response createAccount(#Valid #ConvertGroup(from = Default.class, to = Create.class)
Account account)
#POST
#Consumes(MediaType.APPLICATION_JSON)
Response updateAccount(#Valid #ConvertGroup(from = Default.class, to = Update.class)
Account account)
public class Account {
#Null(groups = Create.class)
#NotNull(groups = Update.class)
private String Id;
}
public interface Create {}
public interface Update {}
We use Apache Shiro to authenticate and authorize users using our active directory.
Authenticating the user and mapping groups works just fine using the following config:
adRealm = org.apache.shiro.realm.activedirectory.ActiveDirectoryRealm
adRealm.searchBase = "OU=MYORGANIZATION,DC=MYDOMAIN,DC=COM"
adRealm.groupRolesMap = "CN=SOMEREADGROUP":"read","CN=SOMEMODIFYGROUP":"modify","CN=SOMEADMINGROUP":"admin"
adRealm.url = ldaps://my.ad.url:636
adRealm.systemUsername= systemuser
adRealm.systemPassword= secret
adRealm.principalSuffix= #myorganization.mydomain.com
I can authenticate in Shiro using the following lines:
String user = "someuser";
String password = "somepassword";
Subject currentUser = SecurityUtils.getSubject ();
if (!currentUser.isAuthenticated ()){
UsernamePasswordToken token = new UsernamePasswordToken (user,
password);
token.setRememberMe (true);
currentUser.login (token);
}
We now want to get more user information from our ActiveDirectory. How can I do that using Apache Shiro? I was not able to find anything about it in the documentation.
In the source code of ActiveDirectoryRealm I found this line:
NamingEnumeration answer = ldapContext.search(searchBase, searchFilter, searchArguments, searchCtls);
So the first part of the answer is clear: use the ldapContext to search something in it. But how can I retrieve the LdapContext?
It depends on what you are trying to do. Are you just trying to reuse the context to run a query for something other then authentication or authorization? Or are you trying to change the behavior of the query in the AD realm?
If the latter, you would need to extend the ActiveDirectoryRealm and override the queryForAuthorizationInfo() method.
Are you implementing something that is custom for your environment?
(updated)
A couple things:
The realm has access to the LdapContext in the two touch points: queryForAuthenticationInfo() and queryForAuthorizationInfo(), so if you extend the AD realm or AbstractLdapRealm you should already have it. You could change the query to return other info and add the extra info to your Principal. Then you have access to that info directly from your Subject object.
Your realms, are not required to be singletons.
If you want to do some other sort of user management (email all users with a given role, create a user, etc). Then you could create a LdapContextFactory in your shiro.ini, and use the same instance for multiple objects.
[main]
...
ldapContextFactory = org.apache.shiro.realm.ldap.JndiLdapContextFactory
ldapContextFactory.systemUsername = foobar
ldapContextFactory.systemPassword = barfoo
adRealm = org.apache.shiro.realm.activedirectory.ActiveDirectoryRealm
adRealm.ldapContextFactory = $ldapContextFactory
...
myObject = com.biz.myco.MyObject
myObject.ldapContextFactory = $ldapContextFactory
This would work well if myObject is interacting with other Shiro components, (responding to events, etc), but less so if you need access to it from another framework. You could work around this by some sort of static initialization that builds creates the ldapContextFactory, but in my opinion, this is where the sweet spot of using the shiro.ini ends, and where using Guice or Spring shines.
I'm using DW 0.9.1 and it would be cool, if I could inject the #Auth XYzObject into some ContainerRequest or even better in a ContainerResponseFilter (or servlet filter).
Does anyone knows if this is possible?
The usecase: Some users does have different allowd access rates (rate limiting), e.g. max. 2 request per second and max 60 per Minute. This can be verified with the injected #Auth XYzObject.
In the end I can do this also in the Ressource, where this information is available, but as I said it would be cool to do this outside of my ressources in a filter or something else. And I do not want to do this is the authenticating/ authorization process, because rate limiting is not related to this. At the moment,all the variants I tried, nothing works, so it seems not possible, but I hope someone knows the trick.
How the #Auth annotation works is that it is handled by a ValueFactoryProvider, which is used only for (resource) method parameter injection. So you can't inject it into arbitrary locations.
However, when you created XyzObject you made it implement java.security.Principal. The reason DW makes you use this type, is because after it authenticates, it sets the Principal in the SecurityContext, as seen in the AuthFilter.
If you look at the implementation for AuthValueFactoryProvider, you will see that the way it obtains the Principal is by getting it from the SecurityContext. And that's how the Principal is injected with #Auth as a method argument.
In a ContainerRequestFilter (and in may other locations), you have access to the SecurityContext. In a filter you can get it with requestContext.getSecurityContext(). So in your filter you can get the Principal from the SecurityContext and just cast it
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
Principal principal = requestContext.getSecurityContext().getUserPrincipal();
if (principal != null) {
XyzObject xyz = (XyzObject)principal;
}
}
I have a multi-tenant system where each tenant shares the same instance of the codebase, but has their own databases.
I'm using RavenDB for persistence, with a standard c# facade/BLL backend wrapped with Asp.net WebAPI, and I'm finding that at every lower level operation (deep within my business logic classes) that touch the datbase, I need to pass in an identifier so that my RavenDb client session knows which database to operate against.
When the user authenticates, I resolve the appropriate database identifer, store it in the session manager. Every call against the Web API layer passes in a session ID which resolves the database ID in the backend, which is then used to pass into every single facade/BLL call.
All my dependencies are handled via an IoC container at the WebAPI level, but i can't pass in the database ID at this phase because it can be different for every user that is logged in.
this, of course is getting tedious.
can someone give me some guidance as to what I can do to alleviate this? Maybe perhaps some sort of policy injection/AOP solution?
a rough sample of my backend code looks like..
public class WidgetService()
{
private WidgetBLL _widgetBLL;
private ISessionManager _sessionManager;
public WidgetService(WidgetBLL _widgetBLL, ISessionManager sessionManager)
{
_widgetBLL = widgetBLL;
_sessionManager = sessionManager
}
public Widget getWidget(string sessionId, string widgetId)
{
string DbId = sessionManager.ResolveDbId(sessionId)
return _widgetBLL.GetWidget(string dbId, string widgetId);
}
}
public class WidgetManager()
{
public GetWidget(string dbId, string widgetId)
{
using (IDocumentSession session = documentStore.OpenSession(dbId)
{
var widget = session.load<Widget>(widgetid);
}
return widget;
}
}
the DBID is the identifier for that particular tenant that this particular user is a member of.
You need to change how you are using the session.
Instead of opening and closing the session yourself, do that in the IoC code.
Then you pass a session that is already opened for the right db.