DEV Community

Bijit Mondal
Bijit Mondal

Posted on

Spring Boot with VueJS with Spring Security

This article was originally published in https://portfolio.bijit.xyz/blogs/spring-boot-with-vue-security-on

Configuring Spring Boot With Vue JS

Dan Vega has an wonderful article on configuring Vue on the frontend and Spring Boot on the backend using a monolithic approach, this design is suitable for a team of full-stack developers who will be working on both sides of the application.

I would recommend to go through the article first. https://www.danvega.dev/blog/full-stack-java.

What happens after adding Spring Security

After Integrating Spring Security into the application, by default, Spring Security will protect all the routes, which will lead to frontend path (/) getting protected as well.

This means that any request made to the application require authentication, including requests to frontend which is not what we want.

Solution

So what can we do? Whitelisting the root (/) path is not a good idea beacuse doing so will also expose the protected controller to unauthorized user. A more secure and recommended approach is to serve frontend from a different path, such as /app and then whitelist the /app path in Spring Security configuration to allow unauthenticated access to frontend.

Vue Configuration

So let's add the configuration in Vue application that will make the vue application be served from /app path. In /frontend folder edit vue.config.js file

const { defineConfig } = require('@vue/cli-service')

module.exports = defineConfig({
  transpileDependencies: true,
  publicPath: '/app/', // [!code focus]
  devServer: {
    client: {
      overlay: false,
    },
    port: 3000,
    proxy: {
      '/api': {
        target: 'http://192.168.43.28:8080',
        ws: true,
        changeOrigin: true
      }
    }
  }
})
Enter fullscreen mode Exit fullscreen mode

It will make the app to be served from a sub-path. For example, if the app is deployed at https://www.foobar.com/my-app/, set publicPath to '/my-app/'.

Spring Boot configuration

As discussed earlier, by default, Spring Boot serves all static content under the root part of the request, /**. We can change it via the spring.mvc.static-path-pattern configuration property.

Change your application.properties

spring.mvc.static-path-pattern=/app/*
Enter fullscreen mode Exit fullscreen mode

or if you are using yaml configuration change your application.yaml or application.yml

spring:
  mvc:
    static-path-pattern: /app/**
Enter fullscreen mode Exit fullscreen mode

Now that we have made the frontend to be served from "/app" sub-path, we need to whitelist the path.Made the following changes as per configuration.

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@EnableMethodSecurity
public class SecurityConfiguration {
    private static final String[] WHITE_LIST_URL = {
            "/api/v1/auth/**",
            "/app/**",  // [!code focus]
    };
    private final JWTAuthenticationFilter jwtAuthFilter;
    private final AuthenticationProvider authenticationProvider;
    private final LogoutHandler logoutHandler;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .csrf(AbstractHttpConfigurer::disable)
                .authorizeHttpRequests(req ->
                        req
                                .requestMatchers(WHITE_LIST_URL).permitAll() // [!code focus]
                                .requestMatchers("/api/v1/user/**").hasAnyRole(ADMIN.name(),USER.name())
                                .requestMatchers("/api/v1/admin/**").hasAnyRole(ADMIN.name())
                                .anyRequest()
                                .authenticated()
                )
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authenticationProvider(authenticationProvider)
                .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
        ;

        return http.build();
    }
}
Enter fullscreen mode Exit fullscreen mode

Your configuration file may be different, we just need to whitelist the /app. Here is my configuration on gitlab.

🎉 we have done it.

But wait, try to refresh the frontend and it will most probably fail. Why this happens?

This is because our vuejs application is pure client side SPA(Single Page Application), there is only one index.html file, and every request is hitting that html file and all subsequent requests are handled by the client-side JavaScript code. When we refresh the page, the browser sends a request to the server for the requested URL, but since there is no file for that URL, the server returns a 404 error.This is why refreshing the page in a Vue.js SPA can result in a failure.

So, if we redirect every request coming to /app/** we can forward it to index.html file it should work.
Create an FrontendForwardController for that.

@Controller
public class FrontendForwardController {
    //    TODO
    //    Better regex needed
    @GetMapping({ "/app","/app/","/app/problems/**","/app/problems","/app/auth","/app/auth/**","/app/admin","/app/admin/**"})
    public String forward() {
        return "forward:/app/index.html";
    }
}

Enter fullscreen mode Exit fullscreen mode

The above controller forwarding some frontend request like /app or /app/problems/** to index.html.

There is a problem though in this approach, whenever we add a new route to our frontend we need to allow it here at FrontendForwardController to forward the request to index.html.

public class FrontendForwardController {
    @GetMapping("/app/**") // [!code error]
    public String forward(){
        return "forward:/app/index.html";
    }
}
Enter fullscreen mode Exit fullscreen mode

We can't make the FrontendForwardController like above, because it will result in an endless recursion.

That's all.
Happy coding
Bijit

Top comments (0)