Spring Security / MVC / JPA --> Request method 'POST' not supported - spring

I am having errors login from the HTML using Spring Security, Spring MVC and JPA.
This is my login.HTML
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head lang="en" xmlns:th="http://www.thymeleaf.org">>
<title>Spring Framework Guru</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<body class="security-app">
<div class="details">
<h2>Spring Security - App</h2>
</div>
<form action="/login" method="post">
<div class="lc-block">
<div>
<input type="text" class="style-4" name="username"
placeholder="User Name" />
</div>
<div>
<input type="password" class="style-4" name="password"
placeholder="Password" />
</div>
<div>
<input type="submit" value="Sign In" class="button red small" />
</div>
<th:if test="${param.error ne null}">
<div class="alert-danger">Invalid username and password.</div>
</th:if>
<th:if test="${param.logout ne null}">
<div class="alert-normal">You have been logged out.</div>
</th:if>
</div>
<input type="hidden" name="${_csrf.parameterName}"
value="${_csrf.token}" />
</form>
</body>
</html>
This is WebSecurity class:
#Configuration
#EnableWebSecurity
#ComponentScan(basePackageClasses = CustomUserDetailsService.class)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordencoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/hello").access("hasRole('ROLE_ADMIN')")
.anyRequest().permitAll()
.and()
.formLogin().loginPage("/login")
.usernameParameter("username").passwordParameter("password")
.and()
.logout().logoutSuccessUrl("/login?logout")
.and()
.exceptionHandling().accessDeniedPage("/403")
.and()
.csrf();
}
#Bean(name = "passwordEncoder")
public PasswordEncoder passwordencoder() {
return new BCryptPasswordEncoder();
}
}
UserDetails service class:
#Service("customUserDetailsService")
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
private final UserRolesRepository userRolesRepository;
#Autowired
public CustomUserDetailsService(UserRepository userRepository, UserRolesRepository userRolesRepository) {
this.userRepository = userRepository;
this.userRolesRepository = userRolesRepository;
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUserName(username);
if (null == user) {
throw new UsernameNotFoundException("No user present with username: " + username);
} else {
List<String> userRoles = userRolesRepository.findRoleByUserName(username);
return new CustomUserDetails(user, userRoles);
}
}
}
I always have 405 error:
2016-10-16 12:15:30.710 WARN 2932 --- [nio-8080-exec-3] o.s.web.servlet.PageNotFound : Request method 'POST' not supported
Any ideas why is not calling the "configure(HttpSecurity http)". Am I missing something?
Thank you very much
Andres

Try adding
.formLogin().loginPage("/xxx").permitAll()
.defaultSuccessUrl("/xxx")
.failureUrl("/xxx?error")
In addition
One typical reason in Spring MVC Applications for controller methods and pages not found is Spring's weird mapping convention to build URLs by adding a new link (or the form action="x") to the end of current URL. 'Request Method POST Not Supported' only means that your request is pointed to somewhere where nothing is accepting the POST request => URL mapping fails. On JSP sites you should always specify URLs like
<form action="${pageContext.request.contextPath}/login" method="post">
or with JSTL
<c:url value="/login" var="loginUrl" />
<form action="${loginUrl}" method="post">
Then you can be sure that your link is set right after the application root in URL. This will save you from a lot of unnecessary problems in the future.

Related

Spring Security getting Acess Denied with Custom Login page

I have created one Inmemory user. Initially I tried with default formLogin. After successful login calling another api using defaultSuccessUrl till here everything fine. Now I have implemented one custom login html page when I try to login with both correct and wrong credentials getting access denied and redirecting login page again.
Anyone please help me.
Security config:
#Configuration
#EnableWebSecurity
public class SecurityConfige extends WebSecurityConfigurerAdapter {
#Autowired
private PasswordEncoder passwordEncoder;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/","index","/courses","login").permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/courses", true);
}
#Override
#Bean
public UserDetailsService userDetailsServiceBean() throws Exception {
UserDetails admin= User.builder()
.username("nithin")
.password(passwordEncoder.encode("nithin"))
.roles("ADMIN")
.authorities(new SimpleGrantedAuthority("ROLE_ADMIN"))
.build();
return new InMemoryUserDetailsManager(admin);
}
}
Html Login page:
<html lang="en">
</head>
<body>
<div class="container">
<form class="form-signin" method="post" action="/login">
<h2 class="form-signin-heading">Please login to Nithincode</h2>
<p>
<label for="username" class="sr-only">Username</label>
<input type="text" id="username" name="username" class="form-control" placeholder="Username" required=""
autofocus="">
</p>
<p>
<label for="password" class="sr-only">Password</label>
<input type="password" id="password" name="password" class="form-control" placeholder="Password"
required="">
</p>
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
<button class="btn btn-lg btn-primary btn-block" type="submit">Login</button>
</form>
</div>
</body>
</html>
Login Api:
#GetMapping("login")
public String getLoginView() {
return "login";
}
I think that you are not configuring the user details service correctly and your user is not found (for which Spring shows bad credentials). Maybe try to change your configuration a bit:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsService myService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/","index","/courses","login").permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/courses", true);
}
#Bean
public UserDetailsService myService() throws Exception {
UserDetails admin= User.builder()
.username("nithin")
.password(new BCryptPasswordEncoder().encode("nithin"))
.roles("ADMIN")
.authorities(new SimpleGrantedAuthority("ROLE_ADMIN"))
.build();
return new InMemoryUserDetailsManager(admin);
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)
throws Exception {
auth.userDetailsService(myService).passwordEncoder(
new BCryptPasswordEncoder());
}
}

Spring oauth2 AuthorizationServer doesn't redirect

I set up an Authorization Service with Spring Security and Oauth2.
Everything worked fine until i tried to customize the login page.
If I login at my custom login page it redirects back to the login page and not to the callback url.
GET /login -> POST /login -> GET /login
SecurityConfig.java
#Configuration
#Order(1)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
DataSource dataSource;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login").permitAll()
.antMatchers("oauth/authorize").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll();
}
#Override
protected void configure(AuthenticationManagerBuilder
auth) throws Exception {
auth.parentAuthenticationManager(authenticationManagerBean())
.jdbcAuthentication()
.dataSource(dataSource)
.usersByUsernameQuery("select mail,password,enabled "
+ "from users "
+ "where mail = ?")
.authoritiesByUsernameQuery("select mail,authority "
+ "from users "
+ "where mail = ?");
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean()
throws Exception {
return super.authenticationManagerBean();
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
AuthorizationServerConfig.java
#Autowired
private PasswordEncoder passwordEncoder;
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private DataSource dataSource;
#Override
public void configure(final AuthorizationServerSecurityConfigurer
oauthServer) {
oauthServer.tokenKeyAccess("permitAll()")
.checkTokenAccess("permitAll()");
}
#Override
public void configure(final ClientDetailsServiceConfigurer
clients) throws Exception {
clients
.jdbc(dataSource);
}
#Override
public void configure(final AuthorizationServerEndpointsConfigurer
endpoints) throws Exception {
endpoints
.tokenStore(tokenStore())
.accessTokenConverter(accessTokenConverter())
.authenticationManager(authenticationManager);
}
#Bean
public TokenStore tokenStore() {
//return new JdbcTokenStore(dataSource);
return new JwtTokenStore(accessTokenConverter());
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("dein-signing-key");
return converter;
}
login.html
<form action="/login" method="POST">
<div class="column">
<div class="title">Anmelden</div>
<div th:if="${param.error}" class="alert alert-error">
Invalid username and password.
</div>
<div th:if="${param.logout}" class="alert alert-success">
You have been logged out.
</div>
<input id="username" name="username" type="email" class="login input" placeholder="E-Mail Adresse"/>
<input id="password" name="password" type="password" class="login input" placeholder="Passwort"/>
<br>
<p style="text-align: center; margin-top: 20px;">Passwort vergessen?</p>
<button style=" margin-top: 20px; margin-bottom: 20px" type="submit" class="button cancel login">Anmelden</button>
</div>
</form>
You have to send the CSRF token with your custom login page, see Spring Security Reference:
9.21.1 Form Login Java Configuration
[...]
An example log in page implemented with JSPs for our current configuration can be seen below:
[...]
<c:url value="/login" var="loginUrl"/>
<form action="${loginUrl}" method="post"> 1
<c:if test="${param.error != null}"> 2
<p>
Invalid username and password.
</p>
</c:if>
<c:if test="${param.logout != null}"> 3
<p>
You have been logged out.
</p>
</c:if>
<p>
<label for="username">Username</label>
<input type="text" id="username" name="username"/> 4
</p>
<p>
<label for="password">Password</label>
<input type="password" id="password" name="password"/> 5
</p>
<input type="hidden" 6
name="${_csrf.parameterName}"
value="${_csrf.token}"/>
<button type="submit" class="btn">Log in</button>
</form>
1 - A POST to the /login URL will attempt to authenticate the user
2 - If the query parameter error exists, authentication was attempted and failed
3 - If the query parameter logout exists, the user was successfully logged out
4 - The username must be present as the HTTP parameter named username
5 - The password must be present as the HTTP parameter named password
6 - We must the section called “Include the CSRF Token” To learn more read the Section 5.1.1, “Cross Site Request Forgery (CSRF)” section of the reference

inMemoryAuthentication with Spring Boot

I've generated a Spring Boot web application using Spring Initializer, embedded Tomcat, Thymeleaf template engine, and package as an executable JAR file.
Technologies used:
Spring Boot 1.4.2.RELEASE, Spring 4.3.4.RELEASE, Thymeleaf 2.1.5.RELEASE, Tomcat Embed 8.5.6, Maven 3, Java 8
This is my security config class:
#Configuration
#EnableWebSecurity
#PropertySource("classpath:/com/tdk/iot/config/app-${APP-KEY}.properties")
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Value("${securityConfig.formLogin.loginPage}")
private String loginPage;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.loginPage(loginPage)
.permitAll()
.loginProcessingUrl("/login")
.failureUrl("/login.html?error=true")
.defaultSuccessUrl("/books/list")
.and()
.exceptionHandling()
.accessDeniedPage("/denied")
.and()
.authorizeRequests()
.antMatchers("/mockup/**").permitAll()
.antMatchers("/books/**").permitAll()
.antMatchers("/welcome/**").authenticated()
.and()
.logout()
.permitAll()
.logoutSuccessUrl("/index.html");
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.passwordEncoder(new StandardPasswordEncoder())
.withUser("test1").password("test1").roles("ADMIN").and()
.withUser("test2").password("test2").roles("USER").and()
.withUser("test3").password("test3").roles("SUPERADMIN");
}
#Bean
public static PropertySourcesPlaceholderConfigurer propertyDefaultConfig() {
return new PropertySourcesPlaceholderConfigurer();
}
}
Here the LoginController
#Controller
public class LoginController {
#RequestMapping(value={ "/", "/tdk/login"}, method = { RequestMethod.POST,RequestMethod.GET})
public String welcome(Map<String, Object> model) {
return "tdk/login";
}
}
and the template:
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<div class="wrap">
<div class="login">
<div class="logo"></div>
<form th:action="#{/login.html}" method="post">
<p th:if="${loginError}" class="error">Wrong user or password</p>
<div class="input_label"><i class="fa fa-user"></i><input type="text" name="user" placeholder="User" /></div>
<div class="input_label"><i class="fa fa-key"></i><input type="password" name="pass" placeholder="Password" /></div>
<input type="submit" value="LOGIN" />
</form>
<div class="forget">
<!-- Do you forgot your password?<br/> -->
<br/>
</div>
</div>
</div>
</body>
</html>
but when I access with test1 / test1 I got this error:
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Sun Mar 05 20:16:11 CET 2017
There was an unexpected error (type=Method Not Allowed, status=405).
Request method 'POST' not supported
Your login page calls /login.html with HTTP POST, but your server doesn't provide such a request mapping.
The configured URL in your Spring Security configuration:
.loginProcessingUrl("/login")
is not matching the URL in your login page:
<form th:action="#{/login.html}" method="post">
See also AbstractAuthenticationFilterConfigurer#loginProcessingUrl:
Specifies the URL to validate the credentials.
try this code
.failureUrl("/tdk/login?error=true")
Controller
#Controller
public class LoginController {
#RequestMapping(value={ "/", "/tdk/login"},params = {"error"},method=RequestMethod.POST)
public String welcome(#RequestParam(value = "error", required = false) int error , ModelMap model) {
if (error == 1) {
model.addAttribute("msg", "Invalid Username or Password");
return "tdk/login";
}
else{
return "redirect:home";
}
}
}
Default method controller for #RequestMapping is GET, not POST.
You need to specify the method on the #requestMapping.
#RequestMapping(value={ "/", "/tdk/login"}, method = RequestMethod.POST)

how could I use csrf in spring security

My login page.
<form class="form-horizontal" ng-controller="loginCtrl" action="/login" method="post">
<div class="form-group input-login">
<div ng-if="message.error" class="alert alert-danger">
<p>Invalid username and password.</p>
</div>
<div ng-if="message.logout" class="alert alert-success">
<p>You have been logged out successfully.</p>
</div>
<label class="control-label sr-only">Email</label>
<div class="col-md-12">
<input type="text" class="form-control" ng-model="user.username" name="username" placeholder="NickName"/>
</div>
</div>
<div class="form-group input-login">
<label class="control-label sr-only">Password</label>
<div class="col-md-12">
<input type="password" class="form-control" ng-model="user.password" name="password" placeholder="Password"/>
</div>
</div>
<input name="_csrf" type="hidden" value="6829b1ae-0a14-4920-aac4-5abbd7eeb9ee" />
<div class="form-group sub-login">
<div class=" col-md-12">
<button name="submit" type="submit" class="btn btn-primary btn-login">Login</button>
</div>
</div>
</form>
But if I didn't disable the csrf,it alway be accessDenied.I don't know where is the problem.
My config code below.
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDao userDao;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(new UserService(userDao)).passwordEncoder(new MD5Util());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/", "/index").access("hasRole('USER')")
.and()
.formLogin()
.loginPage("/login")
.failureUrl("/login#/signin?error=1")
.successHandler(new LoginSuccessHandler())
.usernameParameter("username").passwordParameter("password")
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login#/signin?logout=1")
.and()
.exceptionHandling().accessDeniedPage("/Access_Denied")
.and().csrf().disable(); // If I disable this csrf,it worked!
}
}
And does anyone knows how to ues thymeleaf in ng-route's partial page.Just see this question.
Your best bet would be to have a look at this link: https://spring.io/blog/2015/01/12/the-login-page-angular-js-and-spring-security-part-ii
Particularly, the relevant section is:
CSRF Protection
That’s good because it means that Spring Security’s built-in CSRF protection has kicked in to prevent us from shooting ourselves in the foot. All it wants is a token sent to it in a header called “X-CSRF”. The value of the CSRF token was available server side in the HttpRequest attributes from the initial request that loaded the home page. To get it to the client we could render it using a dynamic HTML page on the server, or expose it via a custom endpoint, or else we could send it as a cookie. The last choice is the best because Angular has built in support for CSRF (which it calls “XSRF”) based on cookies.
So all we need on the server is a custom filter that will send the cookie. Angular wants the cookie name to be “XSRF-TOKEN” and Spring Security provides it as a request attribute, so we just need to transfer the value from a request attribute to a cookie:
public class CsrfHeaderFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class
.getName());
if (csrf != null) {
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
String token = csrf.getToken();
if (cookie==null || token!=null && !token.equals(cookie.getValue())) {
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
}
filterChain.doFilter(request, response);
}
}
After a bit more work, the last sentence is:
With those changes in place we don’t need to do anything on the client side and the login form is now working.
You should include an input hidden to send the CSRF token in the POST method when the user submit the form.
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
You already included a input hidden _csrf in your template, but the value is wrong, just change it.
You can read more about CSRF here:
https://docs.spring.io/spring-security/site/docs/current/reference/html/csrf.html

Spring Security 4 : Request method 'POST' not supported

The issue is that when I click on submit I get
Etat HTTP 405 - Request method 'POST' not supported
Here is the controller :
#RequestMapping(value = "/admin/login", method = RequestMethod.GET)
public ModelAndView login(
#RequestParam(value = "error", required = false) String error,
#RequestParam(value = "logout", required = false) String logout) {
LOGGER.debug("admin login page");
ModelAndView model = new ModelAndView();
if (error != null) {
model.addObject("error", "Invalid username and password!");
}
if (logout != null) {
model.addObject("msg", "You've been logged out successfully.");
}
model.setViewName("/admin/index");
LOGGER.debug("returning admin login page");
return model;
}
And the form :
<form class="m-t" role="form" th:action="#{/admin/login}" method="POST" autocomplete="off">
<div th:if="${param.error}" class="alert alert-danger">Invalid username and password.</div>
<div th:if="${param.logout}" class="alert alert-success">You have been logged out.</div>
<div class="form-group">
<input type="text" class="form-control" id="username" name="username" placeholder="Username" required="" />
</div>
<div class="form-group">
<input type="password" class="form-control" id="username" name="username" placeholder="Username" required="" />
</div>
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
<button type="submit" class="btn btn-primary block full-width m-b">Login</button>
<small>Forgot password?</small>
<p class="text-muted text-center">
<small>Do not have an account?</small>
</p>
<a class="btn btn-sm btn-white btn-block" href="register.html">Create an account</a>
</form>
It seems like csrf field not working.
I explain, I have normal users website which I refer here by and admin part which is /admin
The login form is correctly displayed. But I when I click submit I get Etat HTTP 405 - Request method 'POST' not supported
Any idea please ?
Here is my security configuration class :
package com.mintad.spring.security;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
/**
* #author Sofiane HAMMAMI
*/
#Configuration
#EnableWebSecurity
#ComponentScan
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
DataSource dataSource;
#Autowired
#Qualifier("customUserDetailsService")
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/**").permitAll()
// .antMatchers("/admin/**").access("hasRole('ADMIN')")
.and().formLogin().loginPage("/login")
.defaultSuccessUrl("/welcome").usernameParameter("username").passwordParameter("password")
.and().exceptionHandling().accessDeniedPage("/404");
}
}
In controller , you defined the form as GET:
#RequestMapping(value = "/admin/login", method = RequestMethod.GET)
and in the form, you are calling it as POST:
form class="m-t" role="form" th:action="#{/admin/login}" method="POST"
change one of these, either at controller or in the form.
If you want to change at controller, replace existing line with:
#RequestMapping(value = "/admin/login", method = RequestMethod.POST)
or you can do it by modifying call from form like
form class="m-t" role="form" th:action="#{/admin/login}" method="GET"
Change your security config to use /admin/login as your login page:
...
.formLogin().loginPage("/admin/login")
Reason you get 405 is, you are trying to submit your form with http post method and defined the end point with http get method.
You need to change your request mapping to method as POST like:
#RequestMapping(value = "/admin/login", method = RequestMethod.POST)
OR you could do vice versa (which is not recommend by security reasons) in your form like:
<form class="m-t" role="form" th:action="#{/admin/login}" method="GET" autocomplete="off">

Resources