Start learning free

LESSON 96 OF 173 · BACKEND: USERS AND SECURITY

CORS

Practice

Your browser runs the JavaScript of every page you open. So browsers follow a rule: a page may read answers only from its own site, unless the other site allows it. Without that rule, a page on evil.example.com could call the shop's API from your browser and read your data. The way a site says which other sites may read its answers is CORS (Cross-Origin Resource Sharing).

The browser names the page's site in the request header Origin. When that site is allowed, the API answers with the header Access-Control-Allow-Origin, and the browser lets the page read the answer. Every answer needs the rule, errors too, so it goes in a filter: a class marked @Component that extends OncePerRequestFilter. Spring calls its doFilterInternal for every request, before the route. It can set headers on the response, then hands the request on with chain.doFilter, and the route's answer keeps those headers. This one adds another header to every answer:

@Component
class VersionFilter extends OncePerRequestFilter {
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        response.setHeader("X-Shop-Version", "2");
        chain.doFilter(request, response);
    }
}

Before a request that sends a token or a JSON body, or uses PUT, the browser first asks with an OPTIONS request, a preflight. On its own, Spring refuses a preflight with 403. Your filter answers it instead: it adds the headers that say which headers and methods the shop's site may send, then returns without calling chain.doFilter, so no route runs and the answer is an empty 200. CORS is a rule the browser follows, not a lock: the shop still answers other sites, only without permission.

GET /products/1       Origin: https://shop.example.com  →  200, Access-Control-Allow-Origin: https://shop.example.com
GET /products/1       Origin: https://evil.example.com  →  200, no Access-Control-Allow-Origin
GET /products/99      Origin: https://shop.example.com  →  404, Access-Control-Allow-Origin: https://shop.example.com
OPTIONS /me           Origin: https://shop.example.com  →  200, Access-Control-Allow-Headers: Authorization, Content-Type
OPTIONS /me/password  Origin: https://shop.example.com  →  200, Access-Control-Allow-Methods: GET, POST, PUT
OPTIONS /me           Origin: https://evil.example.com  →  200, no CORS headers

Your task: keep the shop's website in a constant SHOP_SITE in ShopController, and write a filter CorsFilter that adds Access-Control-Allow-Origin, Access-Control-Allow-Headers and Access-Control-Allow-Methods only when the request's Origin is SHOP_SITE, and that ends OPTIONS requests right there.


Assignment
Follow the Coding Tutorial to add the SHOP_SITE constant and the CorsFilter class.


Hint
Read the page's site with request.getHeader("Origin"). It is null when the request has none, so call equals on SHOP_SITE, never on the header. Set answer headers with response.setHeader("...", "..."). Every request that isn't OPTIONS must still go on with chain.doFilter(request, response).


STEP 1 OF 11
    Ctrl + Enter to check