Backend Development 12 min read

Implementing Front‑Back End Separation with Spring Boot, Vue, and Shiro: CORS Handling and Deployment

This article explains how to build a front‑back end separated application using Spring Boot for the backend and Vue.js for the frontend, covering JSON APIs, CORS interception, Shiro security integration, custom session management, and two deployment strategies with code examples.

Java Captain
Java Captain
Java Captain
Implementing Front‑Back End Separation with Spring Boot, Vue, and Shiro: CORS Handling and Deployment

In a front‑back end separated architecture, the frontend is isolated from the backend and communicates solely through JSON APIs; the backend no longer returns HTML pages, only data.

Backend – Spring Boot

The controller returns JSON data via a POST endpoint:

@RequestMapping(value = "/add", method = RequestMethod.POST)
@ResponseBody
public JSONResult addClient(@RequestBody String param) {
    JSONObject jsonObject = JSON.parseObject(param);
    String task = jsonObject.getString("task");
    List
list = jsonObject.getJSONArray("attributes");
    List
attrList = new LinkedList<>(list);
    Client client = JSON.parseObject(jsonObject.getJSONObject("client").toJSONString(), new TypeReference
(){});
    clientService.addClient(client, task, attrList);
    return JSONResult.ok();
}

The POST request receives parameters via @RequestBody .

Frontend – Vue + ElementUI + Vue‑Router + Vuex + Axios + Webpack

The frontend stack references the official Vue guide and popular admin templates. Common issues include cross‑origin requests because the webpack dev server runs on a different port.

CORS Handling

Two solutions are presented:

When the backend is self‑developed, add a Spring interceptor to set the required CORS headers:

@Component
public class CommonInterceptor implements HandlerInterceptor {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        response.setHeader("Access-Control-Allow-Origin", "*");
        if (request.getMethod().equals("OPTIONS")) {
            response.addHeader("Access-Control-Allow-Methods", "GET,HEAD,POST,PUT,DELETE,TRACE,OPTIONS,PATCH");
            response.addHeader("Access-Control-Allow-Headers", "Content-Type, Accept, Authorization");
        }
        return true;
    }
}

When the backend is not under your control, a proxyTable can be configured in the webpack dev server (only for development).

Shiro Integration in a Front‑Back End Separated Project

Because pre‑flight OPTIONS requests do not carry the Authorization header, they fail Shiro authentication. A custom filter extending FormAuthenticationFilter is added to allow OPTIONS requests and to return a JSON error for unauthenticated access:

public class CORSAuthenticationFilter extends FormAuthenticationFilter {
    @Override
    public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        if (request instanceof HttpServletRequest) {
            if (((HttpServletRequest) request).getMethod().toUpperCase().equals("OPTIONS")) {
                return true;
            }
        }
        return super.isAccessAllowed(request, response, mappedValue);
    }
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletResponse res = (HttpServletResponse) response;
        res.setHeader("Access-Control-Allow-Origin", "*");
        res.setStatus(HttpServletResponse.SC_OK);
        res.setCharacterEncoding("UTF-8");
        PrintWriter writer = res.getWriter();
        Map
map = new HashMap<>();
        map.put("code", 702);
        map.put("msg", "未登录");
        writer.write(JSON.toJSONString(map));
        writer.close();
        return false;
    }
}

The Shiro configuration registers this filter and defines the filter chain:

@Configuration
public class ShiroConfig {
    @Bean
    public Realm realm() { return new DDRealm(); }
    @Bean
    public CacheManager cacheManager() { return new MemoryConstrainedCacheManager(); }
    @Bean
    public SimpleCookie rememberMeCookie() {
        SimpleCookie cookie = new SimpleCookie("rememberMe");
        cookie.setMaxAge(259200);
        return cookie;
    }
    @Bean
    public CookieRememberMeManager rememberMeManager() {
        CookieRememberMeManager manager = new CookieRememberMeManager();
        manager.setCookie(rememberMeCookie());
        manager.setCipherKey(Base64.decode("2AvVhdsgUs0FSA3SDFAdag=="));
        return manager;
    }
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager sm = new DefaultWebSecurityManager();
        sm.setRealm(realm());
        sm.setCacheManager(cacheManager());
        sm.setRememberMeManager(rememberMeManager());
        sm.setSessionManager(sessionManager());
        return sm;
    }
    @Bean
    public SessionManager sessionManager() { return new CustomSessionManager(); }
    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager);
        Map
filterChainDefinitionMap = new LinkedHashMap<>();
        filterChainDefinitionMap.put("/", "anon");
        filterChainDefinitionMap.put("/static/**", "anon");
        filterChainDefinitionMap.put("/login/**", "anon");
        filterChainDefinitionMap.put("/**", "corsAuthenticationFilter");
        shiroFilter.setFilterChainDefinitionMap(filterChainDefinitionMap);
        Map
filterMap = new LinkedHashMap<>();
        filterMap.put("corsAuthenticationFilter", corsAuthenticationFilter());
        shiroFilter.setFilters(filterMap);
        return shiroFilter;
    }
    @Bean
    public CORSAuthenticationFilter corsAuthenticationFilter() { return new CORSAuthenticationFilter(); }
    // additional beans for lifecycle, advisors, etc.
}

A custom CustomSessionManager overrides getSessionId to read the session ID from the Authorization header and extends the default timeout:

public class CustomSessionManager extends DefaultWebSessionManager {
    private static final String AUTHORIZATION = "Authorization";
    private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
    public CustomSessionManager() {
        super();
        setGlobalSessionTimeout(DEFAULT_GLOBAL_SESSION_TIMEOUT * 48);
    }
    @Override
    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
        String sessionId = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
        if (!StringUtils.isEmpty(sessionId)) {
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sessionId);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
            return sessionId;
        } else {
            return super.getSessionId(request, response);
        }
    }
}

Deployment

Two deployment approaches are described:

Package the Vue project into static files ( npm run build ) and serve them from a Spring Boot controller, eliminating cross‑origin issues.

Run the frontend as a separate service (Tomcat, Nginx, Node.js), which re‑introduces CORS considerations.

Example controller for serving the index page:

@RequestMapping(value = {"/", "/index"}, method = RequestMethod.GET)
public String index() {
    return "/index";
}

Additional deployment challenges such as Nginx reverse‑proxy redirects from HTTPS to HTTP and handling unauthenticated requests without Shiro’s default redirect are also discussed.

The article concludes with a reminder that the presented solutions may need adjustments for specific projects.

deploymentSpring BootVueCORSshirofrontend-backend
Java Captain
Written by

Java Captain

Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.