Embedding Vaadin spring-boot application into HTML - spring-boot

I've already posted this question in the Vaadin Forum, unfortunately I did not get any response - maybe answer of the question lies somewhere between spring-boot and Vaadin.
currently I'm having a hard time embedding a Vaadin application into an HTML page.
What do I use:
Vaadin 7.6.6
vaadin-spring
spring-boot 1.3.5.RELEASE
To enable CORS in combination with spring-boot, I adapted Sami's Blog entry and created the following custom CORS servlet:
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import com.vaadin.spring.server.SpringVaadinServlet;
/**
* This custom {#link SpringVaadinServlet} enables CORS in combination with
* Spring.
*
* #author Christoph Guse
*
*/
public class CORSServlet extends SpringVaadinServlet {
/**
*
*/
private static final long serialVersionUID = -2482991123719720492L;
/**
* Override to handle the CORS requests.
*/
#Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// Origin is needed for all CORS requests
String origin = request.getHeader("Origin");
if (origin != null && isAllowedRequestOrigin(origin)) {
// Handle a preflight "option" requests
if ("options".equalsIgnoreCase(request.getMethod())) {
response.addHeader("Access-Control-Allow-Origin", origin);
response.setHeader("Allow", "GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS");
// allow the requested method
String method = request.getHeader("Access-Control-Request-Method");
response.addHeader("Access-Control-Allow-Methods", method);
// allow the requested headers
String headers = request.getHeader("Access-Control-Request-Headers");
response.addHeader("Access-Control-Allow-Headers", headers);
response.addHeader("Access-Control-Allow-Credentials", "true");
response.setContentType("text/plain");
response.setCharacterEncoding("utf-8");
response.getWriter().flush();
return;
} // Handle UIDL post requests
else if ("post".equalsIgnoreCase(request.getMethod())) {
response.addHeader("Access-Control-Allow-Origin", origin);
response.addHeader("Access-Control-Allow-Credentials", "true");
super.service(request, response);
return;
}
}
// All the other requests nothing to do with CORS
super.service(request, response);
}
/**
* Check that the page Origin header is allowed.
*/
private boolean isAllowedRequestOrigin(String origin) {
// TODO: Remember to limit the origins.
return origin.matches(".*");
}
}
Additionally I found some documentation about spring-boot and CORS, so I added this Spring configuration:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import CORSServlet;
/**
* #author Christoph Guse
*
*/
#Configuration
public class AuthAppVaadinApplicationConfiguration {
#Bean
public WebMvcConfigurer corsConfigurer(){
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins(".*");
}
};
}
#Bean(name="vaadinServlet")
public CORSServlet corsServlet(){
return new CORSServlet();
}
}
My HTML looks like this:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type"
content="text/html; charset=UTF-8" />
<meta http-equiv="X-UA-Compatible"
content="IE=9;chrome=1" />
<title>Embedding a Vaadin Application in HTML Page</title>
<!-- Set up the favicon from the Vaadin theme -->
<link rel="shortcut icon" type="image/vnd.microsoft.icon"
href="/VAADIN/themes/reindeer/favicon.ico" />
<link rel="icon" type="image/vnd.microsoft.icon"
href="/VAADIN/themes/reindeer/favicon.ico" />
</head>
<body>
<!-- Loads the Vaadin widget set, etc. -->
<script type="text/javascript"
src="http://vaadin.poc:8090/VAADIN/vaadinBootstrap.js?v=7.6.6"></script>
<h1>Embedding a Vaadin UI</h1>
<p>This is a static web page that contains an embedded Vaadin
application. It's here:</p>
<!-- So here comes the div element in which the Vaadin -->
<!-- application is embedded. -->
<div style="width: 100%; height: 75vh; border: 2px solid green;"
id="helloworld" class="v-app">
<!-- Optional placeholder for the loading indicator -->
<div class=" v-app-loading"></div>
<!-- Alternative fallback text -->
<noscript>You have to enable javascript in your browser to
use an application built with Vaadin.</noscript>
</div>
<script type="text/javascript">//<![CDATA[
if (!window.vaadin)
alert("Failed to load the bootstrap JavaScript: "+
"VAADIN/vaadinBootstrap.js");
/* The UI Configuration */
vaadin.initApplication("helloworld", {
"browserDetailsUrl": "http://vaadin.poc:8090/",
"serviceUrl": "http://vaadin.poc:8090/",
"theme": "valo",
"versionInfo": {"vaadinVersion": "7.6.6"},
"widgetset": "com.vaadin.DefaultWidgetSet",
"vaadinDir": "http://vaadin.poc:8090/VAADIN/",
"heartbeatInterval": 300,
"debug": true,
"standalone": false,
"authErrMsg": {
"message": "Take note of any unsaved data, "+
"and <u>click here<\/u> to continue.",
"caption": "Authentication problem"
},
"comErrMsg": {
"message": "Take note of any unsaved data, "+
"and <u>click here<\/u> to continue.",
"caption": "Communication problem"
},
"sessExpMsg": {
"message": "Take note of any unsaved data, "+
"and <u>click here<\/u> to continue.",
"caption": "Session Expired"
}
});//]] >
</script>
<p>Please view the page source to see how embedding works.</p>
</body>
</html>
My problem is the application is initally loaded, but several icons are missing and if I trigger an action in the appliction, i.e. open a dropbox, then the application is not able to do the connect to the spring-boot application.
Error messages look like this:
XMLHttpRequest cannot load http://vaadin.poc:8090/UIDL/?v-uiId=0. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access. The response had HTTP status code 403.
Is there anybody out there who managed to embed a Vaadin spring-boot application into another HTML application?
Any hint is highly appreciated!
Christoph

fortunately someone in the Vaadin forum gave me the missing link. I forgot to add some JavaScript in the standalone HTML:
<script>
XMLHttpRequest.prototype._originalSend = XMLHttpRequest.prototype.send;
var sendWithCredentials = function(data) {
this.withCredentials = true;
this._originalSend(data);
};
XMLHttpRequest.prototype.send = sendWithCredentials;
</script>
That helped, but the fonts were not properly loaded by CORS problems, so I removed the custom Vaadin CORSServlet and added the filter based CORS support provided by spring-boot (as explained in this blog article).
My example now runs properly, the demo application is fully functional, fonts are loaded and used correctly.
Please have a look at https://github.com/flexguse/vaadin-html-embedding to get the fully working example.
Cheers,
Christoph

Related

Spring boot #RestController fails with 403 error after adding authentication

I am using the Spring – REST Controller tutorial which works just as advertized. I then added a JavaScript ajax snipped which sends a POST request that works too.
After that I added authentication to our ActiveDirectory server. The authentication itself works, but all POST request are now rejected with a 403 error at the web browser console. My controller method is never called.
Surprisingly all GET requests still work, both by modifying the ajax snippet to use the GET methods, or by manually entering the url in the web browser. Also standard html forms using POST requests work too (however they are using #Controller instead of #RestController annotations).
So it seems to me that the JavaScript ajax code is missing some essential headers, or maybe the Spring security configuration blocks such request intentionally. Please advise how to fix this problem.
Java code:
#RestController
public class Controller {
#PostMapping({"/rest/api/1/command"})
public void command(#RequestBody String s) {
System.out.println("we got: " + s);
}
...
}
JavaScript Code:
<script>
$.ajax({
async: false,
type: "POST",
url: "/rest/api/1/command",
contentType: "application/json",
dataType: "json",
data: { command: "run" },
success: function (data) {
return true;
}
});
</script>
Authentication Java code:
package com.example.demo;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider;
#Configuration
public class ActiveDirectoryConfig extends WebSecurityConfigurerAdapter {
#Value("${activedirectory.domain}")
private String domain;
#Value("${activedirectory.url}")
private String url;
#Value("${activedirectory.rootdn}")
private String rootdn;
#Value("${activedirectory.searchfilter}")
private String searchfilter;
#Value("${admin.username:}")
private String adminusername;
#Value("${admin.password:}")
private String adminpassword;
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception
{
ActiveDirectoryLdapAuthenticationProvider adProvider =
new ActiveDirectoryLdapAuthenticationProvider(
domain, url, rootdn);
adProvider.setConvertSubErrorCodesToExceptions(true);
adProvider.setUseAuthenticationRequestCredentials(true);
if (searchfilter != null)
adProvider.setSearchFilter(searchfilter);
auth.authenticationProvider(adProvider);
auth.eraseCredentials(false);
}
}
After Marcus Hert da Coregio's comment I was able to fix the program by adding the CSRF token to the ajax call. The actual value of the CSRF token will be provided by the Thymeleaf template.
Code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta id="_csrf" name="_csrf" th:content="${_csrf.token}"/>
<meta id="_csrf_header" name="_csrf_header" th:content="${_csrf.headerName}"/>
<title>Title</title>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous">
</script>
</head>
<body>
<script>
var token = $('#_csrf').attr('content');
var header = $('#_csrf_header').attr('content');
$.ajax({
async: false,
type: "POST",
url: "/rest/api/1/command",
contentType: "application/json",
dataType: "json",
data: { command: "run" },
beforeSend: function(xhr) {
xhr.setRequestHeader(header, token);
},
success: function (data) {
return true;
}
});
</script>
<p>hello world</p>
</body>
</html>

Content-Security-Policy nonce mismatch on error pages (e.g. 404)

We use a Content-Security-Policy nonce successfully on normal pages (Thymeleaf templates). But when we try to do the same on error pages (e.g. 404), the nonce received as a model attribute does not match the nonce specified in the Content-Security-Policy HTTP header. This mismatch causes a policy violation and therefore script errors in our custom error page (also generated from a Thymeleaf template). In the Chrome console, the errors are
Content Security Policy: The page’s settings blocked the loading of a resource at http://localhost:8080/webjars/jquery/3.6.0/jquery.min.js (“script-src”).
Content Security Policy: The page’s settings blocked the loading of a resource at inline (“script-src”).
We enable the policy in the Spring Security configuration:
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return
http.headers()
.contentSecurityPolicy("script-src 'strict-dynamic' 'nonce-{nonce}'")
.and().and()
.addFilterBefore(new ContentSecurityPolicyNonceFilter(), HeaderWriterFilter.class)
.build();
}
The filter is:
public class ContentSecurityPolicyNonceFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
var nonceArray = new byte[32];
(new SecureRandom()).nextBytes(nonceArray);
var nonce = Base64Utils.encodeToString(nonceArray);
request.setAttribute("cspNonce", nonce);
chain.doFilter(request, new NonceResponseWrapper((HttpServletResponse) response, nonce));
}
}
NonceResponseWrapper is
class NonceResponseWrapper extends HttpServletResponseWrapper {
private final String nonce;
NonceResponseWrapper(HttpServletResponse response, String nonce) {
super(response);
this.nonce = nonce;
}
private String getHeaderValue(String name, String value) {
final String retVal;
if (name.equals("Content-Security-Policy") && StringUtils.hasText(value)) {
retVal = value.replace("{nonce}", nonce);
} else {
retVal = value;
}
return retVal;
}
#Override
public void setHeader(String name, String value) {
super.setHeader(name, getHeaderValue(name, value));
}
#Override
public void addHeader(String name, String value) {
super.addHeader(name, getHeaderValue(name, value));
}
}
The nonce value is provided to the page via ControllerAdvice:
#ControllerAdvice
public class ContentSecurityPolicyControllerAdvice {
#ModelAttribute
public void addAttributes(Model model, HttpServletRequest request) {
model.addAttribute("nonce", request.getAttribute("cspNonce"));
}
}
The working index page and the dysfunctional error page follow the same pattern in Thymeleaf and HTML terms:
index.html:
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<title>Application</title>
<script th:src="#{/webjars/jquery/3.6.0/jquery.min.js}" th:nonce="${nonce}"></script>
<script th:inline="javascript" th:nonce="${nonce}">
const randomNumber = /*[[${randomNumber}]]*/ -1;
$(function() {
$('#a-number').text(randomNumber);
});
</script>
</head>
<body>
<h1>Welcome</h1>
<p>Your random number is <span id="a-number">unknown</span>.</p>
</body>
</html>
error/404.html:
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<title>404 Error</title>
<script th:src="#{/webjars/jquery/3.6.0/jquery.min.js}" th:nonce="${nonce}"></script>
<script th:nonce="${nonce}">
$(function() {
const timestampString = new Date().toISOString();
$('#timestamp').text(timestampString);
});
</script>
</head>
<body>
<h1>404 - Page Not Found</h1>
<p>The current time is <span id="timestamp">unknown</span>.</p>
</body>
</html>
Application debug output when loading the invalid URL shows
Nonce for request = qPhdJiUAAkKHrwQBvxzxUz0OUUU4UXaxLcDErhl4g7U=
Content-Security-Policy = script-src 'strict-dynamic' 'nonce-qPhdJiUAAkKHrwQBvxzxUz0OUUU4UXaxLcDErhl4g7U='
Nonce for request = OiZmhtGlYMgb4X+pcFIwM41GzEkre3YvfkLCHFqoqIU=
Nonce for view model = OiZmhtGlYMgb4X+pcFIwM41GzEkre3YvfkLCHFqoqIU=
Nonce for request = sCbXWXA0TPjw+I/dui2bmee1vKKXG1Y2Xt3G7JkuZ04=
Content-Security-Policy = script-src 'strict-dynamic' 'nonce-sCbXWXA0TPjw+I/dui2bmee1vKKXG1Y2Xt3G7JkuZ04='
Nonce for request = hsGwh4+5oqg0W51zNprrT41rHnEeJRdHHO8KTMCSwL8=
Content-Security-Policy = script-src 'strict-dynamic' 'nonce-hsGwh4+5oqg0W51zNprrT41rHnEeJRdHHO8KTMCSwL8='
In this run, the nonce interpolated into the policy is qPhdJiUAAkKHrwQBvxzxUz0OUUU4UXaxLcDErhl4g7U=, while the page is getting OiZmhtGlYMgb4X+pcFIwM41GzEkre3YvfkLCHFqoqIU= from the view model.
I've constructed a minimal, runnable (./gradlew bootRun) code base for this problem at https://gitlab.com/russell-medisens/nonce-problem.git for anyone who might take a look.
I believe I've solved this problem by changing the filter to avoid overwriting an existing nonce:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
final String nonce;
final Object existingNonce = request.getAttribute(REQUEST_NONCE_ATTRIBUTE);
if (existingNonce == null) {
final var nonceArray = new byte[NONCE_SIZE];
SECURE_RANDOM.nextBytes(nonceArray);
nonce = Base64Utils.encodeToString(nonceArray);
request.setAttribute(REQUEST_NONCE_ATTRIBUTE, nonce);
System.err.format("Nonce generated in filter = %s%n", nonce);
} else {
nonce = (String) existingNonce;
System.err.format("Existing nonce retained in filter = %s%n", nonce);
}
chain.doFilter(request, new NonceResponseWrapper((HttpServletResponse) response, nonce));
}
My understanding is that when a requested page is not found, Spring performs a forward (rather than a redirect), but the filter is invoked a second time in the process of serving the substituted 404 page. This code change preserves any existing nonce so that it can be provided to the view model for the error page.

Redirect almost all requests to index.html

I'm creating a web application for which I'm using Vue for the frontend and Spring Boot for the backend. Spring Boot serves index.html at / and /index.html, but I want it to be served at other URL's too, for example /account, which in turn will be detected by Vue's Router and will show the proper page.
Additionally, I have some other URL's I don't want to serve index.html. All of them start with /api, meaning that's the place where the Vue app sends requests.
Any help will be appreciated. Thanks.
What you want to do is called an SPA (single page application). In order to achive this you need to do two things:
Tell vue-router to use HTML5 history push: https://router.vuejs.org/guide/essentials/history-mode.html#example-server-configurations
Tell SpringBoot to serve the index.html when it cannot find a relevant route. Here is a good guide on how to do it using a handler for NoHandlerFoundException: https://medium.com/#kshep92/single-page-applications-with-spring-boot-b64d8d37015d
I have to warn you: when you configure history mode in step 1., click something, it will look like your SPA is already working (no # sign). Beware that this is an illusion. Vue-router tells the browser how the url should look like, but when you refresh the page, the server will return 404. You have to configure step 2 as well.
Because in my application I do not have only VUE in the user interface, redirect all errors to the VUE index.html as is proposed before is not acceptable in my scenario.
Finally, I have solved in another manner using filters ( basically the idea is to intercept all URL that are not css, js, images, etc... used in my VUE UI and take control of the response). In my case the VUE URL starts with "/context/kmobile/":
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
#Component
public class Html5PathFilter extends OncePerRequestFilter {
private static final Logger log = LoggerFactory.getLogger(Html5PathFilter.class);
// Capture the content of a file from /webapps/kmobile/index.html
// Inspired by https://stackoverflow.com/questions/30431025/spring-how-to-access-contents-of-webapp-resources-in-service-layer
#Value("/kmobile/index.html")
private Resource indexResource;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
String path = request.getServletPath();
if (!path.endsWith(".css") && !path.endsWith(".js") && !path.endsWith(".ico") && !path.endsWith(".html") &&
!path.endsWith("/kmobile/")) {
// log.info("YES, do redirect ->" + path);
// Code warning, initially were using redirect, that's a bad practice because from browser get the index.html url what never should be used directly there
// response.sendRedirect(request.getContextPath() + "/kmobile/index.html");
// Disable browser cache
response.setHeader("Expires", "Sat, 6 May 1971 12:00:00 GMT");
response.setHeader("Cache-Control", "must-revalidate");
response.addHeader("Cache-Control", "no-store, no-cache, must-revalidate");
response.addHeader("Cache-Control", "post-check=0, pre-check=0");
response.addHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Pragma", "no-cache");
InputStream is = indexResource.getInputStream();
// Set MIME type
response.setContentType("text/html");
// Content leght
response.setContentLength(is.available());
try (ServletOutputStream out = response.getOutputStream()) {
IOUtils.copy(is, out);
out.flush();
}
return;
} else {
// log.info("NO, do redirect ->" + path);
}
} catch (Exception e) {
log.debug("Error: {}", e.getMessage(), e);
}
//log.info("--> {}", request.getServletPath());
filterChain.doFilter(request, response);
}
#Override
protected boolean shouldNotFilter(HttpServletRequest request) {
String path = request.getServletPath();
boolean valid = path.startsWith("/kmobile");
if (valid) {
log.info("path: {} => {}", path, valid);
}
return !valid;
}
}

HTTP 405 with embedded VAADIN UI in a SPRING-BOOT JSP based webapp

I have integrated Vaadin 7 embedding the UI, as explained in Vaadin Book, in some JSP pages of a Spring Boot based application.
Despite the UI named "v-my-vaadin-ui" is correctly displayed when I call the path in which it is embedded (through a spring MVC controller) I got an HTTP 405 error when interacting with the UI.
Error is on the URL:
http://localhost:8080/v-my-vaadin-ui/UIDL/?v-uiId=4
It seems that because in Spring Boot all controllers have only GET method allowed by default (POST has to be explicitly allowed as explained here)
Already tried disabling the CSRF and configuring Vaadin ralated paths in my Spring Security Configuration:
http.authorizeRequests()
.antMatchers("/vaadinServlet/**",
"/UIDL/**",
"/v-my-vaadin-ui/UIDL/**",
"/v-my-vaadin-ui/PUSH/**",
"/HEARTBEAT/**", "/VAADIN/**").permitAll();
http.csrf().disable();
because VAADIN integration is requiring no CSRF managed by Spring but this doesn't solve the problem.
The VAADIN application is very basic:
#SpringUI(path = "v-my-vaadin-ui")
public class MyVaadinUI extends UI {
private static final long serialVersionUID = -8129815147461786549L;
#Override
protected void init(VaadinRequest vaadinRequest) {
final TextField name = new TextField("Name");
final Button greetButton = new Button("Greet");
greetButton.addClickListener(new ClickListener() {
#Override
public void buttonClick(ClickEvent event) {
Notification.show("Hi " + name.getValue(),
Notification.Type.HUMANIZED_MESSAGE);
}
});
VerticalLayout vl = new VerticalLayout(name, greetButton);
vl.setSpacing(true);
setContent(vl);
}
}
But clicking on the Vaadin button the response code is 405 and the exception in detail is:
.w.s.m.a.ResponseStatusExceptionResolver : Resolving exception from handler [ResourceHttpRequestHandler [locations=[ServletContext resource [/], class path resource [META-INF/resources/], class path resource [resources/], class path resource [static/], class path resource [public/]], resolvers=[org.springframework.web.servlet.resource.PathResourceResolver#4a8756c3]]]: org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' not supported
Unfortunately I didn't find any way to configure the POST method for the UI as for a simple controller i.e. via
#RequestMapping(method = {RequestMethod.GET, RequestMethod.POST})
because this annotation cannot be used for Vaadin UI.
Moreover if I call directly the UI (i.e. not passing through a Spring controller) with the URL:
http://localhost:8080/v-my-vaadin-ui
UI displays and works perfectly.
Any idea what is causing this problem? How can I allow the POST method?
Have you tried with this add-on? https://vaadin.com/directory#!addon/jsp-integration
SOLVED!!
The problem was caused by an error I made in the JS script I was using to embed the VAADIN app (I've followed the documentation here: https://vaadin.com/docs/-/part/framework/advanced/advanced-embedding.html) in particular the code fragment for "serviceUrl" parameter:
"serviceUrl": "helloworld/",
that I've understood to be set with the name of the application, in my case "v-my-vaadin-ui".
But the correct "serviceUrl" in my case, is still the default one "vaadinServlet/" so changing from:
"serviceUrl": "v-my-vaadin-ui/",
to
"serviceUrl": "vaadinServlet/",
everything works.
Here below the complete code of the JSP tag to embed the vaadin app that works for me:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<%#tag description="ajax widget tag" pageEncoding="UTF-8"%>
<%#attribute name="v_app_url"%>
<%-- Javascript as described here: https://vaadin.com/docs/-/part/framework/advanced/advanced-embedding.html --%>
<div>
<div id="${v_app_url}" class="v-app myvaadinui" >
<div class="v-app-loading"></div>
<noscript>
You have to enable javascript in your browser to use an application built with Vaadin.
</noscript>
</div>
<script type="text/javascript">//<![CDATA[
if (!window.vaadin)
alert("Failed to load the bootstrap JavaScript: vaadinBootstrap.js");
/* The UI Configuration */
vaadin.initApplication("${v_app_url}", {
"browserDetailsUrl": "${v_app_url}/",
"serviceUrl": "vaadinServlet/",
"widgetset": "com.vaadin.DefaultWidgetSet",
"theme": "valo",
"versionInfo": {"vaadinVersion": "7.4.4"},
"vaadinDir": "/VAADIN/",
"heartbeatInterval": 300,
"debug": false,
"standalone": false,
"authErrMsg": {
"message": "Take note of any unsaved data, "+
"and <u>click here<\/u> to continue.",
"caption": "Authentication problem"
},
"comErrMsg": {
"message": "Take note of any unsaved data, "+
"and <u>click here<\/u> to continue.",
"caption": "Communication problem"
},
"sessExpMsg": {
"message": "Take note of any unsaved data, "+
"and <u>click here<\/u> to continue.",
"caption": "Session Expired"
}
});//]] >
</script>
</div>

Keep getting 401 (Authorization Required) with spring social twitter

I've been trying this Spring Social Accessing Twitter Data guide. And though I've double, triple an so on checked everything I keep getting this error when I click "Connect to Twitter" button:
POST request for "https://api.twitter.com/oauth/request_token" resulted in 401 (Authorization Required); invoking error handler
Here is my code:
src/main/resources/templates/connect/twitterConnect.html
<html>
<head>
<title>Hello Twitter</title>
</head>
<body>
<h3>Connect to Twitter</h3>
<form action="/connect/twitter" method="POST">
<div class="formInfo">
<p>You aren't connected to Twitter yet. Click the button to connect this application with your Twitter account.</p>
</div>
<p><button type="submit">Connect to Twitter</button></p>
</form>
</body>
src/main/resources/templates/connect/twitterConnected.html
<html>
<head>
<title>Hello Twitter</title>
</head>
<body>
<h3>Connected to Twitter</h3>
<p>
You are now connected to your Twitter account.
Click here to see your Twitter friends.
</p>
</body>
src/main/resources/templates/hello.html
<html>
<head>
<title>Hello Twitter</title>
</head>
<body>
<h3>Hello, <span th:text="${twitterProfile.name}">Some User</span>!</h3>
<h4>These are your friends:</h4>
<ul>
<li th:each="friend:${friends}" th:text="${friend.name}">Friend</li>
</ul>
</body>
src/main/java/hello/HelloController.java
#Controller
#RequestMapping("/")
public class HelloController {
private Twitter twitter;
private ConnectionRepository connectionRepository;
#Inject
public HelloController(Twitter twitter, ConnectionRepository connectionRepository) {
this.twitter = twitter;
this.connectionRepository = connectionRepository;
}
#RequestMapping(method=RequestMethod.GET)
public String helloTwitter(Model model) {
if (connectionRepository.findPrimaryConnection(Twitter.class) == null) {
return "redirect:/connect/twitter";
}
model.addAttribute(twitter.userOperations().getUserProfile());
CursoredList<TwitterProfile> friends = twitter.friendOperations().getFriends();
model.addAttribute("friends", friends);
return "hello";
}
}
src/main/java/hello/Application.java
#Configuration
#EnableAutoConfiguration
#ComponentScan
public class Application {
/*
* SPRING BOOTSTRAP MAIN
*/
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
I ran into the same problem.
After some investigation I found out the problem is in the callback url. Spring social sets this to yourap/signin/twitter. For facebook and linkedin this is fine, but for some reason twitter nees a filled in callback url in the application settings as well.
So the solution: Go to your twitter application settings, and fill in a callback url (this doesn't even have to be the actual callback url your going to use, just fill in any url!). Most likely this is the cause of your problems.
What does your application.properties file look like? It should have entries for spring.social.twitter.appId and spring.social.twitter.appSecret, populated with values you get when registering your application with Twitter.
Be sure that you have those in application.properties and that there are no curly-braces around the values (the guide's text shows curly-braces, but those are meant as placeholders, not something you should actually have in application.properties).

Resources