SpringBoot LDAPTemplate Embedded vs Real LDAP - spring

I am using the below mentioned properties in my SpringBoot App, in application.yml file to have the LDAP Code run in my local machine.
spring:
ldap:
# Use this embedded configuration for local ldap testing
embedded:
base-dn: o=localcompany,c=US
credential:
username: uid=admin
password: secret
ldif: classpath:schemas.ldif
port: 12345
validation:
enabled: false
# Use this below configuration for Ford ldap
# urls: ldaps://mmm.mmm.com:754
# base-dn: o=****,c=US
# username:
# password: {your password goes here}
I want to have both my embedded configuration & actual configuration exist in my Application, so that it works locally as well as in my Cloud Environment. But having Embedded properties in my yml file is overwriting the actual ones even in Cloud Environment. Is there a way to have both the properties and then according to the Environment, wire the LDAPTemplate

I configured my LDAPTemplate using #profile annotation that would differentiate the local and Server environment and achieved what I asked above. Below is my configuration. For the Local environment, having the embedded-properties are sufficient to have LDAPTemplate wired properly
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.support.LdapContextSource;
#Configuration
#Profile("cloud")
public class LDAPConfiguration {
#Value("${ldap.url}")
private String ldapUrl;
#Value("${ldap.base}")
private String ldapBase;
#Value("${ldap.username}")
private String ldapUser;
#Value("${ldap.password}")
private String ldapPassword;
#Bean
public LdapTemplate configureLdapTemplateForCloud() {
return new LdapTemplate(contextSource()) ;
}
private LdapContextSource contextSource() {
LdapContextSource ldapContextSource = new LdapContextSource();
ldapContextSource.setUrl(ldapUrl);
ldapContextSource.setBase(ldapBase);
ldapContextSource.setUserDn(ldapUser);
ldapContextSource.setPassword(ldapPassword);
ldapContextSource.afterPropertiesSet();
return ldapContextSource;
}
}
So now, when I run in my local, Spring Boot will play with my Embedded LDAP, but in the cloud profile, it will execute the actual LDAP Server.

Related

How to deploy BPMN or DMN to Camunda Process Engine in Spring Boot + REST?

Assuming the Spring Boot app is connected to a database which has all the Camunda related tables created like dbo.ACT_HI_PROCINST, dbo.ACT_HI_TASKINST, dbo.ACT_RU_TASK, dbo.ACT_RU_EXECUTION, etc. There is a UI screen (Angular/React frontend) which accepts BPMN & DMN files to be deployed in Camunda database (could be any Relational Database). This (.bpmn/.dmn) should get deployed as the latest version of their file name.
Camunda Version:
7.13.0
User can select a file by browsing the file system from UI screen (assuming UI file browsing and selecting a valid .bpmn or .dmn is done). User has to send this selected file as part of request in REST call to Spring Boot app, and deploy the .bpmn/.dmn file in database.
A .bpmn or .dmn could be deployed in many ways:
via Camunda Modeler
via Spring Boot App startup auto-deploy from /src/main/resources/bpmn/* by using #EnableProcessApplication annotation along with #SpringBootApplication
via Manual deployment by sending .bpmn/.dmn file from frontend UI via REST call to Spring Boot controller and using Camunda's RepositoryService.createDeployment()
application.yaml:
Provide Camunda DB datasource configurations like url, username and password along with Camunda admin user credential like -
camunda.bpm.admin-user:
id: admin
password: admin
camunda:
bpm:
history-level: audit
job-execution:
core-pool-size: 3
max-pool-size: 10
jpa:
enabled: true
handle-transaction: true
server:
port: 8091
spring:
application:
name: camunda-bpm-workflow-app
datasource:
url: jdbc:sqlserver://ABC250.abc.xyz:10010;databaseName=DevCamunda
username: DevUser
password: DevPass
driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
hikari:
maximum-pool-size: 200
jpa:
hibernate:
ddl-auto: create
show-sql: false
properties:
hibernate:
format_sql: true
dialect: org.hibernate.dialect.SQLServerDialect
servlet:
multipart:
max-file-size: 10MB
max-request-size: 10MB
pom.xml:
camunda-bpm-spring-boot-starter
camunda-bpm-spring-boot-starter-webapp
camunda-bpm-spring-boot-starter-rest
Auto-deployment: #EnableProcessApplication: Spring Boot App startup auto-deploy from /src/main/resources/bpmn/*
Turn your Spring Boot app into a Camunda process application using #EnableProcessApplication annotation without extending any super-classes.
In the Spring Boot app, this annotation helps to link a resource to the process engine.
Disables the SpringProcessEngineConfiguration auto-deploy feature
Enables auto-deployment from /src/main/resources/META-INF/processes.xml
processes.xml - is an indicator for resource scanning. This could be left empty.
We can have .bpmn and .dmn files inside bmpn folder, example - /src/main/resources/bpmn/xyz.bmpn or /src/main/resources/bpmn/abc.dmn
On startup of Spring Boot app, all .bpmn and .dmn files from location /src/main/resources/bpmn/* will be auto-deployed if any changes were done in them
Manual deployment: By sending .bpmn/.dmn file from fronend UI via REST call to Spring Boot controller and using Camunda's RepositoryService.createDeployment(). After deployment is successful, new deployment ID can be found in database using SQL:
SELECT
ID_,
NAME_,
DEPLOY_TIME_,
SOURCE_,
TENANT_ID_
FROM
dbo.ACT_RE_DEPLOYMENT
ORDER BY
DEPLOY_TIME_ DESC
Main Class:
import org.camunda.bpm.spring.boot.starter.annotation.EnableProcessApplication;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
#EnableProcessApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
REST Controller endpoint:
A Camunda BPMN or DMN file to be deployed in database will be consumed by the rest endpoint as MediaType.MULTIPART_FORM_DATA_VALUE.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.abc.workflow.dto.CamundaDeployResponse;
import com.abc.workflow.service.CamundaDeployService;
import lombok.extern.slf4j.Slf4j;
#RestController("/camunda")
public class CamundaDeployController {
#Autowired
private CamundaDeployService camundaDeployService;
#PostMapping(value = "/deploy", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<CamundaDeployResponse> deploy(#RequestParam("file") MultipartFile file) {
return ResponseEntity.ok(camundaDeployService.deploy(file));
}
}
Service Interface:
import org.springframework.web.multipart.MultipartFile;
import com.abc.workflow.dto.CamundaDeployResponse;
public interface CamundaDeployService {
CamundaDeployResponse deploy(MultipartFile file);
}
Service Implementation Class:
RepositoryService - Service providing access to the repository of process definitions and deployments.
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.camunda.bpm.engine.RepositoryService;
import org.camunda.bpm.engine.repository.DeploymentWithDefinitions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import com.abc.workflow.dto.CamundaDeployRequestDto;
import com.abc.workflow.dto.CamundaDeployResponse;
import com.abc.workflow.service.CamundaDeployService;
import lombok.extern.slf4j.Slf4j;
#Slf4j
#Service
public class CamundaDeployServiceImpl implements CamundaDeployService {
#Autowired
private RepositoryService repositoryService;
#Override
public CamundaDeployResponse deploy(MultipartFile file) {
CamundaDeployResponse camundaDeployResponse = new CamundaDeployResponse();
String orgFileName = file.getOriginalFilename();
log.info("Camunda file to be deployed [{}]", orgFileName);
if(orgFileName.endsWith(".bpmn") || orgFileName.endsWith(".dmn")) {
try {
log.info("Camunda Deployment START : [{}]", orgFileName);
DeploymentWithDefinitions d = null;
d = repositoryService.createDeployment().addInputStream(orgFileName, file.getInputStream()).name(orgFileName).deployWithResult();
camundaDeployResponse.setSuccessMessage("Camunda Deployment SUCCESS ["+orgFileName+"] : Deployment ID ["+d.getId()+"]");
log.info("Camunda Deployment SUCCESS [{}] : Deployment ID [{}]", orgFileName, d.getId());
}
catch (IOException e) {
camundaDeployResponse.setErrorMessage("Camunda Deployment FAILED : "+e.getMessage());
log.error("Camunda Deployment FAILED [{}]: {}", orgFileName, e.getMessage());
e.printStackTrace();
}
}
else {
camundaDeployResponse.setErrorMessage("Not a valid Camunda file (BPMN/DMN)");
log.error("Not a valid Camunda file (BPMN/DMN)");
}
return camundaDeployResponse;
}
}

Spring boot application not fetching cloud configs on S3

I have a Java spring boot application with gradle. I have my config file in S3. This is the file I have:
description: Service Configuration
source:
profile: prod
server:
port: 8080
servlet:
context-path: /myservice-service
firebase:
authorization-header: "Basic XYZ"
base: baseurl
token-uri: tokenurl
it is named service-dev.yml on S3
I also have the appropriate prop classes:
#Configuration
#ComponentScan
#EnableConfigurationProperties(value = {
MyProps.class
})
public class PropConfiguration {
}
#Data
#Configuration
#Primary
#ConfigurationProperties(prefix = "firebase")
public class FirebaseProps {
private String authorizationHeader;
private String base;
private String tokenUri;
}
but when I try to use the props in my code, I get the error: "could not resolve placeholder in string". For instance when I do "${firebase.base}".
When I run in intelliJ, I have the environment variables set to my S3 bucket with the password and such.
Any suggestions on where I may be going wrong?

Spring Boot: Autowiring app credentials within custom autoconfiguration bean returns null

I am working with a custom AWS Simple System Management client just to avoid the original from using the default AWS authentication chain, so I placed my class in /META-INF/spring.factories and excluded the original from being autconfigured in bootstrap.yml . What I'm facing right now is to get the credentials from application.yml and pass them to my new conf, but when I try to autowire them all I get is null. I wonder if there is any way to achieve it
Here is the code:
package es.example;
import lombok.*;
import org.springframework.boot.context.properties.*;
#ConfigurationProperties(prefix = "aws.credentials")
#Data
public class CustomAWSSSMAuthProperties {
private String accessKey;
private String secretKey;
}
package es.example;
import com.amazonaws.services.simplesystemsmanagement.*;
import org.springframework.beans.factory.annotation.*;
import org.springframework.boot.context.properties.*;
#EnableConfigurationProperties(CustomAWSSSMAuthProperties.class)
public class CustomAWSSSMClient extends AWSSimpleSystemsManagementClient {
#Autowired
private CustomAWSSSMAuthProperties customProperties;
public CustomAWSSSMClient() {
String accessKey = customProperties.getAccessKey();
String secretKey = customProperties.getSecretKey();
}
}
/META-INF/spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
es.example.CustomAWSSSMClient
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
es.example.CustomAWSSSMClient
bootstrap.yml
spring:
cloud:
config:
uri: ${SPRING_CONFIG_URI:http://localhost:8888}
autoconfigure.exclude: com.amazonaws.services.simplesystemsmanagement.AWSSimpleSystemsManagementClient
Many thanks
#ConfigurationProperties does not create a bean like #Configuration or #Component does. In your case CustomAWSSSMAuthProperties type bean/object will not be instantiated in the Spring context.
Generally #ConfigurationProperties is used with #Configuration or #Bean to bind the some properties to the bean.
You can annotate CustomAWSSSMAuthProperties with #Configuration to fix the issue.

Share configuration between Spring cloud config clients

I'm trying to share configuration between Spring Cloud clients with a Spring Cloud config server which have a file-based repository:
#Configuration
#EnableAutoConfiguration
#EnableConfigServer
public class ConfigServerApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigServerApplication.class, args);
}
}
// application.yml
server:
port: 8888
spring:
profiles:
active: native
test:
foo: world
One of my Spring Cloud client use the test.foo configuration, defined in the config server, and it is configured like below:
#SpringBootApplication
#RestController
public class HelloWorldServiceApplication {
#Value("${test.foo}")
private String foo;
#RequestMapping(path = "/", method = RequestMethod.GET)
#ResponseBody
public String helloWorld() {
return "Hello " + this.foo;
}
public static void main(String[] args) {
SpringApplication.run(HelloWorldServiceApplication.class, args);
}
}
// boostrap.yml
spring:
cloud:
config:
uri: ${SPRING_CONFIG_URI:http://localhost:8888}
fail-fast: true
// application.yml
spring:
application:
name: hello-world-service
Despite this configuration, the Environment in the Spring Cloud Client doesn't contains the test.foo entry (cf java.lang.IllegalArgumentException: Could not resolve placeholder 'test.foo')
However it's works perfectly if i put the properties in a hello-world-service.yml file, in my config server file-based repository.
Maven dependencies on Spring Cloud Brixton.M5 and Spring Boot 1.3.3.RELEASE with spring-cloud-starter-config and spring-cloud-config-server
From Spring Cloud documentation
With the "native" profile (local file system backend) it is
recommended that you use an explicit search location that isn’t part
of the server’s own configuration. Otherwise the application*
resources in the default search locations are removed because they are
part of the server.
So i should put the shared configuration in an external directory and add the path in the application.yml file of the config-server.
// application.yml
spring:
profiles:
active: native
cloud:
config:
server:
native:
search-locations: file:/Users/herau/config-repo
// /Users/herau/config-repo/application.yml
test:
foo: world

Session not replicated on session creation with Spring Boot, Session, and Redis

I'm attempting to implement a microservices architecture using Spring Cloud's Zuul, Eureka, and my own services. I have multiple services that have UIs and services and each can authenticate users using x509 security. Now I'm trying to place Zuul in front of those services. Since Zuul can't forward client certs to the backend, I thought the next best thing would be to authenticate the user at the front door in Zuul, then use Spring Session to replicate their authenticated state across the backend services. I have followed the tutorial here from Dave Syer and it almost works, but not on the first request. Here is my basic setup:
Zuul Proxy in it's own application set to route to the backend services. Has Spring security enabled to do x509 auth. Successfully auths users. Also has Spring Session with #EnableRedisHttpSession
Backend service also has spring security enabled. I have tried both enabling/disabling x509 here but always requiring the user to be authenticated for specific endpoints. Also uses Spring Session and #EnableRedisHttpSession.
If you clear all the sessions and start fresh and try to hit the proxy, then it sends the request to the backend using the zuul server's certificate. The backend service then looks up the user based on that user cert and thinks the user is the server, not the user that was authenticated in the Zuul proxy. If you just refresh the page, then you suddenly become the correct user on the back end (the user authenticated in the Zuul proxy). The way I'm checking is to print out the Principal user in the backend controller. So on first request, I see the server user, and on second request, I see the real user. If I disable x509 on the back end, on the first request, I get a 403, then on refresh, it lets me in.
It seems like the session isn't replicated to the backend fast enough so when the user is authenticated in the frontend, it hasn't made it to the backend by the time Zuul forwards the request.
Is there a way to guarantee the session is replicated on the first request (i.e. session creation)? Or am I missing a step to ensure this works correctly?
Here are some of the important code snippets:
Zuul Proxy:
#SpringBootApplication
#Controller
#EnableAutoConfiguration
#EnableZuulProxy
#EnableRedisHttpSession
public class ZuulEdgeServer {
public static void main(String[] args) {
new SpringApplicationBuilder(ZuulEdgeServer.class).web(true).run(args);
}
}
Zuul Config:
info:
component: Zuul Server
endpoints:
restart:
enabled: true
shutdown:
enabled: true
health:
sensitive: false
zuul:
routes:
service1: /**
logging:
level:
ROOT: INFO
# org.springframework.web: DEBUG
net.acesinc: DEBUG
security.sessions: ALWAYS
server:
port: 8443
ssl:
key-store: classpath:dev/localhost.jks
key-store-password: thepassword
keyStoreType: JKS
keyAlias: localhost
clientAuth: want
trust-store: classpath:dev/localhost.jks
ribbon:
IsSecure: true
Backend Service:
#SpringBootApplication
#EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class, ThymeleafAutoConfiguration.class, org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration.class })
#EnableEurekaClient
#EnableRedisHttpSession
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Backend Service Config:
spring.jmx.default-domain: ${spring.application.name}
server:
port: 8444
ssl:
key-store: classpath:dev/localhost.jks
key-store-password: thepassword
keyStoreType: JKS
keyAlias: localhost
clientAuth: want
trust-store: classpath:dev/localhost.jks
#Change the base url of all REST endpoints to be under /rest
spring.data.rest.base-uri: /rest
security.sessions: NEVER
logging:
level:
ROOT: INFO
# org.springframework.web: INFO
# org.springframework.security: DEBUG
net.acesinc: DEBUG
eureka:
instance:
nonSecurePortEnabled: false
securePortEnabled: true
securePort: ${server.port}
homePageUrl: https://${eureka.instance.hostname}:${server.port}/
secureVirtualHostName: ${spring.application.name}
One of the Backend Controllers:
#Controller
public class SecureContent1Controller {
private static final Logger log = LoggerFactory.getLogger(SecureContent1Controller.class);
#RequestMapping(value = {"/secure1"}, method = RequestMethod.GET)
#PreAuthorize("isAuthenticated()")
public #ResponseBody String getHomepage(ModelMap model, Principal p) {
log.debug("Secure Content for user [ " + p.getName() + " ]");
model.addAttribute("pageName", "secure1");
return "You are: [ " + p.getName() + " ] and here is your secure content: secure1";
}
}
Thanks to shobull for pointing me to Justin Taylor's answer to this problem. For completeness, I wanted to put the full answer here too. It's a two part solution:
Make Spring Session commit eagerly - since spring-session v1.0 there is annotation property #EnableRedisHttpSession(redisFlushMode = RedisFlushMode.IMMEDIATE) which saves session data into Redis immediately. Documentation here.
Simple Zuul filter for adding session into current request's header:
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import javax.servlet.http.HttpSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.session.Session;
import org.springframework.session.SessionRepository;
import org.springframework.stereotype.Component;
#Component
public class SessionSavingZuulPreFilter extends ZuulFilter {
#Autowired
private SessionRepository repository;
private static final Logger log = LoggerFactory.getLogger(SessionSavingZuulPreFilter.class);
#Override
public String filterType() {
return "pre";
}
#Override
public int filterOrder() {
return 1;
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
HttpSession httpSession = context.getRequest().getSession();
Session session = repository.getSession(httpSession.getId());
context.addZuulRequestHeader("Cookie", "SESSION=" + httpSession.getId());
log.trace("ZuulPreFilter session proxy: {}", session.getId());
return null;
}
}
Both of these should be within your Zuul Proxy.
Spring Session support currently writes to the data store when the request is committed. This is to try to reduce "chatty traffic" by writing all attributes at once.
It is recognized that this is not ideal for some scenarios (like the one you are facing). For these we have spring-session/issues/250. The workaround is to copy RedisOperationsSessionRepository and invoke saveDelta anytime property is changed.

Resources