How to host angular build files in springboot server? - java

I have a spring boot config file similar to following,
server:
port: 8002
servlet:
context-path: /api/
...
spring:
mvc:
static-path-pattern: "/resources/**"
...
I have copy pasted the build files from my angular project inside resources/resources/static.
Don't know if this is relevant.
Here goes my JwtFilter.java,
#Component
public class JwtFilter extends OncePerRequestFilter {
final private JwtUtil jwtUtil;
#Autowired
JwtFilter(JwtUtil jwtUtil) {
this.jwtUtil = jwtUtil;
}
#Override
protected void doFilterInternal(
HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
FilterChain filterChain
) throws ServletException, IOException {
Cookie[] cookies = httpServletRequest.getCookies();
if(cookies == null) {
cookies = new Cookie[]{};
}
Cookie jwtCookie = Arrays.stream(cookies)
.filter(cookie -> cookie.getName().equals(StringConstants.JWT_AT_COOKIE_NAME))
.findFirst()
.orElse(null);
Cookie rtCookie = Arrays.stream(cookies)
.filter(cookie -> cookie.getName().equals(StringConstants.RT_COOKIE_NAME))
.findFirst()
.orElse(null);
if (rtCookie == null) {
httpServletResponse.sendError(401, "REFRESH_TOKEN_NOT_FOUND");
return;
}
String jwt = null;
String uid = null;
try {
if (jwtCookie != null) {
jwt = jwtCookie.getValue();
uid = jwtUtil.extractSubject(jwt);
} else {
httpServletResponse.sendError(401, "User not authenticated!");
}
if (uid != null) {
if (!jwtUtil.validateToken(jwt)) {
httpServletResponse.sendError(401, "EXPIRED_JWT_TOKEN_EXCEPTION");
return;
}
}
} catch (SignatureException exception) {
httpServletResponse.sendError(403, exception.getMessage());
return;
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
#Override
protected boolean shouldNotFilter(HttpServletRequest request) {
String path = request.getRequestURI();
return path.equals("/api/auth") || path.equals("/api/auth/");
}
}
I have tried adding those js build files to META-INF/resources, public & resources.
No go there as well.
Dir structure is:
java/
...
resources/
resources/
META-INF/
resources/ [contains angular build files]
public/ [contains angular build files]
resources/ [contains angular build files]
static/ [contains angular build files]
Now if I go to http://localhost:8002/ then it just says
HTTP Status 404 – Not Found.

The project structure needs a bit more work on it.
Check out the question here.
Build angular 11 with Spring boot 2.x in one single jar
Delete this.
spring:
mvc:
static-path-pattern: "/resources/**"
You want to put npm build artifacts inside src/main/resources/static.
Firstly, npm run build and you will find the artifacts inside dist/.
Copy whatever from dist/ into src/main/resources/static/. For example, the dist/index.html will be copied and pasted into src/main/resources/static/index.html.
Force Springboot to Serve index.html
However, there is a catch here. If you do mind a hash fragment #/foo/bar in the url, you need to tell Springboot to serve index.html on 404.
#SpringBootApplication
public class BlogApiApplication {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(BlogApiApplication.class);
app.run(args);
}
#Bean
public EmbeddedServletContainerCustomizer containerCustomizer() {
return new EmbeddedServletContainerCustomizer() {
#Override
public void customize(ConfigurableEmbeddedServletContainer container) {
ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/index.html");
container.addErrorPages(error404Page);
}
};
}
}
In this way, you do not need hash mode. The same thing goes with React, Vue.
Reference

Related

Serving multiple static assets using Dropwizard

I have a react app that I am building and trying to serve using an AssetBundle, as such:
#Override
public void initialize(final Bootstrap<PersonalWebsiteConfiguration> bootstrap) {
bootstrap.addBundle(new SwaggerBundle<PersonalWebsiteConfiguration>() {
protected SwaggerBundleConfiguration getSwaggerBundleConfiguration(final PersonalWebsiteConfiguration configuration) {
return configuration.swaggerBundleConfiguration;
}
});
bootstrap.addBundle(new AssetsBundle("/build", "/", "index.html"));
}
I also added the configuration
server:
rootPath: /api
so there wouldn't be conflicts with my API.
This works great for just the landing page of my React app. Whenever I try the route /login /dashboard, this page from the UI not found. So I tried adding more bundles to fix that problem with the routing:
bootstrap.addBundle(new AssetsBundle("/build", "/", "index.html"));
bootstrap.addBundle(new AssetsBundle("/build", "/login", "index.html"));
bootstrap.addBundle(new AssetsBundle("/build", "/dashboard", "index.html"));
Now, only the dashboard is working. Does anyone know how to serve a React build with multiple routing/pages.
For a Single Page App you'd need every client route to return the index.html (to support browser reload or landing on paths other than /)
As far as I know, Dropwizard AssetBundle can't do that, i.e. serve all routes with index.html.
See similar (old) question.
You can implement a servlet filter yourself or use some community plugin like this one.
I must say that another approach worked better for me, don't use dropwizard for serving the static assets at all, only use it as a backend API.
Use CDN routing or different subdomains for API and static assets. That way you can have you static assets at www.mydomain.com and your API at api.mydomain.com (or use same domain and based on the path prefix e.g. /api route to backend or static resources)
You can add a filter to implement the functionality.
For a dropwizard plugin which implements this kind of filter see https://github.com/xvik/dropwizard-guicey-ext/tree/master/guicey-spa.
The following stand-alone example code is in Kotlin.
class SinglePageAppFilter : Filter {
override fun doFilter(servletRequest: ServletRequest,
servletResponse: ServletResponse,
chain: FilterChain) {
val request = servletRequest as HttpServletRequest
val response = servletResponse as HttpServletResponse
if (request.requestURI == "/" || request.requestURI.startsWith("/api")) {
chain.doFilter(servletRequest, servletResponse)
} else {
val wrapper = ResponseWrapper(response)
chain.doFilter(servletRequest, wrapper)
val sc = wrapper.sc
if (sc == HttpServletResponse.SC_NOT_FOUND) {
request.getRequestDispatcher("/").forward(request, response)
} else if (sc != null) {
response.sendError(sc)
}
}
}
override fun init(filterConfig: FilterConfig) {}
override fun destroy() {}
}
class ResponseWrapper(response: HttpServletResponse) : HttpServletResponseWrapper(response) {
var sc: Int? = null
override fun sendError(sc: Int) {
this.sc = sc
}
}
class MyApplication : Application<MyConfiguration>() {
override fun initialize(bootstrap: Bootstrap<MyConfiguration>) {
bootstrap.addBundle(AssetsBundle("/assets", "/", "index.html"))
}
override fun run(configuration: MyConfiguration, environment: Environment) {
environment.servlets().addFilter("SinglePageAppFilter", SinglePageAppFilter())
.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true, "/*")
}
}

spring boot serves index.html despite the resource handler mapping

I have a vuejs + spring boot app. All was working fine, but suddenly got this issue - requests to files in /js/, /css/, /img/ are returning the index.html content despite having a resource mapping pointing to classpath:/static.
Can't trance the original change which lead to the appearance of this problem. front-end works fine by itself (tried deploying to surge & zeit now), so i suppose the problem is that spring boot ignores the resource mapping.
spring boot v2.1.2
WebMvcConfig:
#Configuration
public class MyWebMvcConfig implements WebMvcConfigurer {
String baseApiPath = "/api";
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry
.addResourceHandler("/**/*.css", "/**/*.html", "/**/*.js", "/**/*.png", "/**/*.ttf")
.setCachePeriod(0)
.addResourceLocations("classpath:/static/");
registry.addResourceHandler("/")
.setCachePeriod(0)
.addResourceLocations("classpath:/static/index.html")
.resourceChain(true)
.addResolver(new PathResourceResolver() {
#Override
protected Resource getResource(String resourcePath, Resource location) throws IOException {
if (resourcePath.startsWith(baseApiPath) || resourcePath.startsWith(baseApiPath.substring(1))) {
return null;
}
return location.exists() && location.isReadable() ? location : null;
}
});
}
}
in index.html links like this <script src=/js/chunk-vendors.b7114b0e.js></script><script src=/js/app.5c7ddca5.js></script> returning the index.html itself.

How to make Basic Authentication work as an alternative for keycloak in a Angular JS/Spring boot app

We have migrated from Basic Authentication to Keycloak method in our project in the production environment. However we would like continue using Basic Authentication, for local development, standalone and demo instalations, which could be triggered by a profile or something like this.
In this project we have REST APIs developed with Java/Spring boot and an AngularJS application which consumes these APIs. We are using Keycloak to protect both AngularJS app and the APIs.
The problem is how to make Spring Security and Keycloak to work "together" in the same application with different profiles. The solution I found so far, was to configure both Spring Security and Keycloak, and made a workaround with properties files, as described below:
application-keycloak.properties
#Unactivate Basic Authentication
security.ignored=/**
application-local-auth.properties
#Unactivate Keycloak
spring.autoconfigure.exclude=org.keycloak.adapters.springboot.KeycloakSpringBootConfiguration
When I wanto to use keycloak, I have to ignore security in order to not have problems and when I want to use basic authentication I have to exclude Keycloak configuration in order to also prevent conflicts.
This is my Security Configuration class:
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().and()
.authorizeRequests()
.antMatchers("/","/scripts/**","/keycloak/isActive","/keycloak/config","/bower_components/**","/views/**","/fonts/**",
"/views/inventory/dialogs/**", "/services/**","/resources/**","/styles/**", "/info")
.permitAll()
.anyRequest()
.authenticated()
.and()
.csrf().disable();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("admin").password("admin").roles("ADMIN");
}
And this is my Keycloak Spring Boot configuration:
# Keycloak
keycloak.realm=local
keycloak.realmKey=MIIBIjANBgkqhkiG9wsIIBCgKCAQEAuJYmaWvF3YhifflJhspXOs8RJn74w+eVD8PtpVbu2cYG9OIa49P8SwqVn/kyJQr7kT3OlCq3XMZWBHe+JSzSz7KttKkhfFSfzISdKDKlkPena2H/i3FKlRZIldbeeuQNYdD6nMpzU6QWLwGF1cUAo1M11f2p99QI1FOhVPJSErWsjDsKpWqG+rMMjT1eos0QCNP7krx/yfMdlUyaJCYiDvpOAoec3OWXvDJovEajBNAZMWVXgJF90wAVPRF6szraA2m7K2gG9ozaCNWB0v4Sy6czekbKjqEBPJo45uEmGHd92V//uf/WQG4HSiuv8CTV+b6TQxKtZCpQpqp2DyCLewIDAQAB
keycloak.auth-server-url=http://localhost:8080/auth
keycloak.ssl-required=none
keycloak.resource=App-backend
keycloak.bearer-only=true
keycloak.credentials.secret=a714aede-5af9-4560-8c9d-d655c831772f
keycloak.securityConstraints[0].securityCollections[0].name=Secured API
keycloak.securityConstraints[0].securityCollections[0].authRoles[0]=ROLE_USER
keycloak.securityConstraints[0].securityCollections[0].patterns[0]=/api/*
It is working, however I think it is not an elegant solution. I have tried to implement this using the Keycloak property enable-basic-auth, but I could not understand how it works but it seems that it is just to protect Rest APIs, it does not allow the browser to create a session and use it for all the other requests.
Have someone ever had to implement something like this and can give me some better idea?
I managed to solve this. However, how beautiful my solution is is up for debate.
My use case is that I need to secure most of my endpoints using Keycloak but some (for batch processing) should just use Basic Auth. Configuring both has the downside that Keycloak tries to validate the Authorization Header even if it is Basic Auth so I needed to do three things.
Deactivate all automatic security for my batch route.
Write a custom request filter which secures the batch route.
Manipulate the servlet request object such that the zealous keycloak filter doesn't trip on it.
My security configuration.
#EnableWebSecurity
#EnableResourceServer
public class SecurityConfiguration extends KeycloakWebSecurityConfigureAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.authorizeRequests()
// usual configuration ...
.antMatchers("/api/v1/batch/**").permitAll() // decouple security for this route
.anyRequest().denyAll();
}
}
My custom request filter (needs to run before the spring security filter, thus the ordering annotation):
#Component
#Slf4j
#Order(Ordered.HIGHEST_PRECEDENCE + 2)
public class BasicAuthRequestFilter extends OncePerRequestFilter {
#Value("${batch.user}")
private String user;
#Value("${batch.password}")
private String password;
#Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain
) throws ServletException, IOException {
if (isBatchRequest(request)) {
SimpleHttpFacade facade = new SimpleHttpFacade(request, response);
if (AuthOutcome.AUTHENTICATED.equals(auth(facade))) {
filterChain.doFilter(new AuthentifiedHttpServletRequest(request), response);
}
log.debug("Basic auth failed");
SecurityContextHolder.clearContext();
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unable to authenticate with basic authentication");
return;
}
filterChain.doFilter(request, response);
}
private boolean isBatchRequest(HttpServletRequest request) {
return request.getRequestURI().startsWith("/api/v1/batch/");
}
private AuthOutcome auth(HttpFacade exchange) {
return extractToken(exchange.getRequest().getHeaders(HttpHeaders.AUTHORIZATION))
.map(token -> extractUserPw(token)
.filter(userpw -> verify(userpw.getFirst(), userpw.getSecond()))
.map(userpw -> AuthOutcome.AUTHENTICATED)
.orElse(AuthOutcome.FAILED))
.orElse(AuthOutcome.NOT_ATTEMPTED);
}
private Optional<String> extractToken(List<String> authHeaders) {
return authHeaders == null ? Optional.empty() : authHeaders.stream().map(authHeader -> authHeader.trim().split("\\s+"))
.filter(split -> split.length == 2)
.filter(split -> split[0].equalsIgnoreCase("Basic"))
.map(split -> split[1])
.findFirst();
}
private Optional<Pair<String, String>> extractUserPw(String token) {
try {
String userpw = new String(Base64.decode(token));
String[] parts = userpw.split(":");
if (parts.length == 2) {
return Optional.of(Pair.of(parts[0], parts[1]));
}
} catch (Exception e) {
log.debug("Basic Auth Token formatting error", e);
}
return Optional.empty();
}
private boolean verify(String user, String password) {
return (this.user.equals(user) && this.password.equals(password));
}
}
And finally the wrapped ServletRequest (as you cannot remove Headers from the request):
public class AuthentifiedHttpServletRequest extends HttpServletRequestWrapper {
public AuthentifiedHttpServletRequest(HttpServletRequest request) {
super(request);
}
#Override
public boolean authenticate(HttpServletResponse response) throws IOException, ServletException {
return true;
}
#Override
public String getAuthType() {
return "Basic";
}
#Override
public String getHeader(String name) {
if (!HttpHeaders.AUTHORIZATION.equalsIgnoreCase(name)) {
return super.getHeader(name);
}
return null;
}
#Override
public Enumeration<String> getHeaders(String name) {
if (!HttpHeaders.AUTHORIZATION.equalsIgnoreCase(name)) {
return super.getHeaders(name);
}
return Collections.enumeration(Collections.emptyList());
}
#Override
public Enumeration<String> getHeaderNames() {
return Collections.enumeration(EnumerationUtils.toList(super.getHeaderNames())
.stream()
.filter(s -> !HttpHeaders.AUTHORIZATION.equalsIgnoreCase(s))
.collect(Collectors.toList()));
}
#Override
public int getIntHeader(String name) {
if (!HttpHeaders.AUTHORIZATION.equalsIgnoreCase(name)) {
return super.getIntHeader(name);
}
return -1;
}
}
Not quite sure whether this is still relevant or not, but maybe someone will find it helpful.
By default, Keycloak is overwriting plenty of configurations. It's intercepting all Auth request (OAuth2, BasicAuth etc.)
Fortunately, with Keycloak, it's possible to enable authentication both with OAuth2 and BasicAuth in parallel, which I assume is what you want to enable in your dev/localhost environments.
In order to do that, you first need to add the following property to your
application-local-auth.properties:
keycloak.enable-basic-auth=true
This property will enable Basic Auth in your dev environment. However, you also need to enable Basic Auth at your client in Keycloak.
You can accomplish that by connecting to the Keycloak Admin Console on your local Keycloak server and enabling the Direct Access Grant for your client:
Enabling Basic Auth in Keycloak
After that you can authenticate both with Bearer Token and Basic Auth.

Spring-mvc 406 NOT Acceptable URl

I saw the relevant questions in stackoverflow but i didn't find a solution to my problem.
This is my Initializer Class:
public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
public static HashMap<String, String> response_code = new HashMap<String, String>();
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class[] { MainConfiguration.class };
}
#Override
protected Class<?>[] getServletConfigClasses() {
return null;
}
#Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
Security.addProvider(new BouncyCastleProvider());
servletContext.addListener(new MainContextListener());
}
}
This is the Controller:
#RestController
#Component
public class MainController {
#RequestMapping(value = "/getAll", method = RequestMethod.POST, produces = "application/json;charset=UTF-8")
public Int addNumber (
#RequestParam(value = "number", defaultValue = "0",
required = false) int number ) {
// code to set and return number
}
}
This is the Structure of webapp folder:
main/webapp/index.jsp
Css and Scripts folders are in webapp folder.
and I'm trying to run the project on intellij using tomcat web server.
the problem is when I run the project, index.jsp opens in a browser but it gives 406 not acceptable uri error on getAll.
You intercept all URLs (CSS as well)
protected String[] getServletMappings() {
return new String[] { "/" };
}
You need tyo exclude resources from the intercepting. See e.g. Spring 4.x Java code-based configuration: Static resource files and Dispatcher servlet
This should work
#Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}
But make sure that you put the static contents under resources folder.
Please note that webapp is not a part of the Java web application folder structure and that's the reason why the code you put in your previous comment is not working
after trying lots of ways I discovered I had missed one dependency in my pom file, so response wasn't sent as json and 406 error occured, the missing dependency was jackson:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.5.3</version>
</dependency>

Making Spring/Tomcat compatible with HTML5 pushState

I have a single-page web app that's using Backbone.js client routing with pushState. In order to get this to work, I have to tell my server (Java, Spring 3, Tomcat) which URLs should be resolved on the server (actual JSP views, API requets), and which should simply be sent to the index page to be handled by the client. Currently I'm using an InternalResourceViewResolver to simply serve JSP views that match the name of the URL request. Since client-side URLs don't have a view on the server, the server returns a 404.
What is the best way to specify to Spring (or Tomcat) that a few specific URLs (my client-side routes) should all resolve to index.jsp, and anything else should fall through to the InternalResourceViewResolver?
I found that Spring MVC 3 added a tag that does exactly what I need, the mvc:view-controller tag. This got it done for me:
<mvc:view-controller path="/" view-name="index" />
<mvc:view-controller path="/admin" view-name="index" />
<mvc:view-controller path="/volume" view-name="index" />
http://static.springsource.org/spring/docs/3.0.x/reference/mvc.html
In theory, to handle navigation via history.pushState you want to return index.html for unhandled resources. If you look at official documentation for modern web frameworks it's often realised based on 404 status.
In spring you should handle resources in order:
path mapped REST controllers
app static resources
index.html for others
To do this you have at least 4 possible solutions.
Using EmbeddedServletContainerCustomizer and custom 404 handler
#Controller
static class SpaController {
#RequestMapping("resourceNotFound")
public String handle() {
return "forward:/index.html";
}
}
#Bean
public EmbeddedServletContainerCustomizer containerCustomizer() {
return container -> container.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/resourceNotFound"));
}
Using custom default request mapping handler
#Autowired
private RequestMappingHandlerMapping requestMappingHandlerMapping;
static class SpaWithHistoryPushStateHandler {
}
static class SpaWithHistoryPushStateHandlerAdapter implements HandlerAdapter {
#Override
public boolean supports(final Object handler) {
return handler instanceof SpaWithHistoryPushStateHandler;
}
#Override
public ModelAndView handle(final HttpServletRequest request, final HttpServletResponse response, final Object handler) throws Exception {
response.getOutputStream().println("default index.html");
return null;
}
#Override
public long getLastModified(final HttpServletRequest request, final Object handler) {
return -1;
}
}
#Bean
public SpaWithHistoryPushStateHandlerAdapter spaWithHistoryPushStateHandlerAdapter() {
return new SpaWithHistoryPushStateHandlerAdapter();
}
#PostConstruct
public void setupDefaultHandler() {
requestMappingHandlerMapping.setDefaultHandler(new SpaWithHistoryPushStateHandler());
}
Using custom ResourceResolver
#Autowired
private ResourceProperties resourceProperties;
#Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**")
.addResourceLocations(resourceProperties.getStaticLocations())
.setCachePeriod(resourceProperties.getCachePeriod())
.resourceChain(resourceProperties.getChain().isCache())
.addResolver(new PathResourceResolver() {
#Override
public Resource resolveResource(final HttpServletRequest request, final String requestPath, final List<? extends Resource> locations, final ResourceResolverChain chain) {
final Resource resource = super.resolveResource(request, requestPath, locations, chain);
if (resource != null) {
return resource;
} else {
return super.resolveResource(request, "/index.html", locations, chain);
}
}
});
}
Using custom ErrorViewResolver
#Bean
public ErrorViewResolver customErrorViewResolver() {
final ModelAndView redirectToIndexHtml = new ModelAndView("forward:/index.html", Collections.emptyMap(), HttpStatus.OK);
return (request, status, model) -> status == HttpStatus.NOT_FOUND ? redirectToIndexHtml : null;
}
Summary
Fourth option looks simplest but as always it depends what you need. You may also want to restric returning index.html only when request expects text/html (which BasicErrorController already do based on "produces" header).
I hope one of this options will help in your case.
I would give a clear scheme to my urls and separate frontend from backend.
Some suggestions:
Route all requests starting by /server to the backend and all others to the frontend.
Setup two different domains, one for the backend, one for the frontend.

Categories

Resources