Project Trouble Shooting : 405 Method Not Allowed By Ambiguous URL mapping -> "/login", "/logout"

#Foreword

In the previous team project, I didn't have the reference point for specific Url while designing API. Team leader designed API, and I followed it. What's more, at that time, just following them wasn't easy for me, I couldn’t afford to keep them up.

Anyway, through this trouble shooting, I've realized that there must be a reason if a specific formula is popular.

#Problem : 405 Error with the this code

AuthController Code For "/login", "/logout"


@RestController
@RequestMapping
@RequiredArgsConstructor
public class AuthController {

    public static final String ACCESS_TOKEN_COOKIE_NAME = "accessToken";

    private final CookieUtils cookieUtils;

    private final AuthService authService;

    @PostMapping("/login")
    public ResponseEntity<ResponseDTO<Void>> login(
        @RequestBody @Valid LoginRequest request,
        HttpServletResponse httpServletResponse
    ) {
        Member member = authService.findByEmail(request.email());
        TokenDTO tokenDTO = authService.login(member, request);

        Cookie accessToken = cookieUtils.makeCookie(
            ACCESS_TOKEN_COOKIE_NAME, tokenDTO.accessToken()
        );
        httpServletResponse.addCookie(accessToken);

        return ResponseEntity.ok(ResponseDTO.ok());
    }

    @PostMapping("/logout")
    public ResponseEntity<ResponseDTO<Void>> logout(HttpServletResponse httpServletResponse) {
        Cookie expiredCookie = cookieUtils.expireCookie(ACCESS_TOKEN_COOKIE_NAME);
        httpServletResponse.addCookie(expiredCookie);

        return ResponseEntity.ok(ResponseDTO.ok());
       }
}

#405 Not Support - Reason

I thought it's from this Incorrect URL mapping :

Current URL

Login Url -> POST localhost:8080/login

Logout Url -> POST localhost:8080/logout

@RestController
@RequestMapping
@RequiredArgsConstructor
public class AuthController {

    @PostMapping("/login")
    public ResponseEntity<ResponseDTO<Void>> login(~~~~
    )

    @PostMapping("/logout")
    public ResponseEntity<ResponseDTO<Void>> logout(~~~~
    )

: What we're getting in the previous senario is an HTTP response with the 405 Status Code, which is a client error indicating that the server doesn't support the method/verb sent in request.

As the name here suggests, the reason for this error is sending the request with a non-supported method. (I thought, in my case, it's caued by Ambiguous URL Mapping with similar name.)

#Solution : Defining Consise URL

To make Url clear, add "/auth" Url on @RequestMapping

As we can expect, we can solve this by defining concise URL mapping for POST "/login", "/logout" explicitly, adding @RequestMapping("/auth").

Modified URL

Login Url -> POST localhost:8080/auth/login

Logout Url -> POST localhost:8080/auth/logout

@RestController
@RequestMapping("/auth)
@RequiredArgsConstructor
public class AuthController {

    @PostMapping("/login")
    public ResponseEntity<ResponseDTO<Void>> login(~~~~
    )

    @PostMapping("/logout")
    public ResponseEntity<ResponseDTO<Void>> logout(~~~~
    )

I guess the previous URLs were similar to each other, so it didn't work well.

Below is the modified whole source code.


import com.FC.SharedOfficePlatform.domain.auth.controller.request.LoginRequest;
import com.FC.SharedOfficePlatform.domain.auth.dto.TokenDTO;
import com.FC.SharedOfficePlatform.domain.auth.service.AuthService;
import com.FC.SharedOfficePlatform.domain.member.entity.Member;
import com.FC.SharedOfficePlatform.global.common.CookieUtils;
import com.FC.SharedOfficePlatform.global.util.ResponseDTO;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/auth")
@RequiredArgsConstructor
public class AuthController {

    public static final String ACCESS_TOKEN_COOKIE_NAME = "accessToken";

    private final CookieUtils cookieUtils;

    private final AuthService authService;

    @PostMapping("/login")
    public ResponseEntity<ResponseDTO<Void>> login(
        @RequestBody @Valid LoginRequest request,
        HttpServletResponse httpServletResponse
    ) {
        Member member = authService.findByEmail(request.email());
        TokenDTO tokenDTO = authService.login(member, request);

        Cookie accessToken = cookieUtils.makeCookie(
            ACCESS_TOKEN_COOKIE_NAME, tokenDTO.accessToken()
        );
        httpServletResponse.addCookie(accessToken);

        return ResponseEntity.ok(ResponseDTO.ok());
    }

    @PostMapping("/logout")
    public ResponseEntity<ResponseDTO<Void>> logout(HttpServletResponse httpServletResponse) {
        Cookie expiredCookie = cookieUtils.expireCookie(ACCESS_TOKEN_COOKIE_NAME);
        httpServletResponse.addCookie(expiredCookie);

        return ResponseEntity.ok(ResponseDTO.ok());
       }
}

+Reference

(Ambiguous Mapping)

@RequestMapping(
  value = "/employees", 
  produces = "application/json", 
  method = {RequestMethod.GET, RequestMethod.PUT}) ...Copy

Alternatively, we can define the new method/mapping separately:

@RequestMapping(value = "/employees", 
  produces = "application/json", 
  method = RequestMethod.GET)
public List<Employee> getEmployees() {
    // Code to return the list of employees
}

@RequestMapping(value = "/employees", 
  produces = "application/json", 
  method = RequestMethod.PUT)
public Employee updateEmployee(@RequestBody Employee employee) {
    // Code to update an employee
}