How do you store users in a database with a new plain vanilla Grails 3.0 application?
Background:
The Shiro and Spring Security plugins are not yet available for Grails 3.0 (and it sounds like Spring Boot is the future for Grails security).
There are various examples out there showing how to use inMemoryAuthentication(), but they seem completely pointless as passwords end up being stored in plain text (besides, it only takes about 30 seconds of effort to create a domain model in Grails).
Pretty much all Grails applications need this functionality.
I happen to be using MongoDB, but that's probably irrelevant.
Related: Grails 3 and Spring Security Plugin
I currently have inMemoryAuthentication() working with the following:
build.gradle
compile "org.springframework.boot:spring-boot-starter-security"
grails-app/conf/spring/resources.groovy
import com.tincanworks.AppSecurityConfig
beans = {
webSecurityConfiguration(AppSecurityConfig)
}
AppSecurityConfig.groovy
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
class AppSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/assets/**").permitAll()
.antMatchers("/admin/**").hasAnyRole("admin")
.anyRequest().authenticated()
.and()
.formLogin().permitAll()
.and()
.logout().permitAll()
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("user").password("123456").roles("user")
.and()
.withUser("admin").password("1qaz2wsx").roles("user", "admin")
}
}
It seems like the answer may be related to JdbcDaoImpl, but I have no idea how to hook that up in Grails.
GORM-based
I wrote up two blog posts (part 1 - In Memory Auth and part 2 - Gorm-based Auth) on how to use spring-starter-security and GORM in a Grails 3 application. I also created a github repo with a working Grails 3 application using spring-starter-security.
JDBC-based - untested
Alternatively, if you wanted to use the standard JDBC-based authentication you could just create the database tables using the following SQL script
HSQLDB
From http://docs.spring.io/spring-security/site/docs/3.0.x/reference/appendix-schema.html
create table users(
username varchar_ignorecase(50) not null primary key,
password varchar_ignorecase(50) not null,
enabled boolean not null);
create table authorities (
username varchar_ignorecase(50) not null,
authority varchar_ignorecase(50) not null,
constraint fk_authorities_users foreign key(username) references users(username));
create unique index ix_auth_username on authorities (username,authority);
MySQL
This comes from http://justinrodenbostel.com/2014/05/30/part-5-integrating-spring-security-with-spring-boot-web/
create table users (
username varchar(50) not null primary key,
password varchar(255) not null,
enabled boolean not null) engine = InnoDb;
create table authorities (
username varchar(50) not null,
authority varchar(50) not null,
foreign key (username) references users (username),
unique index authorities_idx_1 (username, authority)) engine = InnoDb;
and then change the configureGlobal method to
#Autowired //not sure if this is needed as you have the AppSecurityConfig bean referenced in resources.groovy
def datasource //bean injected by Grails
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.jdbcAuthentication()
.dataSource(datasource)
}
If you want to avoid building a whole User-management layer from scratch with a DB you could consider Stormpath.
Among other things, they provide a Spring Security Plugin that uses Stormpath as the authentication and authorization provider. They also have a sample Spring Security app that shows how the plugin is used. Since you are using Java Annotations (rather than xml configuration) take a look at this branch.
So, in summary, the key pieces that you will need to define are these:
The Stormpath Client Bean that will provide fast and secure communication with Stormpath via the Stormpath Java SDK:
//Let's create the Stormpath client using the apiKey.properties file from the User's home folder.
#Bean
ClientFactory stormpathClient(CacheManager cacheManager) {
ClientFactory clientFactory = new ClientFactory();
clientFactory.setApiKeyFileLocation(System.getProperty("user.home") + File.separator + ".stormpath" + File.separator + "apiKey.properties");
clientFactory.setCacheManager(cacheManager);
return clientFactory;
}
You will need to define the Stormpath Authentication Provider so Spring Security can transparently communicate with Stormpath to authenticate and authorize users:
#Bean
#Autowired
public StormpathAuthenticationProvider stormpathAuthenticationProvider(Client client, String applicationRestUrl) throws Exception {
StormpathAuthenticationProvider stormpathAuthenticationProvider = new StormpathAuthenticationProvider();
stormpathAuthenticationProvider.setClient(client);
stormpathAuthenticationProvider.setApplicationRestUrl(applicationRestUrl);
return stormpathAuthenticationProvider;
}
The applicationRestUrl needs to point to the Stormpath application where all the users/groups will exist:
#Bean
public String getApplicationRestUrl() {
return "https://api.stormpath.com/v1/applications/9TqbyZ2po73eDP4gYo2H92";
}
Your Spring Security configuration needs to be configured to use the Stormpath Authentication Provider:
//Let's add the StormpathAuthenticationProvider to the `AuthenticationProvider`
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(stormpathAuthenticationProvider);
}
Finally, in order to restrict the access to resources by roles, you will need to define the roles. For example:
//The access control settings are defined here
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.and()
.authorizeRequests()
.accessDecisionManager(accessDecisionManager())
.antMatchers("/account/*").hasAuthority("https://api.stormpath.com/v1/groups/36O9eBTN2oLtjoMSWLdnwL") //you are giving access to "/account/*" to users' that belong to the group univocally identified by this href value
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/index.jsp")
.and()
.httpBasic()
.and()
.csrf().disable();
}
Disclaimer, I am an active Stormpath contributor.
Related
I created a project following the steps of the sample project at https://github.com/teiid/teiid-spring-boot/tree/master/samples/mongodb and added org.teiid:spring-odata dependency for OData exposure.
I find out that it exposed all collections in the MongoDB database as OData entities by default. Would it possible to configure it to expose specific collections only?
Updated:
You can add following to application.properties,
spring.teiid.data.mongodb.accounts.remoteServerList=localhost:27017
spring.teiid.data.mongodb.accounts.database=sampledb
spring.teiid.data.mongodb.accounts.user=admin
spring.teiid.data.mongodb.accounts.password=admin
spring.teiid.data.mongodb.accounts.authDatabase=admin
spring.teiid.data.mongodb.accounts.importer.excludeTables=.*
where "accounts" is the bean name. See "importer properties"
http://teiid.github.io/teiid-documents/master/content/reference/MongoDB_Translator.html
Then to configure the data source
#Configuration
public class DataSources {
#Bean
public MongoDBConnectionFactory accounts(#Qualifier("config") #Autowired MongoDBConfiguration config) {
return new MongoDBConnectionFactory(new MongoDBTemplate(config));
}
#ConfigurationProperties("spring.teiid.data.mongodb.accounts")
#Bean("config")
public MongoDBConfiguration mongoConfig() {
return new MongoDBConfiguration();
}
}
The above is when strictly talking about exposing MongoDB alone without any other changes.
I'm able to Authenticate user using Apache Shiro by passing username and password. But if i have pass encrypted data, then how will i achieve this?
I have tried the below:
public static void main(String[] args) {
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager);
JcaCipherService jcaCipherService =new DefaultBlockCipherService("RSA") ;
jcaCipherService.decrypt("UOK4yfsmoavRDq+5NiYuMh2s2KC9GBzUCra4MGIx+7ERbdb0IRGsQZxDza7kir/OAupq18Vzm8cZzaHoKeC/TA==".getBytes(), "MIIBVwIBADANBgkqhk".getBytes());
But is it giving me the below error:
Exception in thread "main" org.apache.shiro.crypto.CryptoException: Unable to acquire a Java JCA Cipher instance using javax.crypto.Cipher.getInstance( "RSA/CBC/PKCS5Padding" ). RSA under this configuration is required for the org.apache.shiro.crypto.DefaultBlockCipherService instance to function.
at org.apache.shiro.crypto.JcaCipherService.newCipherInstance(JcaCipherService.java:414)
at org.apache.shiro.crypto.JcaCipherService.initNewCipher(JcaCipherService.java:591)
at org.apache.shiro.crypto.JcaCipherService.crypt(JcaCipherService.java:444)
at org.apache.shiro.crypto.JcaCipherService.decrypt(JcaCipherService.java:390)
at org.apache.shiro.crypto.JcaCipherService.decrypt(JcaCipherService.java:382)
at com.thbs.esb.authenticate.Authenticate.main(Authenticate.java:24)
Caused by: java.security.NoSuchAlgorithmException: Cannot find any provider supporting RSA/CBC/PKCS5Padding
at javax.crypto.Cipher.getInstance(Cipher.java:539)
at org.apache.shiro.crypto.JcaCipherService.newCipherInstance(JcaCipherService.java:408)
... 5 more
How i can integrate the Keycloak with Payara Micro?
I want create an stateless REST JAX-RS application that use the Keycloak as authentication and authorization server, but i unknown how do it.
The Eclipse MicroProfile JWT Authentication API defines the #LoginConfig annotation:
#LoginConfig(authMethod = "MP-JWT", realmName = "admin-realm")
#ApplicationPath("/")
public class MyApplication extends Application {...}
And the java EE the #RolesAllowed annotation:
#Path("/api/v1/books")
public class BooksController {
#GET
#RolesAllowed("read-books")
public Books findAll() {...}
}
How integrate these two things?
Keycloak project doesn't provide a native adapter for Payara Server or Payara Micro and the Payara project doesn't provide it either.
But Keycloak also provides a generic servlet filter adapter which should also use with Payara Micro: https://www.keycloak.org/docs/latest/securing_apps/index.html#_servlet_filter_adapter
Just add the keycloak-servlet-filter-adapter dependency into your web application and configure the adapter in the web.xml according to the documentation. I haven't tested it though, so I don't know if it really works.
I faced the same challenge in a personal project and as is mentioned Keycloak project does not provide a native adapter for Payara, in that moment I did a library to secure my app with Keycloak, if you like, you can take it a look and let me know if it's ok or how we can improve it.
https://github.com/pablobastidasv/kc_security
You can find solution in The Payara Monthly Roundup for April 2019
MicroProfile JWT with Keycloak - In this step by step blog, Hayri Cicekā demonstrates how to secure your services using MicroProfile JWT and Keycloak.
Init LoginConfig and map your roles using DeclareRoles
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
import org.eclipse.microprofile.auth.LoginConfig;
import javax.annotation.security.DeclareRoles;
#LoginConfig(authMethod = "MP-JWT")
#ApplicationPath("/")
#DeclareRoles({ "mysimplerole", "USER" })
public class ApplicationConfig extends Application {
}
Add params to microprofile-config.properties
mp.jwt.verify.publickey.location=http://localhost:8084/auth/realms/public/protocol/openid-connect/certs
mp.jwt.verify.issuer=http://localhost:8084/auth/realms/public
And you can use your roles in RolesAllowed
#ApplicationScoped
#Path("/hello")
public class HelloWorldEndpoint {
#GET
#Produces("text/plain")
#RolesAllowed("mysimplerole")
public Response doGet() {
return Response.ok("Hello from MicroProfile!").build();
}
}
I am trying to migrate an application from EJB3 + JTA + JPA (EclipseLink). Currently, this application makes use of application managed persistent context due to an unknown number of databases on design time.
The application managed persistent context allows us to control how to create EntityManager (e.g. supply different datasources JNDI to create proper EntityManager for specific DB on runtime).
E.g.
Map properties = new HashMap();
properties.put(PersistenceUnitProperties.TRANSACTION_TYPE, "JTA");
//the datasource JNDI is by configuration and without prior knowledge about the number of databases
//currently, DB JNDI are stored in a externalized file
//the datasource is setup by operation team
properties.put(PersistenceUnitProperties.JTA_DATASOURCE, "datasource-jndi");
properties.put(PersistenceUnitProperties.CACHE_SHARED_DEFAULT, "false");
properties.put(PersistenceUnitProperties.SESSION_NAME, "xxx");
//create the proper EntityManager for connect to database decided on runtime
EntityManager em = Persistence.createEntityManagerFactory("PU1", properties).createEntityManager();
//query or update DB
em.persist(entity);
em.createQuery(...).executeUpdate();
When deployed in a EJB container (e.g. WebLogic), with proper TransactionAttribute (e.g. TransactionAttributeType.REQUIRED), the container will take care of the transaction start/end/rollback.
Now, I am trying to migrate this application to Spring Boot.
The problem I encounter is that there is no transaction started even after I annotate the method with #Transactional(propagation = Propagation.REQUIRED).
The Spring application is packed as an executable JAR file and run with embadded Tomcat.
When I try to execute those update APIs, e.g. EntityManager.persist(..), EclipseLink always complains about:
javax.persistence.TransactionRequiredException: 'No transaction is currently active'
Sample code below:
//for data persistence
#Service
class DynamicServiceImpl implements DynamicService {
//attempt to start a transaction
#Transactional(propagation = Propagation.REQUIRED)
public void saveData(DbJndi, EntityA){
//this return false that no transaction started
TransactionSynchronizationManager.isActualTransactionActive();
//create an EntityManager based on the input DbJndi to dynamically
//determine which DB to save the data
EntityManager em = createEm(DbJndi);
//save the data
em.persist(EntityA);
}
}
//restful service
#RestController
class RestController{
#Autowired
DynamicService service;
#RequestMapping( value = "/saveRecord", method = RequestMethod.POST)
public #ResponseBody String saveRecord(){
//save data
service.saveData(...)
}
}
//startup application
#SpringBootApplication
class TestApp {
public static void main(String[] args) {
SpringApplication.run(TestApp.class, args);
}
}
persistence.xml
-------------------------------------------
<persistence-unit name="PU1" transaction-type="JTA">
<properties>
<!-- comment for spring to handle transaction??? -->
<!--property name="eclipselink.target-server" value="WebLogic_10"/ -->
</properties>
</persistence-unit>
-------------------------------------------
application.properties (just 3 lines of config)
-------------------------------------------
spring.jta.enabled=true
spring.jta.log-dir=spring-test # Transaction logs directory.
spring.jta.transaction-manager-id=spring-test
-------------------------------------------
My usage pattern does not follow most typical use cases (e.g. with known number of DBs - Spring + JPA + multiple persistence units: Injecting EntityManager).
Can anybody give me advice on how to solve this issue?
Is there anybody who has ever hit this situation that the DBs are not known in design time?
Thank you.
I finally got it work with:
Enable tomcat JNDI and create the datasource JNDI to each DS programmatically
Add transaction stuff
com.atomikos:transactions-eclipselink:3.9.3 (my project uses eclipselink instead of hibernate)
org.springframework.boot:spring-boot-starter-jta-atomikos
org.springframework:spring-tx
You have pretty much answered the question yourself: "When deployed in a EJB container (e.g. WebLogic), with proper TransactionAttribute (e.g. TransactionAttributeType.REQUIRED), the container will take care of the transaction start/end/rollback".
WebLogic is compliant with the Java Enterprise Edition specification which is probably why it worked before, but now you are using Tomcat (in embedded mode) which are NOT.
So you simply cannot do what you are trying to do.
This statement in your persistence.xml file:
<persistence-unit name="PU1" transaction-type="JTA">
requires an Enterprise Server (WebLogic, Glassfish, JBoss etc.)
With Tomcat you can only do this:
<persistence-unit name="PU1" transaction-type="RESOURCE_LOCAL">
And you need to handle transactions by your self:
myEntityManager.getTransaction.begin();
... //Do your transaction stuff
myEntityManager.getTransaction().commit();
I have migrated my EJB application from jboss 5.0.1 to JBOSS EAP 7.
I want to pass user data from EJB client to my EJB.
I'm using this code to pass custom attribute to ejb server but it does not work anymore.
Client:
public class CustomData extends SimplePrincipal{
String userData1;
public CustomData(String userData1){
this.userData1 = userData1;
}
SecurityClient client = SecurityClientFactory.getSecurityClient();
client.setSimple(new CustomData("MyData"), credentials.getPass());
client.login();
Server:
#Resource
SessionContext ejbCtx;
Principal data= ejbCtx.getCallerPrincipal();
data.getName() --- anonymous
How to fix it on new JBOSS ?
1.Create the client side interceptor
This interceptor must implement the org.jboss.ejb.client.EJBClientInterceptor. The interceptor is expected to pass the additional security token through the context data map, which can be obtained via a call to EJBClientInvocationContext.getContextData().
2.Create and configure the server side container interceptor
Container interceptor classes are simple Plain Old Java Objects (POJOs). They use the #javax.annotation.AroundInvoke to mark the method that is invoked during the invocation on the bean.
a.Create the container interceptor
This interceptor retrieves the security authentication token from the context and passes it to the JAAS (Java Authentication and Authorization Service) domain for verification
b. Configure the container interceptor
3.Create the JAAS LoginModule
This custom module performs the authentication using the existing authenticated connection information plus any additional security token.
Add the Custom LoginModule to the Chain
You must add the new custom LoginModule to the correct location the chain so that it is invoked in the correct order. In this example, the SaslPlusLoginModule must be chained before the LoginModule that loads the roles with the password-stacking option set.
a.Configure the LoginModule Order using the Management CLI
The following is an example of Management CLI commands that chain the custom SaslPlusLoginModule before the RealmDirect LoginModule that sets the password-stacking option.
b. Configure the LoginModule Order Manually
The following is an example of XML that configures the LoginModule order in the security subsystem of the server configuration file. The custom SaslPlusLoginModule must precede the RealmDirect LoginModule so that it can verify the remote user before the user roles are loaded and the password-stacking option is set.
Create the Remote Client
In the following code example, assume the additional-secret.properties file accessed by the JAAS LoginModule
See the link:
https://access.redhat.com/documentation/en-US/JBoss_Enterprise_Application_Platform/6.2/html/Development_Guide/Pass_Additional_Security_For_EJB_Authentication.html
I have done with this way:
Client:
Properties properties = new Properties();
properties.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
properties.put("org.jboss.ejb.client.scoped.context", "true");
properties.put("remote.connection.default.username", "MyData");
Server:
public class MyContainerInterceptor{
#AroundInvoke
public Object intercept(InvocationContext ctx) throws Exception {
Connection connection = RemotingContext.getConnection();
if (connection != null) {
for (Principal p : connection.getPrincipals()) {
if (p instanceof UserPrincipal) {
if (p.getName() != null && !p.getName().startsWith("$"))
System.out.println(p.getName()); //MyData will be printed
}
}
}
return ctx.proceed();
}
}
Don't forget to configure container interceptor in jboss-ejb3.xml (not in ejb-jar.xml)
<?xml version="1.0" encoding="UTF-8"?>
<jee:assembly-descriptor>
<ci:container-interceptors>
<jee:interceptor-binding>
<ejb-name>*</ejb-name>
<interceptor-class>package...MyContainerInterceptor</interceptor-class>
</jee:interceptor-binding>
</ci:container-interceptors>
</jee:assembly-descriptor>