I am currently training and I'm working on Android application that uses RESTEasy API and I encounter some problem with ProxyFactory.create method (..., ...).
Let me explain it:
I have two REST services.
AuthenticateService :
#Path("/authent/tokens")
public interface AuthenticateService {
// This method add a data "token" in cookie
#POST
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public PostCustomerOutput createToken(PostCustomerInput postCustomerInput) throws ConnectException;
#Path("/{id}")
#DELETE
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public Void deleteToken(#PathParam("id") String token);
}
EnrollmentService :
#Path("/enrollment/otp")
public interface UserEnrollmentService {
#POST
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public PostGenerateOTPOutput postGenerateOTP(PostGenerateOTPInput postGenerateOTPInput);
#POST
#Path("/check")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public OutputImpl postCheckOTP(PostCheckOTPInput postCheckOTPInput);
}
On these two services, I have a interceptor that handles recovered data in Cookies.
GrantAccessInterceptor :
public class GrantAccessInterceptor extends AbstractInDatabindingInterceptor {
public GrantAccessInterceptor() {
super(Phase.USER_STREAM);
}
#Override
public void handleMessage(Message message) throws Fault {
HttpServletRequest request = (HttpServletRequest) message.get(AbstractHTTPDestination.HTTP_REQUEST);
if (null != request) {
// Read http header to get cookie/
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cook : cookies) {
if (cook.getName().equals("token")) {
log.info("Token find in cookies");
// TODO : do what I want with the cookie
}
}
} else {
log.info("Cookies are empty !");
}
}
}
}
Now I wrote the following test :
#org.junit.Test
public void testCreateToken() {
RegisterBuiltin.register(ResteasyProviderFactory.getInstance());
// Recover AuthenticateService
AuthenticateService authenticateService = ProxyFactory.create(AuthenticateService.class, urlLocal, executor);
// Recover UserEnrollmentService
UserEnrollmentService userEnrollmentService = ProxyFactory.create(UserEnrollmentService.class, urlLocal, executor);
PostCustomerInput in = new PostCustomerInput();
// put data in PostCustomerInput
PostCustomerOutput out = authenticateService.createToken(in);
// authenticateService.deleteToken(out.getCustomerToken());
PostGenerateOTPInput postGenerateOTPInput = new PostGenerateOTPInput();
userEnrollmentService.postGenerateOTP(postGenerateOTPInput);
}
When I call the method authenticateService.createToken, my GrantAccessInterceptor shows me the right message "Cookies are empty!" This is normal because the cookie is added to the createToken method.
Now, if I call deleteToken method on the same service (AuthenticateService) I get the message "Token find in cookies" which is OK.
Until then all is well.
Now, if after calling the method createToken of AuthenticateService I call a method of my UserEnrollmentService, GrantAccessInterceptor finds nothing in cookies ... -> "Cookies are empty!"
I think that the problem comes from ProxyFactory which does not share cookies between differents services.
It's not ProxyFactory's job to handle cookies, that's up to the ClientExecutor.
By passing the same ClientExecutor to ProxyFactory, you should be able to share cookies:
ApacheHttpClient4Executor executor = new ApacheHttpClient4Executor();
ProxyFactory.create(ServiceIntf1.class, "http://my-service-url", executor);
ProxyFactory.create(ServiceIntf1.class, "http://my-service-url", executor);
Related
I am using React for front-end and Java spring boot for backend. My api was working before I used Bcrypt to encode passwords but now there seems to be a problem with the internal filter before every api call where the response is null...
this is my WebSecurityConfig.java
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService myUserDetailsService;
#Autowired
private JwtRequestFilter jwtRequestFilter;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService).passwordEncoder(passwordEncode());
}
#Bean
public PasswordEncoder passwordEncode(){
return new BCryptPasswordEncoder();
}
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable()
.authorizeRequests().antMatchers("/authenticate").permitAll()
.antMatchers("/personInfo").permitAll()
.antMatchers("/signup").permitAll().
anyRequest().authenticated().and().
addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class).exceptionHandling();
}
}
and this is my JWTRequestFilter.java
I was thinking it may have to do with the passwordEncoder() because my calls were working when i didn't use BcryptPasswordEncoder()...
#Component
public class JwtRequestFilter extends OncePerRequestFilter {
#Autowired
private MyUserDetailsService userDetailsService;
#Autowired
private JwtUtil jwtUtil;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
if(request == null){
System.out.println("request is null");
}
final String authorizationHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7);
username = jwtUtil.extractUsername(jwt);
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtUtil.validateToken(jwt, userDetails)) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
i am using react for the front-end and am calling with axios
async totals(){
console.log('Bearer ', localStorage.getItem('id_token'));
let data = await axios.get("http://localhost:8080/totals", {
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + localStorage.getItem('id_token')
}
})
.then(this._checkStatus);
return data.request.response;
}
the api works when i use the token in postman and so the problem is between the initial request and the filter...
the error I get is -
Access to XMLHttpRequest at 'http://localhost:8080/totals' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
thanks for your time :)
If anybody ever gets the same problem, I solved it by adding this line at the end of my configure(HttpSecurity httpSecurity) method in WebSecurityConfig.java
httpSecurity.cors();
:)
I am having issues with my Spring backend and an AngularJS frontend. As an info, I'm pretty new to Spring Security and learning with this project as well.
I'm not using SpringBoot. Both work seperately and are supposed to be able to run on seperate machines. ATM my frontend is running locally via gulp server on https://localhost:3000, the backend is running in a Tomcat at https://localhost:8443/context. I've set up a CORSFilter in Java.
So far, so good. If I start the frontend, calls are being made to the backend for getting resources and I'm seing the login page. If I choose login, the call is being made to https://localhost:8443/context/login, as its supposed to. But: After the login is being processed in the backend, the backend does a redirect to https://localhost:8443/context instead of https://localhost:3000, which of course creates a 404 and results in a failed login (frontend-wise). I just can't find where this weird redirect is being made.
SpringSecurityConfig:
private static final String C440_LOGIN = "/login";
private static final String c440_START_PAGE = "/index.html";
private static final String FAVICON_ICO = "/favicon.ico";
#Override
protected void configure(HttpSecurity http) throws Exception {
// HttpSecurity workHttp = http.addFilterBefore(new CORSFilter(), SessionManagementFilter.class); does not work!
HttpSecurity workHttp = http.addFilterBefore(new CORSFilter(), ChannelProcessingFilter.class);
workHttp.addFilterBefore(new CookieFilter(), ChannelProcessingFilter.class);
workHttp.addFilterBefore(getUsernamePasswordPortalAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
// set authorizations
workHttp = authorizeRequests(http);
// login handling
workHttp = formLogin(workHttp);
// exception handling
workHttp = exceptionHandling(workHttp);
// logout handling
workHttp = logout(workHttp);
// cookie handling
workHttp = rememberMe(workHttp);
// disable caching because if IE11 webfonds bug
// https://stackoverflow.com/questions/7748140/font-face-eot-not-loading-over-https
http.headers().cacheControl().disable();
csrf(workHttp);
}
/**
* Configures request authorization.
*
* #param http The security configuration.
* #return The configured security configuration.
* #throws Exception is throws if the configuration fails.
*/
protected HttpSecurity authorizeRequests(HttpSecurity http) throws Exception {
return http
.authorizeRequests()
// secured pages
.antMatchers("/", getCustomerdWebRessourceSecuredPath()).authenticated()
// common resources
.antMatchers("/app/**").permitAll()
.antMatchers("/profiles/**").permitAll()
.antMatchers("/captcha/**").permitAll()
.antMatchers("/", getCustomerRessourcePath()).permitAll()
.antMatchers("/", getCustomerWebRessourcePath()).permitAll()
.antMatchers("/", c440_START_PAGE).permitAll()
.antMatchers("/", FAVICON_ICO).permitAll()
.antMatchers(C440_LOGIN).permitAll()
// frontend services
.antMatchers("/services/userService/**").permitAll()
.antMatchers("/services/applicationService/**").permitAll()
.antMatchers("/services/textContentService/**").permitAll()
.antMatchers("/services/textContentBlockService/**").permitAll()
.antMatchers("/services/menuItemService/**").permitAll()
.antMatchers("/services/calculatorService/**").permitAll()
.anyRequest().authenticated()
.and();
}
private String getCustomerRessourcePath() {
return "/resources/app-" + portalFrontendBase + "/**";
}
private String getCustomerWebRessourcePath() {
return "/app-" + portalFrontendBase + "/**";
}
private String getCustomerdWebRessourceSecuredPath() {
return "/app-" + portalFrontendBase + "/secure/**";
}
/**
* Configures form login.
*
* #param http The security configuration.
* #return The configured security configuration.
* #throws Exception is throws if the configuration fails.
*/
protected HttpSecurity exceptionHandling(HttpSecurity http) throws Exception {
return http
.exceptionHandling()
.authenticationEntryPoint((request, response, authException) -> {
if (authException != null) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
/**
* IMPORTANT: do not redirect the requests. The front-end will be responsible to do this.
* Otherwise the unauthorized status cannot be caught in the front-end correctly.
*/
return;
}
})
.and();
}
/**
* Configures form login.
*
* #param http The security configuration.
* #return The configured security configuration.
* #throws Exception is throws if the configuration fails.
*/
protected HttpSecurity formLogin(HttpSecurity http) throws Exception {
return http
.formLogin()
.loginPage(c440_START_PAGE)
.successHandler(getAuthenticationSuccessHandler())
.failureHandler(getAuthenticationFailureHandler())
.loginProcessingUrl(C440_LOGIN)
.permitAll()
.and();
}
/**
* Configures logout.
*
* #param http The security configuration.
* #return The configured security configuration.
* #throws Exception is throws if the configuration fails.
*/
protected HttpSecurity logout(HttpSecurity http) throws Exception {
return http
.logout()
.logoutUrl(portalLogoutURL)
.addLogoutHandler(getLogoutHandler())
.logoutSuccessHandler(getLogoutSuccessHandler())
.invalidateHttpSession(true)
.and();
}
#Bean
public UsernamePasswordPortalAuthenticationFilter getUsernamePasswordPortalAuthenticationFilter() throws Exception {
UsernamePasswordPortalAuthenticationFilter customFilter = new UsernamePasswordPortalAuthenticationFilter();
customFilter.setAuthenticationManager(authenticationManagerBean());
return customFilter;
}
UsernamePasswordPortalAuthenticationFilter.java:
#PropertySource(value = {"classpath:application.properties"})
public class UsernamePasswordPortalAuthenticationFilter extends
UsernamePasswordAuthenticationFilter {
private Logger log = Logger.getLogger(this.getClass());
#Value("${captchaActive}")
private boolean captchaActive;
#Override
public AuthenticationManager getAuthenticationManager() {
return super.getAuthenticationManager();
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
UsernamePasswordPortalAuthenticationToken authRequest = getAuthenticationTokenFromRequest(request);
return getAuthenticationManager().authenticate(authRequest);
}
/**
* Reads the UsernamePasswordPortalAuthenticationToken from the data of the request.
*
* #param request The request to read the data from.
* #return The authentication token.
* #throws AuthenticationException is thrown if the data cannot be read.
*/
public UsernamePasswordPortalAuthenticationToken getAuthenticationTokenFromRequest(final HttpServletRequest request) throws AuthenticationException {
StringBuffer buf = new StringBuffer();
String line = null;
try {
BufferedReader reader = request.getReader();
while ((line = reader.readLine()) != null) {
buf.append(line);
}
UsernamePasswordPortalAuthenticationToken loginDataWithCaptcha =
new ObjectMapper().readValue(buf.toString(), UsernamePasswordPortalAuthenticationToken.class);
if (this.captchaActive) {
String answer = (String) request.getSession().getAttribute("COLLPHIRCAPTCHA");
List<CaptchaCookieDto> captchaCookieDtos;
captchaCookieDtos = (List<CaptchaCookieDto>) request.getAttribute("captchaCookies");
CaptchaCookieDto captchaCookieDto = captchaCookieDtos.stream().filter(captchaCookie -> captchaCookie.getUsername().equals(
loginDataWithCaptcha.getUsername())).findAny().orElse(null);
if (captchaCookieDto != null && captchaCookieDto.getCounter() >= 2) {
if (answer.equals(loginDataWithCaptcha.getConfirmCaptcha())) {
return new ObjectMapper().readValue(loginDataWithCaptcha.loginDataToStringWithoutCaptcha(),
UsernamePasswordPortalAuthenticationToken.class);
} else {
throw new BadCredentialsException("invalid data");
}
} else {
return new ObjectMapper().readValue(loginDataWithCaptcha.loginDataToStringWithoutCaptcha(),
UsernamePasswordPortalAuthenticationToken.class);
}
} else {
return new ObjectMapper().readValue(loginDataWithCaptcha.loginDataToStringWithoutCaptcha(), UsernamePasswordPortalAuthenticationToken.class);
}
} catch (Exception e) {
throw new BadCredentialsException("invalid data");
}
}
}
I tried changing the order of my two custom filters (CORSFilter and CookieFilter), or putting the CORSFilter somehwere else (addFilterBefore SessionManagementFilter does not work, if I do that, the login-call won't work because of the missing CORS-header) and almost everything else...
I also tried using the idea from the authsuccesshandler from https://www.baeldung.com/spring_redirect_after_login where I just get the requests origin header (which should be the frontend-URL https://localhost:3000) to redirect back to it:
#Component
public class MyTestAuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private Logger LOG = Logger.getLogger(this.getClass());
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
public MyTestAuthenticationSuccessHandler() {
super();
setUseReferer(true);
}
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication auth) throws IOException {
LOG.info("onAuthenticationSuccess");
SecurityContextHolder.getContext().setAuthentication(auth);
handle(request, response, auth);
}
protected void handle(HttpServletRequest request, HttpServletResponse response, Authentication auth) throws IOException {
String targetUrl = determineTargetUrl(request);
if (response.isCommitted()) {
LOG.info("Response has already been committed. Unable to redirect to " + targetUrl);
return;
}
redirectStrategy.sendRedirect(request, response, targetUrl);
}
protected String determineTargetUrl(HttpServletRequest request) {
return request.getHeader("Origin");
}
}
but still it doesn't work.
Also, if I try to debug the backend and set breakpoints inside of the authsuccesshandler and authfailurehandler, it still doesn't stop there. Shouldn't it stop there?
.formLogin()
.loginPage(c440_START_PAGE)
.successHandler(getAuthenticationSuccessHandler())
.failureHandler(getAuthenticationFailureHandler())
.loginProcessingUrl(C440_LOGIN)
.permitAll()
.and();
I really don't understand where this redirect is happening and why it won't use my new authsuccesshandler.
UPDATE 07.03.19: It seems that the successhandler isn't being called at all, even if I deploy both frontend and backend at the same URL as a bundled WAR file which makes the login work again. Weird thing is, even if I remove the .formLogin() stuff from the configure method inside the SecurityConfig the login still works. So I guess it looks like all the magic is happening in the AuthenticationProvider which is being called in our custom UsernamePasswordAuthenticationFilter:
UsernamePasswordAuthenticationFilter
[...]
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
UsernamePasswordPortalAuthenticationToken authRequest = getAuthenticationTokenFromRequest(request);
return getAuthenticationManager().authenticate(authRequest);
}
[...]
AuthenticationProvider:
[...]
#Override
public CollphirAuthentication authenticate(Authentication authentication) throws AuthenticationException {
if (authentication == null) {
throw new IllegalArgumentException("authentication");
}
if (UsernamePasswordPortalAuthenticationToken.class.isAssignableFrom(authentication.getClass())) {
UsernamePasswordPortalAuthenticationToken clientAuthentication = (UsernamePasswordPortalAuthenticationToken) authentication;
CollphirUser user = getUserService().loginUser(
clientAuthentication.getName(), clientAuthentication.getCredentials().toString(), clientAuthentication.getPortal(), clientAuthentication.getArbeitgeber());
CollphirAuthentication auth = null;
if (user == null || user.getBenutzerkennung() == null || user.getCOLRolle() == null) {
LOG.info("authentication failed");
Notification[] notifications = user.getNotifications();
String msg = null;
if (notifications != null && notifications[0] != null && notifications[0].getText() != null) {
msg = notifications[0].getText();
}
throw new BadCredentialsException(msg);
}
Referenz arbeitgeberReference = getArbeitgeberReference(user, clientAuthentication.getPortal(), clientAuthentication.getArbeitgeber());
auth = new CollphirAuthentication(user, arbeitgeberReference);
auth.setArbeitgeber(getArbeitgeber( arbeitgeberReference));
LOG.debug("is authenticated: " + auth.isAuthenticated());
return auth;
}
throw new BadCredentialsException("type");
}
[...]
So my guess is: Somewhere inside the UsernamePasswordPortalAuthenticationFilter or AuthenticationProvider the redirect is being made. If I think about it, a redirect doesn't make sense at all in an AngularJS frontend where the call to the backend is being made via REST, right? Shouldn't the backend just send back a status code or something which the AngularJS controller can evaluate to change the state or display an error message?
It looks like the whole login process in this application is really weird. I can't imagine it is usual to not use the .formLogin() and .successHandler()? The thing is, I don't have a best practice example for an AngularJS frontend and Spring Security backend as comparison...
Do you use the Springs default AuthenticationSuccessHandler? I think that this handler just redirect to the application base path and it is not working if your frontend is at another context or URL. So you have to perform a redirect after an successfull login back to your frontend.
Take a look at this page: https://www.baeldung.com/spring-security-redirect-login
Here you find a few possibilities to handle that case.
I have a route as follows:
from(fromEndpoint).routeId("ticketRoute")
.log("Received Tickets : ${body}")
.doTry()
.process(exchange -> {
List<TradeTicketDto> ticketDtos = (List<TradeTicketDto>) exchange.getIn().getBody();
ticketDtos.stream()
.forEach(t -> solaceMessagePublisher.sendAsText("BOOKINGSERVICE.TICKET.UPDATED", t));
ticketToTradeConverter.convert(ticketDtos)
.forEach(t -> solaceMessagePublisher.sendAsText("BOOKINGSERVICE.TRADE.UPDATED", t));
}).doCatch(java.lang.RuntimeException.class)
.log(exceptionMessage().toString() + " --> ${body}");
solaceMessagePublisher is a utility class in application which performs some action on passed object (second argument) and finally converts it to json string and sends to a jms topic (first argument).
SolaceMessagePublisher.java
public void sendAsText(final String destinationKey, Object payload) {
LOGGER.debug("Sending object as text to %s",destinationKey);
String destinationValue = null;
if (StringUtils.isNotEmpty(destinationKey)) {
destinationValue = properties.getProperty(destinationKey);
}
LOGGER.debug("Identified Destination Value = %s from key %s", destinationValue,destinationKey);
if (StringUtils.isEmpty(destinationValue)) {
throw new BaseServiceException("Invalid destination for message");
}
sendAsTextToDestination(destinationValue, payload);
}
public void sendAsTextToDestination(final String destinationValue, Object payload) {
if (payload == null) {
LOGGER.debug(" %s %s",EMPTY_PAYLOAD_ERROR_MESSAGE, destinationValue);
return;
}
final String message = messageCreator.createMessageEnvelopAsJSON(payload, ContextProvider.getUserInContext());
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Created message = " + message);
}
jmsTemplate.send(destinationValue, new MessageCreator() {
#Override
public Message createMessage(Session session) throws JMSException {
LOGGER.debug("Creating JMS Text Message");
return session.createTextMessage(message);
}
});
}
I am having a problem in creating a mock endpoint to listen to messages sent to this topic. Question is how to listen to the messages sent to a topic which is out of camel context?
I have tried in my Test using mock:jms:endpoint. It doesn't work.
My Test is as below
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { SiteMain.class })
public class TicketRouteCamelTest extends CamelSpringTestSupport{
#Autowired
protected BaseMessageEnvelopCreator messageCreator;
private static final String MOCK_TICKET_UPDATED_QUEUE = "direct:mockTicketUpdated";
#Before
public void configureMockEndpoints() throws Exception {
//mock input
final AdviceWithRouteBuilder mockRouteAdvice = new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
replaceFromWith(MOCK_TICKET_UPDATED_QUEUE);
}
};
context().getRouteDefinition("ticketRoute").adviceWith(context(), mockRouteAdvice);
}
#Test
public void testTicketRouteWithListOfTickets() throws Exception {
//create test data
TradeTicketDto tradeTicketDto = TradeTestDataHelper.getTradeTicketDto();
//create an exchange and set its body with test data
List<TradeTicketDto> list = new ArrayList<>();
list.add(tradeTicketDto);
list.add(tradeTicketDto);
Exchange requestExchange = ExchangeBuilder.anExchange(context()).build();
requestExchange.getIn().setBody(list);
//create assert on the mock endpoints
MockEndpoint mockTicketUpdatedEndpoint = getMockEndpoint("mock:DEV/bookingservice/ticket/updated");
mockTicketUpdatedEndpoint.expectedBodiesReceived(
messageCreator.createMessageEnvelopAsJSON(list.get(0), ContextProvider.getUserInContext()),
messageCreator.createMessageEnvelopAsJSON(list.get(1), ContextProvider.getUserInContext()) );
MockEndpoint mockTradeUpdatedEndpoint = getMockEndpoint("mock:DEV/bookingservice/trade/updated");
mockTradeUpdatedEndpoint.expectedBodiesReceived(
messageCreator.createMessageEnvelopAsJSON(list.get(0).getTicketInstruments().get(0), ContextProvider.getUserInContext()),
messageCreator.createMessageEnvelopAsJSON(list.get(0).getTicketInstruments().get(1), ContextProvider.getUserInContext()),
messageCreator.createMessageEnvelopAsJSON(list.get(1).getTicketInstruments().get(0), ContextProvider.getUserInContext()),
messageCreator.createMessageEnvelopAsJSON(list.get(1).getTicketInstruments().get(1), ContextProvider.getUserInContext()));
//send test exchange to request mock endpoint
template.send(MOCK_TICKET_UPDATED_QUEUE, requestExchange);
//test the asserts
assertMockEndpointsSatisfied();
}
}
On running test actual bodies received on mockendpont is 0
Mock is NOT a queue for consumers/producers to exchange data. Its a sink for testing purpose where you can setup expectations on the mock.
If you want to simulate a JMS via some kind of other means, then take a look at the stub component: http://camel.apache.org/stub
Its also listed in the bottom of the testing docs at: http://camel.apache.org/testing
I have done an extensive search for the mentioned issue, but unable to find a workable solution.
Kindly have a look on some imp codes and suggest.
// My factory method that returns a promise
contactBackend : function(requestedMethod, requestedUrl,
requestedData) {
return $http({
method : requestedMethod,
url : backend + requestedUrl,
data : requestedData
});
}
//Actual Login method that calls the
loginC.validateLogin = function() {
welcomeMFactory.contactBackend("POST", "/rs/login",
loginC.user).then(
function(success) {
var msg = success.data.loginMsg;
if (msg == "login.valid") {
alert(JSON.stringify(success));
welcomeMFactory.moveToWidget("/home");
} else {
loginC.error = welcomeMFactory.printMsg(
true, msg);
}
},
function(error) {
loginC.error = welcomeMFactory.printMsg(true,
"Unable to reach backend for login");
});
}
// SpringController.xml
<mvc:cors>
<mvc:mapping path="/**" allowed-origins="http://localhost:8080"
allowed-headers="content-type,authentication" exposed-headers="content-type,authentication" />
</mvc:cors>
//Login Controller
#Autowired
private LoginRespI response;
#Autowired
private ProxyHandler proxyHandler;
#Autowired
private LoginServiceImpl loginServiceImpl;
#RequestMapping(method = RequestMethod.POST)
public LoginRespB authenticateUserLogin(#RequestBody LoginReqB request, HttpServletResponse resp) {
LoginDTO loginDTO = loginServiceImpl.validateLoginService(request.getUsername(), request.getPassword());
if (loginDTO != null) {
response.setLoginMsg("login.valid");
} else {
response.setLoginMsg("login.invalid");
}
Claims claims = Jwts.claims().setSubject("ABCDE");
claims.put("ID", "12345");
String toke = Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS512, "ABCDE").compact();
resp.setHeader("Authentication", "Bearer: " + toke);
return (LoginRespB) (proxyHandler.getTargetObject(response));
}
OPTIONS req/resp headers
Login req/resp headers
Add this method to your code ,it will allow cros domain request
package org.codingpedia.demo.rest.util;
import java.io.IOException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.core.MultivaluedMap;
public class CORSResponseFilter
implements ContainerResponseFilter {
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
throws IOException {
MultivaluedMap<String, Object> headers = responseContext.getHeaders();
headers.add("Access-Control-Allow-Origin", "*");
//headers.add("Access-Control-Allow-Origin", "http://podcastpedia.org"); //allows CORS requests only coming from podcastpedia.org
headers.add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT");
headers.add("Access-Control-Allow-Headers", "X-Requested-With, Content-Type, X-Codingpedia");
}
}
I am looking to implement a route where reslet/cxfrs end point will accept file as multipart request and process. (Request may have some JSON data as well.
Thanks in advance.
Regards.
[EDIT]
Have tried following code. Also tried sending file using curl. I can see file related info in headers and debug output, but not able to retrieve attachment.
from("servlet:///hello").process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
Message in = exchange.getIn();
StringBuffer v = new StringBuffer();
HttpServletRequest request = (HttpServletRequest) in
.getHeaders().get(Exchange.HTTP_SERVLET_REQUEST);
DiskFileItemFactory diskFile = new DiskFileItemFactory();
FileItemFactory factory = diskFile;
ServletFileUpload upload = new ServletFileUpload(factory);
List items = upload.parseRequest(request);
.....
curl :
curl -vvv -i -X POST -H "Content-Type: multipart/form-data" -F "image=#/Users/navaltiger/1.jpg; type=image/jpg" http://:8080/JettySample/camel/hello
following code works (but can't use as it embeds jetty, and we would like to deploy it on tomcat/weblogic)
public void configure() throws Exception {
// getContext().getProperties().put("CamelJettyTempDir", "target");
getContext().setStreamCaching(true);
getContext().setTracing(true);
from("jetty:///test").process(new Processor() {
// from("servlet:///hello").process(new Processor() {
public void process(Exchange exchange) throws Exception {
String body = exchange.getIn().getBody(String.class);
HttpServletRequest request = exchange.getIn().getBody(
HttpServletRequest.class);
StringBuffer v = new StringBuffer();
// byte[] picture = (request.getParameter("image")).getBytes();
v.append("\n Printing All Request Parameters From HttpSerlvetRequest: \n+"+body +" \n\n");
Enumeration<String> requestParameters = request
.getParameterNames();
while (requestParameters.hasMoreElements()) {
String paramName = (String) requestParameters.nextElement();
v.append("\n Request Paramter Name: " + paramName
+ ", Value - " + request.getParameter(paramName));
}
I had a similar problem and managed to resolve inspired by the answer of brentos. The rest endpoint in my case is defined via xml:
<restContext id="UploaderServices" xmlns="http://camel.apache.org/schema/spring">
<rest path="/uploader">
<post bindingMode="off" uri="/upload" produces="application/json">
<to uri="bean:UploaderService?method=uploadData"/>
</post>
</rest>
</restContext>
I had to use "bindingMode=off" to disable xml/json unmarshalling because the HttpRequest body contains multipart data (json/text+file) and obviously the standard unmarshaling process was unable to process the request because it's expecting a string in the body and not a multipart payload.
The file and other parameters are sent from a front end that uses the file upload angular module: https://github.com/danialfarid/ng-file-upload
To solve CORS problems I had to add a CORSFilter filter in the web.xml like the one here:
public class CORSFilter implements Filter {
#Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException,
ServletException {
HttpServletResponse httpResp = (HttpServletResponse) resp;
HttpServletRequest httpReq = (HttpServletRequest) req;
httpResp.setHeader("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, CONNECT, PATCH");
httpResp.setHeader("Access-Control-Allow-Origin", "*");
if (httpReq.getMethod().equalsIgnoreCase("OPTIONS")) {
httpResp.setHeader("Access-Control-Allow-Headers",
httpReq.getHeader("Access-Control-Request-Headers"));
}
chain.doFilter(req, resp);
}
#Override
public void init(FilterConfig arg0) throws ServletException {
}
#Override
public void destroy() {
}
}
Also, I had to modify a little bit the unmarshaling part:
public String uploadData(Message exchange) {
String contentType=(String) exchange.getIn().getHeader(Exchange.CONTENT_TYPE);
MediaType mediaType = MediaType.valueOf(contentType); //otherwise the boundary parameter is lost
InputRepresentation representation = new InputRepresentation(exchange
.getBody(InputStream.class), mediaType);
try {
List<FileItem> items = new RestletFileUpload(
new DiskFileItemFactory())
.parseRepresentation(representation);
for (FileItem item : items) {
if (!item.isFormField()) {
InputStream inputStream = item.getInputStream();
// Path destination = Paths.get("MyFile.jpg");
// Files.copy(inputStream, destination,
// StandardCopyOption.REPLACE_EXISTING);
System.out.println("found file in request:" + item);
}else{
System.out.println("found string in request:" + new String(item.get(), "UTF-8"));
}
}
} catch (Exception e) {
e.printStackTrace();
}
return "200";
}
I'm using the Camel REST DSL with Restlet and was able to get file uploads working with the following code.
rest("/images").description("Image Upload Service")
.consumes("multipart/form-data").produces("application/json")
.post().description("Uploads image")
.to("direct:uploadImage");
from("direct:uploadImage")
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
MediaType mediaType =
exchange.getIn().getHeader(Exchange.CONTENT_TYPE, MediaType.class);
InputRepresentation representation =
new InputRepresentation(
exchange.getIn().getBody(InputStream.class), mediaType);
try {
List<FileItem> items =
new RestletFileUpload(
new DiskFileItemFactory()).parseRepresentation(representation);
for (FileItem item : items) {
if (!item.isFormField()) {
InputStream inputStream = item.getInputStream();
Path destination = Paths.get("MyFile.jpg");
Files.copy(inputStream, destination,
StandardCopyOption.REPLACE_EXISTING);
}
}
} catch (FileUploadException | IOException e) {
e.printStackTrace();
}
}
});
you can do this with restdsl even if you are not using restlet (exemple jetty) for your restdsl component.
you need to turn restdinding of first for that route and reate two classes to handle the multipart that is in your body.
you need two classes :
DWRequestContext
DWFileUpload
and then you use them in your custom processor
here is the code :
DWRequestContext.java
import org.apache.camel.Exchange;
import org.apache.commons.fileupload.RequestContext;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
public class DWRequestContext implements RequestContext {
private Exchange exchange;
public DWRequestContext(Exchange exchange) {
this.exchange = exchange;
}
public String getCharacterEncoding() {
return StandardCharsets.UTF_8.toString();
}
//could compute here (we have stream cache enabled)
public int getContentLength() {
return (int) -1;
}
public String getContentType() {
return exchange.getIn().getHeader("Content-Type").toString();
}
public InputStream getInputStream() throws IOException {
return this.exchange.getIn().getBody(InputStream.class);
}
}
DWFileUpload.java
import org.apache.camel.Exchange;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUpload;
import org.apache.commons.fileupload.FileUploadException;
import java.util.List;
public class DWFileUpload extends
FileUpload {
public DWFileUpload() {
super();
}
public DWFileUpload(FileItemFactory fileItemFactory) {
super(fileItemFactory);
}
public List<FileItem> parseInputStream(Exchange exchange)
throws FileUploadException {
return parseRequest(new DWRequestContext(exchange));
}
}
you can define your processor like this:
routeDefinition.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
// Create a factory for disk-based file items
DiskFileItemFactory factory = new DiskFileItemFactory();
factory.setRepository(new File(System.getProperty("java.io.tmpdir")));
DWFileUpload upload = new DWFileUpload(factory);
java.util.List<FileItem> items = upload.parseInputStream(exchange);
//here I assume I have only one, but I could split it here somehow and link them to camel properties...
//with this, the first file sended with your multipart replaces the body
// of the exchange for the next processor to handle it
exchange.getIn().setBody(items.get(0).getInputStream());
}
});
I stumbled into the same requirement of having to consume a multipart request (containing file data including binary) through Apache Camel Restlet component.
Even though 2.17.x is out, since my project was part of a wider framework / application, I had to be using version 2.12.4.
Initially, my solution drew a lot from restlet-jdbc example yielded data in exchange that although was successfully retrieving text files but I was unable to retrieve correct binary content.
I attempted to dump the data directly into a file to inspect the content using following code (abridged).
from("restlet:/upload?restletMethod=POST")
.to("direct:save-files");
from("direct:save-files")
.process(new org.apache.camel.Processor(){
public void process(org.apache.camel.Exchange exchange){
/*
* Code to sniff exchange content
*/
}
})
.to("file:///C:/<path to a folder>");
;
I used org.apache.commons.fileupload.MultipartStream from apache fileuplaod library to write following utility class to parse Multipart request from a file. It worked successfully when the output of a mulitpart request from Postman was fed to it. However, failed to parse content of the file created by Camel (even through to eyes content of both files looked similar).
public class MultipartParserFileCreator{
public static final String DELIMITER = "\\r?\\n";
public static void main(String[] args) throws Exception {
// taking it from the content-type in exchange
byte[] boundary = "------5lXVNrZvONBWFXxd".getBytes();
FileInputStream fis = new FileInputStream(new File("<path-to-file>"));
extractFile(fis, boundary);
}
public static void extractFile(InputStream is, byte[] boundary) throws Exception {
MultipartStream multipartStream = new MultipartStream(is, boundary, 1024*4, null);
boolean nextPart = multipartStream.skipPreamble();
while (nextPart) {
String headers = multipartStream.readHeaders();
if(isFileContent(headers)) {
String filename = getFileName(headers);
File file = new File("<dir-where-file-created>"+filename);
if(!file.exists()) {
file.createNewFile();
}
FileOutputStream fos = new FileOutputStream(file);
multipartStream.readBodyData(fos);
fos.flush();
fos.close();
}else {
multipartStream.readBodyData(System.out);
}
nextPart = multipartStream.readBoundary();
}
}
public static String[] getContentDispositionTokens(String headersJoined) {
String[] headers = headersJoined.split(DELIMITER, -1);
for(String header: headers) {
System.out.println("Processing header: "+header);
if(header != null && header.startsWith("Content-Disposition:")) {
return header.split(";");
}
}
throw new RuntimeException(
String.format("[%s] header not found in supplied headers [%s]", "Content-Disposition:", headersJoined));
}
public static boolean isFileContent(String header) {
String[] tokens = getContentDispositionTokens(header);
for (String token : tokens) {
if (token.trim().startsWith("filename")) {
return true;
}
}
return false;
}
public static String getFileName(String header) {
String[] tokens = getContentDispositionTokens(header);
for (String token : tokens) {
if (token.trim().startsWith("filename")) {
String filename = token.substring(token.indexOf("=") + 2, token.length()-1);
System.out.println("fileName is " + filename);
return filename;
}
}
return null;
}
}
On debugging through the Camel code, I noticed that at one stage Camel is converting the entire content into String. After a point I had to stop pursuing this approach as there was very little on net applicable for version 2.12.4 and my work was not going anywhere.
Finally, I resorted to following solution
Write an implementation of HttpServletRequestWrapper to allow
multiple read of input stream. One can get an idea from
How to read request.getInputStream() multiple times
Create a filter that uses the above to wrap HttpServletRequest object, reads and extract the file to a directory Convenient way to parse incoming multipart/form-data parameters in a Servlet and attach the path to the request using request.setAttribute() method. With web.xml, configure this filter on restlet servlet
In the process method of camel route, type cast the
exchange.getIn().getBody() in HttpServletRequest object, extract the
attribute (path) use it to read the file as ByteStreamArray for
further processing
Not the cleanest, but I could achieve the objective.