Spring Boot + Mongo DB + Shiro configuration - spring

I am developing a web-based application in Spring boot and Mongo DB. Now I want to use Apache Shiro for Authentication and Authorisation. Can somebody explain to me the procedure and how to establish a mongo db realm and where to mention the permission-user mapping? Thank You.

Basically you need three component
#Component
public class YourMongoConfiguration {
#Bean(name = "mongoTemplate")
#DependsOn({ "lifecycleBeanPostProcessor" })
public MongoTemplate mongoTemplate() throws Exception {
MongoTemplate mt = new MongoTemplate(YOUR_CONFIGURATIOP_HERE);
return mt;
}
}
Then a MongoRealm
#Component("mongoRealm")
public class MongoRealm extends AuthorizingRealm {
private final MongoTemplate mongoTemplate;
#Autowired
public MongoRealm(MongoTemplate mongoTemplate) {
this.mongoTemplate = mongoTemplate;
HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
credentialsMatcher.setHashAlgorithmName(Sha512Hash.ALGORITHM_NAME);
credentialsMatcher.setHashIterations(53);
setCredentialsMatcher(credentialsMatcher);
}
#Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// YOUR IMPLEMENTATION
}
#Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)
throws AuthenticationException {
// YOUR IMPLEMENTATION
}
}
and finally a security manager
#Component("securityManager")
public class SecurityManager extends DefaultWebSecurityManager {
#Autowired
public SecurityManager(Realm mongoRealm, SessionDAO mongoSessionDAO) {
super(mongoRealm);
setRealm(mongoRealm);
SessionManager sessionManager = new SessionManager();
setSessionManager(sessionManager);
sessionManager.setSessionDAO(mongoSessionDAO);
}
}
From now on either Shiro will call your MongoRealm to validate login and permission and you will be able to hadle your collection with classes like
#Service
public class ONE_OF_YOUR_Services {
#Autowired
private MongoTemplate mongoTemplate;
protected List<T> getDocuments(Class<T> clazz, String collection) {
return mongoTemplate.findAll(clazz, collection);
}
}
I hope it helps.

There are a few MongoDB realms up on GitHub. I don't want to link to them as haven't tried them out, but that would be your best place to start.

Related

Change Multitenancy sessions manually

I need to create a multitenanacy application with ability to switch between schemas inside my java-code (not based on a user request).
I've read articles:
https://fizzylogic.nl/2016/01/24/make-your-spring-boot-application-multi-tenant-aware-in-2-steps/
http://www.greggbolinger.com/tenant-per-schema-with-spring-boot/
Solution works fine, when the schema is passed in Rest-request.
However I need to implement the following logic:
public void compare(String originalSchema, String secondSchema){
TenantContext.setCurrentTenant(originalSchema);
List<MyObject> originalData = myRepository.findData();
TenantContext.setCurrentTenant(secondSchema);
List<MyObject> migratedData = myRepository.findData();
}
The point is, that connection is not switched, when I manually set up TenenantContext. MultiTenantConnectionProviderImpl.getConnection is invoked only on the first call to my repository.
#Component
public class MultiTenantConnectionProviderImpl implements MultiTenantConnectionProvider {
#Override
public Connection getConnection(String tenantIdentifier) throws SQLException {
final Connection connection = getAnyConnection();
try {
connection.createStatement().execute( "ALTER SESSION SET CURRENT_SCHEMA = " + tenantIdentifier );
}
catch ( SQLException e ) {
throw new HibernateException(
"Could not alter JDBC connection to specified schema [" + tenantIdentifier + "]",e);
}
return connection;
}
}
Is it possible to force switching sessions?
Found a hard-coded solution.
#Service
public class DatabaseSessionManager {
#PersistenceUnit
private EntityManagerFactory entityManagerFactory;
public void bindSession() {
if (!TransactionSynchronizationManager.hasResource(entityManagerFactory)) {
EntityManager entityManager = entityManagerFactory.createEntityManager();
TransactionSynchronizationManager.bindResource(entityManagerFactory, new EntityManagerHolder(entityManager));
}
}
public void unbindSession() {
EntityManagerHolder emHolder = (EntityManagerHolder) TransactionSynchronizationManager
.unbindResource(entityManagerFactory);
EntityManagerFactoryUtils.closeEntityManager(emHolder.getEntityManager());
}
}
Each block, loading data in a new tenantContext should execute the following:
databaseSessionManager.unbindSession();
TenantContext.setCurrentTenant(schema);
databaseSessionManager.bindSession();
//execute selects
Well, you need it
public interface Service {
List<MyObject> myObjects();
}
#Service
#Transactional(propagation = Propagation.REQUIRES_NEW)
public class ServiceImpl implements Service {
#Autowired
private MyRepository myRepository;
#Override
public List<MyObject> myObjects() {
return myRepository.findData();
}
}
#Service
public class AnotherService() {
#Autowired
private Service service;
public void compare(String originalSchema, String secondSchema){
TenantContext.setCurrentTenant(originalSchema);
List<MyObject> originalData = service.myObjects();
TenantContext.setCurrentTenant(secondSchema);
List<MyObject> migratedData = service.myObjects();
}
}
Try using
spring.jpa.open-in-view=false
in your application.properties file.
More info on this
What is this spring.jpa.open-in-view=true property in Spring Boot?
Hope this helps..

unable to use #AspectJ with Spring-Apache CXF services

I am new to spring and am working on a rest service written using Spring and Apache CXF with Java Configurations. I have the following rest service.
#Path("/release/")
#Component
#RestService
#Consumes({ MediaType.APPLICATION_JSON })
#Produces({ MediaType.APPLICATION_JSON })
#Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
public class ReleaseResource extends AbstractService implements IResource {
#Override
#CustomLogger
#GET
public Response get() {
//Some Logic
return Response.ok("Success!!").build();
}
}
I have created an aspect using #AspectJ for logging. However, the aspect is not working on the services written in CXF. I did a bit of searching in net and found that Spring needs proxy beans for the aspects to work. Then I tried few approaches such as
Making the service class implement an interface
Using CGLIB library and scope proxy mode TARGET_CLASS
Extending a class with method
#Override
public void setMessageContext(MessageContext context) {
this.context = context;
}
But none of them worked.
Any idea if it is possible to run the aspect around the services?
If yes, can someone please tell me how to.
I have read that this can be achieved by bytecode weaving the aspectj manually instead of using spring aspectj autoproxy (not sure how to do it though). Can someone tell me if this is a good option and how to do it?
EDIT:
Sorry for the incomplete info provided. Attaching the other classes
#Aspect
#Configuration
public class LoggerAspect {
#Pointcut(value = "execution(* *(..))")
public void anyPublicMethod() {
}
#Around("anyPublicMethod() && #annotation(CustomLogger)")
public Object logAction(ProceedingJoinPoint pjp, CustomLogger customLogger) throws Throwable {
//Log Some Info
return pjp.proceed();
}
}
Web Initializer class:
#Configuration
public class WebInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.addListener(new ContextLoaderListener(createWebAppContext()));
addApacheCxfServlet(servletContext);
}
private void addApacheCxfServlet(ServletContext servletContext) {
CXFServlet cxfServlet = new CXFServlet();
ServletRegistration.Dynamic appServlet = servletContext.addServlet("CXFServlet", cxfServlet);
appServlet.setLoadOnStartup(1);
appServlet.addMapping("/*");
}
private WebApplicationContext createWebAppContext() {
AnnotationConfigWebApplicationContext appContext = new AnnotationConfigWebApplicationContext();
appContext.register(TestConfig.class);
return appContext;
}
}
Config Class:
#Configuration
#ComponentScan(basePackages = "com.my.package")
#EnableAspectJAutoProxy(proxyTargetClass = true)
public class TestConfig {
private static final String RESOURCES_PACKAGE = "com.my.package";
#ApplicationPath("/")
public class JaxRsApiApplication extends Application {
}
#Bean(destroyMethod = "shutdown")
public SpringBus cxf() {
return new SpringBus();
}
#Bean
public JacksonJsonProvider jacksonJsonProvider() {
return new JacksonJsonProvider();
}
#Bean
public LoggerAspect getLoggerAspect() {
return new LoggerAspect();
}
#Bean
IResource getReleaseResource() {
return new ReleaseResource();
}
#Bean
#DependsOn("cxf")
public Server jaxRsServer(ApplicationContext appContext) {
JAXRSServerFactoryBean factory = RuntimeDelegate.getInstance().createEndpoint(jaxRsApiApplication(),
JAXRSServerFactoryBean.class);
factory.setServiceBeans(restServiceList(appContext));
factory.setProvider(jacksonJsonProvider());
return factory.create();
}
private List<Object> restServiceList(ApplicationContext appContext) {
return RestServiceBeanScanner.scan(appContext, TestConfig.RESOURCES_PACKAGE);
}
#Bean
public JaxRsApiApplication jaxRsApiApplication() {
return new JaxRsApiApplication();
}
}
RestServiceBeanScanner class
public final class RestServiceBeanScanner {
private RestServiceBeanScanner() {
}
public static List<Object> scan(ApplicationContext applicationContext, String... basePackages) {
GenericApplicationContext genericAppContext = new GenericApplicationContext();
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(genericAppContext, false);
scanner.addIncludeFilter(new AnnotationTypeFilter(RestService.class));
scanner.scan(basePackages);
genericAppContext.setParent(applicationContext);
genericAppContext.refresh();
List<Object> restResources = new ArrayList<>(
genericAppContext.getBeansWithAnnotation(RestService.class).values());
return restResources;
}
}

Register a CustomConverter in a MongoTemplate with Spring Boot

How can I register a custom converter in my MongoTemplate with Spring Boot? I would like to do this only using annotations if possible.
I just register the bean:
#Bean
public MongoCustomConversions mongoCustomConversions() {
List list = new ArrayList<>();
list.add(myNewConverter());
return new MongoCustomConversions(list);
}
Here is a place in source code where I find it
If you only want to override the custom converters portion of the Spring Boot configuration, you only need to create a configuration class that provides a #Bean for the custom converters. This is handy if you don't want to override all of the other Mongo settings (URI, database name, host, port, etc.) that Spring Boot has wired in for you from your application.properties file.
#Configuration
public class MongoConfig
{
#Bean
public CustomConversions customConversions()
{
List<Converter<?, ?>> converterList = new ArrayList<Converter<?, ?>>();
converterList.add(new MyCustomWriterConverter());
return new CustomConversions(converterList);
}
}
This will also only work if you've enabled AutoConfiguration and excluded the DataSourceAutoConfig:
#SpringBootApplication(scanBasePackages = {"com.mypackage"})
#EnableMongoRepositories(basePackages = {"com.mypackage.repository"})
#EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
public class MyApplication
{
public static void main(String[] args)
{
SpringApplication.run(MyApplication.class, args);
}
}
In this case, I'm setting a URI in the application.properties file and using Spring data repositories:
#mongodb settings
spring.data.mongodb.uri=mongodb://localhost:27017/mydatabase
spring.data.mongodb.repositories.enabled=true
You need to create a configuration class for converter config.
#Configuration
#EnableAutoConfiguration(exclude = { EmbeddedMongoAutoConfiguration.class })
#Profile("!testing")
public class MongoConfig extends AbstractMongoConfiguration {
#Value("${spring.data.mongodb.host}") //if it is stored in application.yml, else hard code it here
private String host;
#Value("${spring.data.mongodb.port}")
private Integer port;
#Override
protected String getDatabaseName() {
return "test";
}
#Bean
public Mongo mongo() throws Exception {
return new MongoClient(host, port);
}
#Override
public String getMappingBasePackage() {
return "com.base.package";
}
#Override
public CustomConversions customConversions() {
List<Converter<?, ?>> converters = new ArrayList<>();
converters.add(new LongToDateTimeConverter());
return new CustomConversions(converters);
}
}
#ReadingConverter
static class LongToDateTimeConverter implements Converter<Long, Date> {
#Override
public Date convert(Long source) {
if (source == null) {
return null;
}
return new Date(source);
}
}

Spring security CustomPermissionEvaluator not working

MethodSecurityConfig.java
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled=true)
#ComponentScan(basePackageClasses={EventWritePermissionEvaluator.class})
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration{
private EventWritePermissionEvaluator eventWritePermissionEvaluator;
#Autowired
public void setEventWritePermissionEvaluator(
EventWritePermissionEvaluator eventWritePermissionEvaluator) {
this.eventWritePermissionEvaluator = eventWritePermissionEvaluator;
}
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler=new DefaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(eventWritePermissionEvaluator);
return expressionHandler;
}
}
CustomPermissionEvaluator
#Component
public class EventWritePermissionEvaluator implements PermissionEvaluator{
private ChecklistService checklistService;
private UserService userService;
#Autowired
public void setChecklistService(ChecklistService checklistService) {
this.checklistService = checklistService;
}
#Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
public CustomUser currentUser()
{
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
CustomUser customUser=(CustomUser) userService.loadUserByUsername(auth.getName());
return customUser;
}
#Override
public boolean hasPermission(Authentication authentication,
Object targetDomainObject, Object permission) {
Checklist checklist=(Checklist) targetDomainObject;
Event event=checklistService.getChecklist(checklist.getId()).getEvent();
String grp=event.getCreator().getGrp();
System.out.println("event grp:"+grp);
System.out.println("user grp:"+currentUser().getGrp());
if(currentUser().getGrp().equals(grp))
return true;
else
return false;
}
#Override
public boolean hasPermission(Authentication authentication,
Serializable targetId, String targetType, Object permission) {
return true;
}
}
ServiceMethod
#PreAuthorize("hasPermission(#ch,'write')")
public Map<String, Object> updateState(Checklist ch, HttpServletRequest request, HttpServletResponse response) throws MessagingException
{
}
The hasPermission() methods which i wrote in permissionEvaluator class are not getting invoked for incoming requests to service layer. Did i wrote anything wrong? i wrote some console statements in hasPermission() methods to see their execution. but i did not see anything in the console.
Thanks
What are you trying to achieve? It seems like I can achieve exactly the same thing just by using UserDetailsService implementation from latest Spring Security.
This is my blog post about it
Implementing UserDetailsService:
http://www.yjsblog.com/2015/10/05/how-to-implement-custom-spring-security-authentication-with-userdetailsservice/
Implementing Role as property on an entity:
http://www.yjsblog.com/2015/10/05/userdetails-role-from-database-or-as-an-entity-property/
Please have a look at above links.
Cheers,

Spring jdbc configuration

I have been trying to implement a web service using spring. This webservice will provide data access to a mySQL database using JDBC. I am trying to not use any xml configuration files, so I have come across a problem trying to connect to the database.
I am following the tutorial: http://spring.io/guides/tutorials/rest/ but I changed a few things along the way.
Now that I am trying to implement the connection with the database I get an error when trying to execute the tomcat instance, and I guess the problem is within the configurations.
Here follows some of my code:
Datasource configuration:
#Configuration
#Profile("mySQL")
#PropertySource("classpath:/services.properties")
public class MySQLDataSourceConfiguration implements DataSourceConfiguration{
#Inject
private Environment environment;
#Bean
public DataSource dataSource() throws Exception {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setPassword(environment.getProperty("dataSource.password"));
dataSource.setUrl(environment.getProperty("dataSource.url"));
dataSource.setUsername(environment.getProperty("dataSource.user"));
dataSource.setDriverClassName(environment.getPropertyAsClass("dataSource.driverClass", Driver.class).getName());
return dataSource;
}
}
the file service.properties is where I keep my configurations for the database, so when I desire to change the database I will just have to change 4 fields.
The JDBCConfiguration class for the setup of the JDBCtemplate
#Configuration
#EnableTransactionManagement
#PropertySource("classpath:/services.properties")
#Import( { MySQLDataSourceConfiguration.class })
public class JdbcConfiguration {
#Autowired
private DataSourceConfiguration dataSourceConfiguration;
#Inject
private Environment environment;
#Bean
public JdbcTemplate setupJdbcTemplate() throws Exception {
return new JdbcTemplate(dataSourceConfiguration.dataSource());
}
#Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) throws Exception {
return new DataSourceTransactionManager(dataSource);
}
}
Then there is the Repository, that recieves the template.
#Transactional
#Repository
#Qualifier("jdbcRepository")
public class JdbcIndividualRepository implements IndividualsRepository{
private static final Logger LOG = LoggerFactory.getLogger(JdbcIndividualRepository.class);
#Autowired
private JdbcTemplate jdbcTemplate;
#Autowired
public JdbcIndividualRepository(DataSource jdbcDataSource) {
LOG.info("JDBCRepo arg constructor");
this.jdbcTemplate = new JdbcTemplate(jdbcDataSource);
}
#Override
public Individual save(Individual save) {
String sql = "INSERT INTO Individual(idIndividual, Name) VALUES(?,?)";
this.jdbcTemplate.update(sql, save.getId(), save.getName());
return save;
}
#Override
public void delete(String key) {
String sql = "DELETE FROM Individual WHERE idIndividual=?";
jdbcTemplate.update(sql, key);
}
#Override
public Individual findById(String key) {
String sql = "SELECT i.* FROM Individual i WHERE i.idIndividual=?";
return this.jdbcTemplate.queryForObject(sql, new IndividualRowMapper(), key);
}
#Override
public List<Individual> findAll() {
String sql = "SELECT * FROM Individual";
return new LinkedList<Individual>(this.jdbcTemplate.query(sql, new IndividualRowMapper()));
}
}
Then I register the jdbc configuration in the initializer class when creating the root context of the application as follows:
private WebApplicationContext createRootContext(ServletContext servletContext) {
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(CoreConfig.class, SecurityConfig.class, JdbcConfiguration.class);
rootContext.refresh();
servletContext.addListener(new ContextLoaderListener(rootContext));
servletContext.setInitParameter("defaultHtmlEscape", "true");
return rootContext;
}
However, the Tomcat server wont run because it can't autowire the class MySQLDataSourceConfiguration.
Anyone knows what the problem might be? I can give more details on the code, but the question is already really large.
Appreciate any kind of help!
Cheers
EDIT
Solved changing the JdbcConfiguration class to:
#Configuration
#EnableTransactionManagement
#PropertySource("classpath:/services.properties")
#Import( { MySQLDataSourceConfiguration.class })
public class JdbcConfiguration {
#Autowired
private DataSource dataSource;
#Inject
private Environment environment;
#Bean
public JdbcTemplate setupJdbcTemplate() throws Exception {
return new JdbcTemplate(dataSource);
}
#Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) throws Exception {
return new DataSourceTransactionManager(dataSource);
}
#Bean
public IndividualsRepository createRepo(){
return new JdbcIndividualRepository(dataSource);
}
}
Remove
#Autowired
private DataSourceConfiguration dataSourceConfiguration;
Because that's not how it's supposed to be used. Instead add to the same class the following:
#Autowired DataSource dataSource;
and use it like this: new JdbcTemplate(dataSource);
Also, try adding #ComponentScan to JdbcConfiguration class. From what I see in your code the class JdbcIndividualRepository is not picked up by anything.
In your class JdbcConfiguration, you are trying to autowire DataSourceConfiguration. I'm not really sure if that's possible - typically you should try to autwire the DataSource, not the DataSourceConfiguration.
#Import( { MySQLDataSourceConfiguration.class })
public class JdbcConfiguration {
#Autowired
private DataSource dataSource;
#Bean
public JdbcTemplate setupJdbcTemplate() throws Exception {
return new JdbcTemplate(dataSource);
}
Also if you have several DataSources and you're using Spring profiles to separate them, it's easier to provide all the DataSource beans in one file and annotate each bean with a different profile:
#Configuration
public class DataSourceConfig {
#Bean
#Profile("Test")
public DataSource devDataSource() {
.... configure data source
}
#Bean
#Profile("Prod")
public DataSource prodDataSource() {
... configure data source
}

Resources