I've been working on this task for too long to abandon the idea of using Spring Security to achieve it, but I wish that the community will provide with some support that will help reduce the regret that I have for choosing Spring Security. Enough ranting and now let's get to the point.
I'm trying to create an ACL by using JDBCMutableAclService.createAcl as follows:
public void addPermission(IWFArtifact securedObject, Sid recipient, Permission permission,
Class clazz) {
ObjectIdentity oid = new ObjectIdentityImpl(clazz.getCanonicalName(), securedObject.getId());
this.addPermission(oid, recipient, permission);
}
#Override
#Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_UNCOMMITTED, readOnly = false)
public void addPermission(ObjectIdentity oid, Sid recipient, Permission permission) {
SpringSecurityUtils.assureThreadLocalAuthSet();
MutableAcl acl;
try {
acl = this.mutableAclService.createAcl(oid);
} catch (AlreadyExistsException e) {
acl = (MutableAcl) this.mutableAclService.readAclById(oid);
}
// try {
// acl = (MutableAcl) this.mutableAclService.readAclById(oid);
// } catch (NotFoundException nfe) {
// acl = this.mutableAclService.createAcl(oid);
// }
acl.insertAce(acl.getEntries().length, permission, recipient, true);
this.mutableAclService.updateAcl(acl);
}
The call throws a NotFoundException from the line:
// Retrieve the ACL via superclass (ensures cache registration, proper retrieval etc)
Acl acl = readAclById(objectIdentity);
I believe this is caused by something related to Transactional, and that's why I have tested with many TransactionDefinition attributes. I have also doubted the annotation and tried with declarative transaction definition, but still with no luck.
One important point is that I have used the statement used to insert the oid in the database earlier in the method directly on the database and it worked, and also threw a unique constraint exception at me when it tried to insert it in the method.
I'm using Spring Security 2.0.8 and IceFaces 1.8 (which doesn't support spring 3.0 but definetely supprorts 2.0.x, specially when I keep caling SpringSecurityUtils.assureThreadLocalAuthSet()). My AppServer is Tomcat 6.0, and my DB Server is MySQL 6.0
I wish to get back a reply soon because I need to get this task off my way
Your code isn't formatted very well to understand on which line error is thrown.
Anyway a NotFoundException has to catched and create a new acl :
try {
acl = (MutableAcl) mutableAclService.readAclById(oi);
}
catch (NotFoundException e) {
acl = mutableAclService.createAcl(oi);
}
Related
I'm publishing messages into RabbitMQ and I would like to track the errors when RabbitMQ is down, for this I added one RetryTemplate with the recovery callback, but the recovery callback only provides this method getLastThrowable() and I'm not sure how to provide the details of the messages that failed when RabbitMQ is down. (as per documentation "The RecoveryCallback is somewhat limited in that the retry context only contains the
lastThrowable field. For more sophisticated use cases, you should use an external
RetryTemplate so that you can convey additional information to the RecoveryCallback via
the context’s attributes") but I don't know how to do that, if anyone could help me with one example that will be awesome.
Rabbit Template
public RabbitTemplate rabbitMqTemplate(RecoveryCallback publisherRecoveryCallback) {
RabbitTemplate r = new RabbitTemplate(rabbitConnectionFactory);
r.setExchange(exchangeName);
r.setRoutingKey(routingKey);
r.setConnectionFactory(rabbitConnectionFactory);
r.setMessageConverter(jsonMessageConverter());
RetryTemplate retryTemplate = new RetryTemplate();
ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
backOffPolicy.setInitialInterval(500);
backOffPolicy.setMultiplier(10.0);
backOffPolicy.setMaxInterval(10000);
retryTemplate.setBackOffPolicy(backOffPolicy);
r.setRetryTemplate(retryTemplate);
r.setRecoveryCallback(publisherRecoveryCallback);
return r;
}
Recovery Callback
#Component
public class PublisherRecoveryCallback implements RecoveryCallback<AssortmentEvent> {
#Override
public AssortmentEvent recover(RetryContext context) throws Exception {
log.error("Error publising event",context.getLastThrowable());
//how to get message details here??
return null;
}
}
AMQP Outbound Adapter
return IntegrationFlows.from("eventsChannel")
.split()
.handle(Amqp.outboundAdapter(rabbitMqTemplate)
.exchangeName(exchangeName)
.confirmCorrelationExpression("payload")
.confirmAckChannel(ackChannel)
.confirmNackChannel(nackChannel)
)
.get();
The isn't possible because the function RabbitTemplate.execute() is already not aware about message you send, because it may be performed from any other method, where we might not have messages to deal:
return this.retryTemplate.execute(
(RetryCallback<T, Exception>) context -> RabbitTemplate.this.doExecute(action, connectionFactory),
(RecoveryCallback<T>) this.recoveryCallback);
What I suggest you to do is like storing message to the ThreadLocal before send and get it from there from your custom RecoveryCallback.
I am using Spring SAML in a multi-tenant application to provide SSO. Different tenants use different urls to access the application, and each has a separate Identity Provider configured. How do I automatically assign the correct Identity Provider given the url used to access the application?
Example:
Tenant 1: http://tenant1.myapp.com
Tenant 2: http://tenant2.myapp.com
I saw that I can add a parameter idp to the url (http://tenant1.myapp.com?idp=my.idp.entityid.com) and the SAMLContextProvider will pick the identity provider with that entity id. I developed a database-backed MetadataProvider that takes the tenant hostname as initialisation parameter to fetch the metadata for that tenant form the database linked to that hostname. Now I think I need some way to iterate over the metadata providers to link entityId of the metadata to the hostname. I don't see how I can fetch the entityId of the metadata, though. That would solve my problem.
You can see how to parse available entityIDs out of a MetadataProvider in method MetadataManager#parseProvider. Note that generally each provider can supply multiple IDP and SP definitions, not just one.
Alternatively, you could further extend the ExtendedMetadataDelegate with your own class, include whatever additional metadata (like entityId) you wish, and then simply retype MetadataProvider to your customized class and get information from there when iterating data through the MetadataManager.
If I were you, I'd take a little bit different approach though. I would extend SAMLContextProviderImpl, override method populatePeerEntityId and perform all the matching of hostname/IDP there. See the original method for details.
At the time of writing, Spring SAML is at version 1.0.1.FINAL. It does not support multi-tenancy cleanly out of the box. I found another way to achieve multi-tenancy apart from the suggestions given by Vladimir above. It's very simple and straight-forward and does not require extension of any Spring SAML classes. Furthermore, it utilizes Spring SAML's in-built handling of aliases in CachingMetadataManager.
In your controller, capture the tenant name from the request and create an ExtendedMetadata object using the tenant name as the alias. Next create an ExtendedMetadataDelegate out of the ExtendedMetadata and initialize it. Parse the entity ids out of it and check if they exist in MetadataManager. If they don't exist, add the provider and refresh metadata. Then get the entity id from MetadataManager using getEntityIdForAlias().
Here is the code for the controller. There are comments inline explaining some caveats:
#Controller
public class SAMLController {
#Autowired
MetadataManager metadataManager;
#Autowired
ParserPool parserPool;
#RequestMapping(value = "/login.do", method = RequestMethod.GET)
public ModelAndView login(HttpServletRequest request, HttpServletResponse response, #RequestParam String tenantName)
throws MetadataProviderException, ServletException, IOException{
//load metadata url using tenant name
String tenantMetadataURL = loadTenantMetadataURL(tenantName);
//Deprecated constructor, needs to change
HTTPMetadataProvider httpMetadataProvider = new HTTPMetadataProvider(tenantMetadataURL, 15000);
httpMetadataProvider.setParserPool(parserPool);
//Create extended metadata using tenant name as the alias
ExtendedMetadata metadata = new ExtendedMetadata();
metadata.setLocal(true);
metadata.setAlias(tenantName);
//Create metadata provider and initialize it
ExtendedMetadataDelegate metadataDelegate = new ExtendedMetadataDelegate(httpMetadataProvider, metadata);
metadataDelegate.initialize();
//getEntityIdForAlias() in MetadataManager must only be called after the metadata provider
//is added and the metadata is refreshed. Otherwise, the alias will be mapped to a null
//value. The following code is a roundabout way to figure out whether the provider has already
//been added or not.
//The method parseProvider() has protected scope in MetadataManager so it was copied here
Set<String> newEntityIds = parseProvider(metadataDelegate);
Set<String> existingEntityIds = metadataManager.getIDPEntityNames();
//If one or more IDP entity ids do not exist in metadata manager, assume it's a new provider.
//If we always add a provider without this check, the initialize methods in refreshMetadata()
//ignore the provider in case of a duplicate but the duplicate still gets added to the list
//of providers because of the call to the superclass method addMetadataProvider(). Might be a bug.
if(!existingEntityIds.containsAll(newEntityIds)) {
metadataManager.addMetadataProvider(metadataDelegate);
metadataManager.refreshMetadata();
}
String entityId = metadataManager.getEntityIdForAlias(tenantName);
return new ModelAndView("redirect:/saml/login?idp=" + URLEncoder.encode(entityId, "UTF-8"));
}
private Set<String> parseProvider(MetadataProvider provider) throws MetadataProviderException {
Set<String> result = new HashSet<String>();
XMLObject object = provider.getMetadata();
if (object instanceof EntityDescriptor) {
addDescriptor(result, (EntityDescriptor) object);
} else if (object instanceof EntitiesDescriptor) {
addDescriptors(result, (EntitiesDescriptor) object);
}
return result;
}
private void addDescriptors(Set<String> result, EntitiesDescriptor descriptors) throws MetadataProviderException {
if (descriptors.getEntitiesDescriptors() != null) {
for (EntitiesDescriptor descriptor : descriptors.getEntitiesDescriptors()) {
addDescriptors(result, descriptor);
}
}
if (descriptors.getEntityDescriptors() != null) {
for (EntityDescriptor descriptor : descriptors.getEntityDescriptors()) {
addDescriptor(result, descriptor);
}
}
}
private void addDescriptor(Set<String> result, EntityDescriptor descriptor) throws MetadataProviderException {
String entityID = descriptor.getEntityID();
result.add(entityID);
}
}
I believe this directly solves the OP's problem of figuring out how to get the IDP for a given tenant. But this will work only for IDPs with a single entity id.
I'm having a set of Sping Data Repositories which are all exposed over Rest by using Spring-data-rest project. Now I want to secure the HTTP, so that only registered users can access the http://localhost:8080/rest/ So for this purpose I add #Secured(value = { "ROLE_ADMIN" }) to all the repositories and I also enable the security by specifying the
#EnableGlobalMethodSecurity(securedEnabled = true, jsr250Enabled = true, prePostEnabled = true)
So now what happens is I go to the rest and it's all good - i'm asked to authenticate. Next thing I do is I go to my website (which uses all the repositories to access the database) but my request fails with
nested exception is org.springframework.security.authentication.AuthenticationCredentialsNotFoundException: An Authentication object was not found in the SecurityContext
which is correct because i'm browsing my website as anonymous user.
So my question is: is there a way to provide method authentication for the REST layer only? To me it sounds like a new annotation is needed (something like #EnableRestGlobalMethodSecurity or #EnableRestSecurity)
I don't know if this will solve your problem, however I managed to get something similar, working for me by creating an event handler for my specific repository, and then used the #PreAuthorize annotation to check for permissions, say on beforeCreate. For example:
#RepositoryEventHandler(Account.class)
public class AccountEventHandler {
private final Logger logger = LoggerFactory.getLogger(getClass());
#PreAuthorize("isAuthenticated() and (hasRole('ROLE_USER'))")
#HandleBeforeCreate
public void beforeAccountCreate(Account account) {
logger.debug(String.format("In before create for account '%s'", account.getName()));
}
#PreAuthorize("isAuthenticated() and (hasRole('ROLE_ADMIN'))")
#HandleBeforeSave
public void beforeAccountUpdate(Account account) {
logger.debug(String.format("In before update for account '%s'", account.getName()));
//Don't need to add anything to this method, the #PreAuthorize does the job.
}
}
I'm new to Vaadin.
Before this I made a JSF Web Application.
I had a ManagedBean performing the login of the user. I used a security-realm to delegate the actual verification of the credentials.
How do I do this in Vaadin? Is there a best practice?
I'm at a point where I would just put something together, but there has to be some kind of standard-procedure, shouldn't it!?
I found some tutorials on this, but mostly using Spring (I want to use EJB).
Also, every tutorial seemed unneccessary complicated.
There has to be some simple+conclusive tutorial for something so common.
Here is the only Vaadin official article about how to secure a Vaadin aplication using JAAS that I could find. I mean native or official Vaadin Securization. You will find more if you google a little bit with Spring security, Shiro, but pure ESB uses JAAS.
I agree that this article is not enough to learn how to set up an application. I share here my experience:
1. Vaadin CDI extension:
<!-- Vaadin Official DI support. -->
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>vaadin-cdi</artifactId>
<version>1.0.0.alpha2</version>
</dependency>
2. Understanding about JAAS
I suggest you to have a look how JAAS works in a non-vaadin application, by reading overview articles, because JAAS depends on Server application provider (TomEE,Jboss, Wildfly, Glasfish) all have different configuration settings.
Here you will find a proof of concept using Tomee.
3. Developing your own Login Module.
If you have decided to create a custom login module for example:
public class MyLoginModule implements LoginModule {
private CallbackHandler handler;
private Subject subject;
private UserPrincipal userPrincipal;
private RolePrincipal rolePrincipal;
private String login;
private List<String> userGroups;
#Override
public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState,
Map<String, ?> options) {
handler = callbackHandler;
this.subject = subject;
}
#Override
public boolean login() throws LoginException {
Callback[] callbacks = new Callback[2];
callbacks[0] = new NameCallback("login");
callbacks[1] = new PasswordCallback("password", true);
try {
handler.handle(callbacks);
String name = ((NameCallback) callbacks[0]).getName();
String password = String.valueOf(((PasswordCallback) callbacks[1]).getPassword());
// Here we validate the credentials against some
// authentication/authorization provider.
// It can be a Database, an external LDAP, a Web Service, etc.
// For this tutorial we are just checking if user is "user123" and
// password is "pass123"
if (name != null && name.equals("admin") && password != null && password.equals("admin")) {
login = name;
userGroups = new ArrayList<String>();
userGroups.add("admin");
return true;
}
// If credentials are NOT OK we throw a LoginException
throw new LoginException("Authentication failed");
} catch (IOException e) {
throw new LoginException(e.getMessage());
} catch (UnsupportedCallbackException e) {
throw new LoginException(e.getMessage());
}
}
#Override
public boolean commit() throws LoginException {
userPrincipal = new UserPrincipal(login);
subject.getPrincipals().add(userPrincipal);
if (userGroups != null && userGroups.size() > 0) {
for (String groupName : userGroups) {
rolePrincipal = new RolePrincipal(groupName);
subject.getPrincipals().add(rolePrincipal);
}
}
return true;
}
#Override
public boolean abort() throws LoginException {
return false;
}
#Override
public boolean logout() throws LoginException {
subject.getPrincipals().remove(userPrincipal);
subject.getPrincipals().remove(rolePrincipal);
return true;
}
}
Reference it in the META-INF/context.xml of your application. This settings are for TomEE, but for glashfish or other must be similar.
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<Realm className="org.apache.catalina.realm.JAASRealm" appName="myrealm" userClassNames="net.sf.jaas.auth.UserPrincipal"
roleClassNames="net.sf.jaas.auth.RolePrincipal" />
</Context>
Be aware that userClassNames and roleClassNames are just simple Java Pojo but must implement java.security.Principal;
3. Enabling JAAS into your Vaadin Login View
Define your favourite login form, using Vaadin TextField and Password, and define in your doLoginEvent() a references to JAAS.
Whenever JaasAccessControl.login is called, a new instance of your LoginModule will be created.
import com.vaadin.cdi.access.JaasAccessControl;
try {
JaasAccessControl.login(loginEvent.getUsername(), loginEvent.getPassword());
logger.info("User {} authenticated", getPrincipalName());
navigator.navigateTo(Main.NAME);
} catch (Exception e) {
Notification.show("Error logging in", Type.ERROR_MESSAGE);
logger.error(e.getMessage(), e);
}
4. Using information of your logged user.
Whenever a user passes the JAAS login filter. It will be available in his request context a class that identifies him. This class is called Principal (the one you have previously set in the LoginModule class).
public boolean isUserInRole(String role) {
return JaasAccessControl.getCurrentRequest().isUserInRole(role);
}
public String getPrincipalName() {
Principal principal = JaasAccessControl.getCurrentRequest().getUserPrincipal();
if (principal != null) {
return principal.getName();
}
return null;
}
public boolean isUserSignedIn() {
Principal principal = JaasAccessControl.getCurrentRequest().getUserPrincipal();
return principal != null;
}
5. Alternatives to LoginModule
It's not mandatory to create your custom login Module, normally the Java EE providers as Tomee supply few implementation. Authentication based on properties file, in some database tables.
Have a look on Tomee documentation JAAS to see some examples:
6. Enable JAAS in TomEE
You need to create a jaas.config file which references you LoginModule, for example:
filename: jaas.config
myrealm{
net.sf.jaas.MyLoginModule required;
};
Then start the application server with this parameter:
-Djava.security.auth.login.config=E:/apache-tomee-jaxrs-1.6.0.2/conf/jaas.config
If you want to have a look a proof of concept of this. Checkout this example, which uses Tomee, Vaadin 7, JAAS,
I was running into the same problem a couple of months ago. I wasn't then able to figure out how it really works, so I went over the spring adddon and now I'm using a Vaadin login form and spring security
A full example that uses JAAS, Vaadin 8, Vaadin CDI add-on and the built-in LoginForm is available here, steps are as follows:
Enable the Vaadin CDI add-on in pom.xml
Enable CDI in Vaadin UI with #CDIUI annotation and configure the Navigator
Enable authorization in a CDI view by adding the #RolesAllowed annotation (or any other javax.annotation.security annotation)
Implement a login view that derives from the built-in LoginForm and uses JaasAccessControl to authenticate the user.
It is actually a rather nice, fluent experience once you figure out how the pieces fit together.
There's a longer article in Vaadin wiki that explains how to use database-backed authentication with JAAS.
There is an older thread that seems to be the only relevant discussion I have been able to find.
I am trying to implement Kerberos with Javamail (over IMAP) and I have gotten my self thoroughly confused on exactly what is to be done with mail.imap.sasl.mechanisms. Assume I give the value "GSS-API" but am kind of lost where to go from there. I notice that Javamail has an class IMAPSaslAuthernticator. It seems to me that this is what is needed but I can find precious little documentation on where or how to use it.
Any ideas?
NOTE: I wanted to post more code for my question, but according the site directions, full posts are only for answers. So, I have edited the code I originally posted question.
Below is the real meat. For now, once I pass this point I get the Message[] from the server and print the size to console.
SSL/TLS security is required so it is enabled below. In this example certificates are managed by a trusted keystore in Java.
private Folder folder;
private Session session;
private Store store;
public boolean connectToKerberosMail() {
if (folder != null && folder.isOpen()) {
return true;
}
Properties properties = new Properties();
properties.setProperty("mail.debug", "true");
properties.put("mail.imaps.connectiontimeout",600000);
properties.put("mail.imaps.timeout",601000);
properties.put("mail.imaps.fetchsize", 65000);
properties.put("mail.imaps.starttls.enable", "true");
properties.put("mail.imaps.starttls.required", "false");
properties.put("mail.imaps.sasl.enable","true");
properties.put("mail.imaps.sasl.mechanisms","GSSAPI");
properties.put("mail.imaps.sasl.authorizationid",<user>);
properties.put("mail.imaps.sasl.realm",<realm>);
System.setProperty( "sun.security.krb5.debug", "true");
System.setProperty( "java.security.krb5.realm",<realm>);
System.setProperty( "java.security.krb5.kdc", <ip-address>);
System.setProperty( "java.security.auth.login.config", "jaas.conf");
System.setProperty( "javax.security.auth.useSubjectCredsOnly", "false");
try {
session = Session.getInstance(properties);
} catch (Exception e) {
session = null;
return false;
}
session.setDebug(true);
URLName url = new URLName("imaps", <host>, <port>, "", <user>, <pass>);
store = new IMAPSSLStore(session, url);
try {
store.connect();
} catch (Exception e) {
e.printStackTrace();
store = null;
session = null;
return false;
}
return openFolder();
}
My jaas.conf file is as follows (the ticket cache was acquired from kinit):
com.sun.security.jgss.initiate {
com.sun.security.auth.module.Krb5LoginModule required
principal="<principal>"
ticketCache="<cache-path>"
doNotPrompt="true"
useTicketCache="true"
debug="true";
};
com.sun.security.jgss.accept {
com.sun.security.auth.module.Krb5LoginModule required
principal="<principal>"
ticketCache="<cache-path>"
doNotPrompt="true"
useTicketCache="true"
debug="true";
};
I recently posted here the output but noticed that some of my properties where designated "imap" instead of "imaps". So I am doing more testing before posintg output incase it changes.
In the mean time is what I have above correct? From what I understand I have to enable imap for the imap connection, startTLS for the TLS/SSL, and sasl for kerberos. But maybe something is overriding the another?
While not 100% the way there yet i made some discoveries. LOGIN was happening with the protocol in the NamedURL was "imap". I changed it to "imaps".
However, it look like javamail takes the protocol and host uses them to contruct the principal. protocol/host#realm? so I was applying to principal imaps/host#REALM which didnt exist so failed on a non-matching pricipals error.
So, we added this new principal to the servers and got past this.
But authentication is still failing. In the kerberos log I was approved and sent a ticket for accessing the mail. But I do not see it in my ticket cache (using klist) only the first ticket for accessing kerberos (I got from using kinit).
It seems that I say this in every response. I don't know how to get the word out....
You almost certainly want to change Session.getDefaultInstance() to Session.getInstance(), although that's probably not the source of your problems.
Anyway, what does the protocol trace show when you run your program? (emailSession.setDebug(true);)
I don't know enough about Kerberos, and especially how Kerberos works as a SASL mechanism, but aren't you going to have to specify some sort of password? Or can it get the appropriate Kerberos ticket without asking you to prove who you are?