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?
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
.
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:
@RequestMapping(value="/user-login", method=RequestMethod.GET)
public ModelAndView loginForm() {
return new ModelAndView("login-form");
}
And change your view.
<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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With