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
Related
I want to implement Springboot Security for my first project and I don't know how to do the next thing:
I have the frontend and backend in different folders
on the frontend I use live-server with the url http://127.0.0.1:9000/ and the backend with the url http://localhost:8080
my login page is http://127.0.0.1:9000/login.html
Can anyone help me binding the frontend login page with the backend ?
My HTML:
<body>
<div class="login-container">
<div class="login-box">
<h1 class="login-header">LOGIN</h1>
<div class="login-input-container">
<form action="http://localhost:8080/login" method="POST">
<div class="login-input-container-email-wrapper">
<input type="text" class="login-input-email" placeholder="Email" autocorrect="off" autocomplete="off" required/>
<svg class="login-input-email-icon">
<use xlink:href="img/sprite.svg#icon-mail_outline"></use>
</svg>
</div>
<div class="login-input-container-password-wrapper">
<input type="password" class="login-input-password" placeholder="Password" required/>
<svg class="login-input-password-icon">
<use xlink:href="img/sprite.svg#icon-vpn_key"></use>
</svg>
</div>
<div class="login-submit">
<button class="login-submit-button login-submit-button--green">Submit</button>
</div>
</form>
<span class="login-separator">Or Login With</span>
<div class="login-google">
<div class="g-signin2" data-onsuccess="onSignIn" id="my-signin2"
style="display:flex;justify-content:stretch;align-items: stretch;width: 500;" data-longtitle="true"></div>
</div>
</div>
</div>
</div>
</body>
MY Springboot security config:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("http://127.0.0.1:9000/login.html").permitAll()
.defaultSuccessUrl("http://127.0.0.1:9000")
.permitAll();
}
You need to write a login controller with a rest end point where you verify credentials and you need to map that in configure() method. Not the front-end url. From front-end you should call that rest end point. Modify the configure() as below.
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").permitAll()
.defaultSuccessUrl("http://127.0.0.1:9000")
.permitAll();
}
And the login controller should be like
#RestController
public class LoginController {
#CrossOrigin
#RequestMapping(value = "/login"", method = RequestMethod.POST)
#ResponseBody
public User getUserByDetails(#RequestBody Map<String, String> credentials, HttpServletResponse response)
throws IOException {
// business logic
}
}
You can refer https://www.baeldung.com/spring-security-login-angular for further details.
Do refer below config and modify your config file as required!
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login") // Specifies the login page URL (goes to controller to look for this URL)
//.loginProcessingUrl("/login") // Specifies the URL,which is used in action on the <from> tag
//.successHandler(successHandler)
.defaultSuccessUrl("http://127.0.0.1:9000")
.and()
.logout()
.logoutUrl("/logout");
// Following is to go to direct controller URL for access denied.
// exceptionHandling().accessDeniedPage("/accessDenied");
}
Here, you must understand the difference between loginPage() and loginProcessingUrl().
Now your controller should have mapping for /login
So, your controller will have method to call your custom-login-page with below config
#GetMapping("/login")
public String customLoginPage() {
return "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());
}
}
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
I'm making a webshop in Spring Boot 2.1.1, with ThymeLeaf 3.0.11
My login page does not appear in every cases, everytime it wants to load it gives a "TemplateInputException" and fails to show up.
I figured it out, if I delete thymeleaf variables from the body or div tags (I mean th:with attributes), then it works until it reaches the next html tag with TL variables, after that, the html page just stops to render. What could be the problem? There is no scenario where i dont use those variables, I need them in the container tag. What is the relation between the Spring Boot login page and template variables?
I copy some code, if you need any more, please let me know.
Any help would be appreciated!
Here is my Webconfig:
#Configuration
public class WebConfig implements WebMvcConfigurer{
#Override
public void addViewControllers(ViewControllerRegistry registry){
registry.addViewController("/login").setViewName("auth/login");
registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
}
My security config's configure method:
#Override
public void configure (HttpSecurity httpSec)throws Exception {
httpSec
.authorizeRequests()
.antMatchers("/", "/reg", "/login", "/css/**","/images/**",
"/js/**", "/register", "/error",
"/records", "/search", "/record", "/altercart",
"/showcart", "/category", "/viewchange",
"/images").permitAll()
.antMatchers("/admin").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.logoutSuccessUrl("/login?logout")
.permitAll();
}
My login page(using layout dialect):
<div layout:fragment="content">
<div>
<div th:if="${param.error}" th:text="#{login.error.wrongusernameorpassword}" class="col-12 error-message"/>
<div th:if="${param.logout}" th:text="#{logoutSuccess}" class="col-12 success-message"/>
<p th:text="#{logingreetings}" class="col-12"/>
<form method="post" th:action="#{login}">
<input type="text" name="username" th:placeholder="#{login.ph.username}" required class="col-12"/>
<br />
<input type="password" name="password" th:placeholder="#{login.ph.password}" required class="col-12"/>
<br />
<input type="submit" th:value="#{loginSubmitButton}" class="col-12"/>
<br /><br />
</form>
<br />
<a class="col-12 anchor" th:href="#{register}" th:text="#{misc.registration}">Registration</a>
</div>
</div>
Beggining of stack trace:
org.thymeleaf.exceptions.TemplateInputException: An error happened during
template parsing (template: "class path resource
[templates/auth/login.html]"
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">