Spring Security - 405 Request Method 'POST' Not Supported
Asked Answered
D

7

9

I have implemented Spring Security to my project, but I am getting status 405 when I try to log in. I have already added csrf token in the form.

This is the error I am getting when I send username and password: HTTP Status 405 - Request method 'POST' not supported

Spring version: 4.0.2.RELEASED

<div class="login-form">
    <c:url var="loginUrl" value="/login" />
    <form action="${loginUrl}" method="post" class="form-horizontal">
        <c:if test="${param.error != null}">
            <div class="alert alert-danger">
                <p>Invalid username and password.</p>
            </div>
        </c:if>
        <c:if test="${param.logout != null}">
            <div class="alert alert-success">
                <p>You have been logged out successfully.</p>
            </div>
        </c:if>
        <div class="input-group input-sm">
            <label class="input-group-addon" for="username">
                <i class="fa fa-user"></i>
            </label>
            <input type="text" class="form-control" id="username"
                name="clientusername" placeholder="Enter Username" required>
        </div>
        <div class="input-group input-sm">
            <label class="input-group-addon" for="password">
                <i class="fa fa-lock"></i>
            </label>
            <input type="password" class="form-control" id="password"
                name="clientpassword" placeholder="Enter Password" required>
        </div>

        <input type="hidden" name="${_csrf.parameterName}"
            value="${_csrf.token}" />

        <div class="form-actions">
            <input type="submit" class="btn btn-block btn-primary btn-default"
                value="Log in">
        </div>
    </form>
</div>

Security Configuration:

@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    @Qualifier("G2BUserDetailsService")
    UserDetailsService userDetailsService;

    @Autowired
    public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
      http.authorizeRequests()
        .antMatchers("/", "/home").permitAll()
        .antMatchers("/admin/**").access("hasRole('ADMIN')")
        .and().formLogin().loginPage("/login")
        .usernameParameter("clientusername").passwordParameter("clientpassword")
        .and().csrf()
        .and().exceptionHandling().accessDeniedPage("/Access_Denied");
//        .and().csrf().disable();
    }

Controller:

@RequestMapping(value = "/login", method = RequestMethod.GET)
public ModelAndView loginPage() {
    return new ModelAndView("login");
}

@RequestMapping(value="/logout", method = RequestMethod.GET)
public String logoutPage (HttpServletRequest request, HttpServletResponse response) {
    Authentication auth = SecurityContextHolder.getContext().getAuthentication();
    if (auth != null){    
        new SecurityContextLogoutHandler().logout(request, response, auth);
    }
    return "redirect:/login?logout";
}

 @RequestMapping(value = "/Access_Denied", method = RequestMethod.GET)
    public ModelAndView accessDeniedPage(ModelMap model) {
        model.addAttribute("user", getPrincipal());
        return new ModelAndView("accessDenied");
    }

 @RequestMapping(value = "/admin", method = RequestMethod.GET)
    public ModelAndView adminPage(ModelMap model) {
        model.addAttribute("user", getPrincipal());
        return new ModelAndView("admin");
    }

 private String getPrincipal(){
        String userName = null;
        Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();

        if (principal instanceof UserDetails) {
            userName = ((UserDetails)principal).getUsername();
        } else {
            userName = principal.toString();
        }
        return userName;
    }

Almost every topic about this issue says that we need to add csrf token, but I already added. Am I missing something?

Dilution answered 13/2, 2017 at 14:55 Comment(2)
Does it work if you disable csrf? Have you tested that? Because from what it seems, there is no endpoint being detected for /login that takes a POST request. The one you have currently as GET is okay for showing the login page, but where is the processing url?Jeri
@px It doesn't. Should I create one more request mapping for the processing url?Dilution
S
3

You can set two endpoints for one url. But you cannot set any request parameter as required. As I saw your request map for login, you can set your request method like this:

@RequestMapping(value = "/login", method = { RequestMethod.GET, RequestMethod.POST })
public ModelAndView loginPage() {
    return new ModelAndView("login");
}
Sagacious answered 13/2, 2017 at 15:10 Comment(4)
But the Post /login is supposed to check the authentication not returning the view !Radiance
Where happens the authentication in this case?Ding
If I add RequestMethod.POST as a second end point then login functionality is not working.Pilsudski
@Pilsudski Can you please show errors if you are getting and what you have done so far?Sagacious
J
12

First of all csrf is enabled by default in Spring as of Spring 4.0 so there no need to explicitly enable it yourself.

Secondly, there is no endpoint for you to authenticate your login. What you're doing is sending a request to /login which only takes a GET request. You could create another controller method to receive that POST request and authenticate or you could use a UserDetailsService.

SecurityConfiguration

protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                    .antMatchers("/login-form")
                        .anonymous()
                    .and()
                .formLogin()
                    .loginPage("/user-login") 
                    .defaultSuccessUrl("/admin", true) // the second parameter is for enforcing this url always
                    .loginProcessingUrl("/login")
                    .failureUrl("/user-login")
                    .permitAll();
}

@Autowired 
private UserDetailsService userDetailsService;  

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    BCryptPasswordEncoder pe = new  BCryptPasswordEncoder();
    auth.userDetailsService(userDetailsService).passwordEncoder(pe);
}

Here our view page is /user-login and the processing url is /login this means in your controller you need remove the mapping for /login and add the following:

Controller

@RequestMapping(value="/user-login", method=RequestMethod.GET)
public ModelAndView loginForm() {
    return new ModelAndView("login-form");
}

And change your view.

View (login-form.jsp)

<c:url value="/login" var="loginUrl"/>
<form action="${loginUrl}" method="post" modelAttribute="user">
    Username: <input type="text" id="username" name="username" placeholder=""><br>
    Password: <input type="password" id="password" name="password" placeholder=""><br>

    <input type="hidden"
    name="${_csrf.parameterName}"
    value="${_csrf.token}"/>
    <button type="submit">Login</button>
</form>
Jeri answered 13/2, 2017 at 15:15 Comment(1)
Just as a side note. Using .successForwardUrl(...) doesn't have the same effect as using .defaultSuccessUrl(...). The first one will lead to the error the question was describing, while the second one is the right one.Roseberry
V
10

I started getting the same thing when I added a successForwardUrl and found that the response on sucessful login is a POST to that endpoint or to "/" if not set. Once I enabled POST on the defined endpoint as well as GET all was fine.

Volteface answered 9/4, 2018 at 17:43 Comment(0)
S
3

You can set two endpoints for one url. But you cannot set any request parameter as required. As I saw your request map for login, you can set your request method like this:

@RequestMapping(value = "/login", method = { RequestMethod.GET, RequestMethod.POST })
public ModelAndView loginPage() {
    return new ModelAndView("login");
}
Sagacious answered 13/2, 2017 at 15:10 Comment(4)
But the Post /login is supposed to check the authentication not returning the view !Radiance
Where happens the authentication in this case?Ding
If I add RequestMethod.POST as a second end point then login functionality is not working.Pilsudski
@Pilsudski Can you please show errors if you are getting and what you have done so far?Sagacious
C
2

Check your web.xml file you might forgot to keep "securityFilterChain"

Use this code in web.xml file

<!-- Security configuration goes here -->
<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
Caridadcarie answered 17/4, 2019 at 11:24 Comment(0)
P
0

You are calling for a POST yet have only defined GET methods. Change your endpoint to RequestMethod.POST

Penholder answered 13/2, 2017 at 14:57 Comment(2)
Then how can I enter login page if I change it to POST?Dilution
If you are using the same URI to render the view, create a second endpoint to handle the POST. You can define the same URI for each of the HTTP verbsPenholder
N
0

If you are using JSP/JSTL

Change

<form action="${loginUrl}" method="post"></form> 

to

<form:form action="${loginUrl}" method="post" </form:form>

with tag declaration

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>

solve my problem

Nevus answered 12/6, 2019 at 9:48 Comment(0)
W
0

Ensure that the Spring Security filter chain is registered.

With Java configuration, this can be done by creating a class that extends AbstractSecurityWebApplicationInitializer.

public class SecurityWebInitializer extends AbstractSecurityWebApplicationInitializer {
}

Alternatively, edit web.xml and add the following code. (See the documentation.)

<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>springSecurityFilterChain</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
Wishful answered 28/1, 2022 at 15:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.