I just want to register a user on my web site using springboot and thymeleaf, the problem is that when user clicks on "submit", after filled the registration form to save its credentials, well this operation isn't done and I land on login page.
I'm sure registration isn't completed because when I try to login it is unsuccessful and both "credentials" and "user" tables are empty.
This is the registration form, I'm saving "user" and "credentials" two different entities :
<form th:action="#{/process_register}" method="post">
<label for="username">Email:</label>
<input type="email" name="username" id="username" required th:field="${credentials.username}"/>
<label for="password">Password:</label>
<input type="password" name="password" id="password" required th:field="${credentials.password}"/>
<label for="name">Nome:</label>
<input type="name" name="name" id="name" th:field="${user.name}" required />
<button type="submit" class="btn btn-primary">Register</button>
<a th:href="#{/login}" href="login.html" > or login</a>
</form>
This is /process_register controller :
#PostMapping("/process_register")
public String processRegister(#ModelAttribute("credentials")Credentials credentials,#ModelAttribute("user") User user) {
credentials.setUser(user);
credentialService.saveCredentials(credentials);
System.out.println("Ho invocato saveCredentials");
return "login";
}
saveCredentials() method :
#Transactional
public Credentials saveCredentials(Credentials credentials) {
credentials.setPassword(this.passwordEncoder.encode(credentials.getPassword()));
return this.credentialsRepository.save(credentials);
}
where credentialsRepository extends CrudRepository.
EDIT :
AuthConfiguration :
#Configuration
#EnableWebSecurity
public class AuthConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
DataSource datasource;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
// authorization paragraph: qui definiamo chi può accedere a cosa
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/",
"/index",
"/login",
"/collection/*",
"/collections",
"/NFTS",
"/signup_form",
"/register",
"/css/**",
"zzz",
"/images/**").permitAll()
.antMatchers(HttpMethod.POST, "/login", "/register").permitAll()
.antMatchers(HttpMethod.GET, "/admin/**").hasAnyAuthority(ADMIN_ROLE)
.antMatchers(HttpMethod.POST, "/admin/**").hasAnyAuthority(ADMIN_ROLE)
.anyRequest().authenticated()
.and().formLogin()
.loginPage("/login")
.defaultSuccessUrl("/default")
.and().logout()
.logoutUrl("/logout")
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/index")
.invalidateHttpSession(true)
.clearAuthentication(true).permitAll();
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication()
//use the autowired datasource to access the saved credentials
.dataSource(this.datasource)
//retrieve username and role
.authoritiesByUsernameQuery("SELECT username, role FROM credentials WHERE username=?")
//retrieve username, password and a boolean flag specifying whether the user is enabled or not (always enabled in our case)
.usersByUsernameQuery("SELECT username, password, 1 as enabled FROM credentials WHERE username=?");
/*auth.inMemoryAuthentication()
.withUser("user1").password(passwordEncoder().encode("user1")).roles("USER")
.and()
.withUser("user2").password(passwordEncoder().encode("user2")).roles("USER")
.and()
.withUser("admin").password(passwordEncoder().encode("admin")).roles("ADMIN_ROLE");*/
}
#Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
After looking at github repo: you need to create your controllers with regard to auth filters configured in AuthConfiguration. Only a few paths are allowed to be accessed by an unauthorized user i.a. /register and /login.
Since /process_register is not whitelisted, then the POST request doesn't reach the controller. It is caugth by the filter and redirected to login page instead.
To fix the issue you can for instance do these:
Change #PostMapping("/process_register") to #PostMapping("/register") in your controller.
Change th:action="#{/process_register}" to th:action="#{/register}" in your template.
After it's done the registration form should work.
By the way:
You should not use th:field like this. It is supposed to be used together with form backing bean (th:object) - please refer to the documentation.
Related
I'm new to Thymeleaf, but worked with JSPs before, so that should be the same principle I would have thought.
Anyhow... I'm trying to put together the following Spring security tutorial https://spring.io/guides/gs/securing-web/.
It uses Thymeleaf for the frontend page rendering, and the following Login page does not play ball:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"
xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
<title>Spring Security Example </title>
</head>
<body>
<div th:if="${param.error}">
Invalid username and password.
</div>
<form th:action="#{/login.html}" method="post">
<div><label> User Name : <input type="text" name="username"/> </label></div>
<div><label> Password: <input type="password" name="password"/> </label></div>
<div><input type="submit" value="Sign In"/></div>
</form>
</body>
</body>
</html>
Then, I have the following Spring security configuration class:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home", "/greeting").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/homepage.html", true)
.failureUrl("/login.html?error=true")
.permitAll()
.and()
.logout()
.permitAll();
}
#Bean
#Override
public UserDetailsService userDetailsService() {
UserDetails user =
User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
}
The page with the form displays just fine, but when I provide the above credentials (user/password), it does not log the user in. It basically gets stuck in a loop demanding to login; there is a homepage.html, but it's never redirected to.
There is nothing in the logs, no errors or any messages.
Trying to debug it, I've added the following controller:
#Controller
#Slf4j
public class GreetingController {
#PostMapping("/login")
public String greeting(#RequestParam(name = "username", value = "xxx") String username, #RequestParam(name = "password", value = "yyy") String password, Model model) {
log.info("U " + username + " P " + password);
model.addAttribute("username", username);
model.addAttribute("password", password);
return "login";
}
}
but when trying to invoke http://localhost:8080/login, I get the following error:
There was an unexpected error (type=Method Not Allowed, status=405).
Request method 'GET' not supported
org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'GET' not supported
So, having tried all above, I'm kinda stuck now and not sure what to try next.
How do I debug such a login page to find out what's going on? Also, could anyone spot any problems with the code I've provided above so far?
I want to have more control over the logging in and out, via custom controller and login page.
My SecurityConfiguration code currently looks like this:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private SpringDataJpaUserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(this.userDetailsService)
.passwordEncoder(Manager.PASSWORD_ENCODER);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/resources/**", "/built/**", "/main.css", "/login.css").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/loginSecure")
.defaultSuccessUrl("/index", true)
.permitAll()
.usernameParameter("username").passwordParameter("password")
.and()
.csrf().disable()
.logout()
.permitAll();
}
}
My login config in my Controller:
#RequestMapping(value = "/login")
public String login() {
return "login";
}
My loginSecure mapping in my controller:
#RequestMapping(value="/loginSecure", method = RequestMethod.POST)
public String login(#RequestAttribute("username") String userName, #RequestAttribute("password") String password) {
//does the authentication
final Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
userName,
password
)
);
SecurityContextHolder.getContext().setAuthentication(authentication);
return "index";
}
My login.html:
<form class="login100-form validate-form" action="/loginSecure" method="post">
<span class="login100-form-title p-b-26">
Welcome
</span>
<span class="login100-form-title p-b-48">
<i class="zmdi zmdi-font"></i>
</span>
<div class="wrap-input100 validate-input" data-validate = "Valid email is: a#b.c">
<input class="input100" type="text" id="username" name="username"/>
<span class="focus-input100" data-placeholder="Email/Username"></span>
</div>
<div class="wrap-input100 validate-input" data-validate="Enter password">
<span class="btn-show-pass">
<i class="zmdi zmdi-eye"></i>
</span>
<input class="input100" type="password" id="password" name="password"/>
<span class="focus-input100" data-placeholder="Password"></span>
</div>
<div class="container-login100-form-btn">
<div class="wrap-login100-form-btn">
<div class="login100-form-bgbtn"></div>
<button class="login100-form-btn">
Login
</button>
</div>
</div>
</form>
When i submit the form, in chrome dev tools it submits as loginSecure? with url encoded but it just redirects back to the login.html again.
Edit: Removed the extra form from login.html and added csfr().disable to securityConfiguration. Added loginProcessUrl to httpSecurity and this fixed it. Above code works.
If you create a custom login html and a custom authenticator then you need to add this to the HttpSecurity config -> .loginProcessingUrl("/loginSecure")
Good example here -> https://www.boraji.com/spring-security-4-custom-login-from-example
From what you wrote I guess that the problem is that after clicking "Login" your application is hit by two request.
I think that problem is that your login page has two forms one inside another. So when you click "Login" both forms sends their requests. You can verify that in Chrome Developer Tools.
As you can read here HTML doesn't allow nested forms Is it valid to have a html form inside another html form?
I have a Spring Boot application which is able to login and register new users. My users are saved in a database (so far, I use in-memory H2 databse for testing).
The service is secured with OAuth2 (it is an auth server for other services I am developing).
Everything works fine and I am able to check the user's credentials but I would like to redirect the user somewhere after successful login. It works if I access the login page with the response_type=token parameter.
http://localhost:9000/auth/oauth/authorize?response_type=token&client_id=client&redirect_uri=http://localhost:9000/auth/user
But when I access the login page directly and login, it redirects me to the page I selected as my default success page but without the token or anything else that would indicate that the user is logged in and I get 401. Going straight to the /login and using the correct credentials results in this:
<oauth>
<error_description>
Full authentication is required to access this resource
</error_description>
<error>unauthorized</error>
</oauth>
Could someone point out to me what I need to adjust to make the login work?
WebSecurityConfig:
#Configuration
#Order(-20)
#EnableResourceServer
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("userDetailsService")
private UserDetailsService userDetailsService;
#Autowired
private AuthenticationManager authenticationManager;
#Override
protected void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.authorizeRequests()
.antMatchers("/register", "/confirm").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/")
.permitAll()
.and()
.requestMatchers()
.antMatchers("/login", "/oauth/authorize", "/oauth/confirm_access", "/register", "/confirm")
.and()
.logout()
.permitAll();
// #formatter:on
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.parentAuthenticationManager(authenticationManager);
auth.userDetailsService(userDetailsService);
}
}
OAuth2Config:
#Configuration
#EnableAuthorizationServer
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
#Autowired
#Qualifier("userDetailsService")
private UserDetailsService userDetailsService;
#Autowired
private AuthenticationManager authenticationManager;
#Autowired
private TokenStore tokenStore;
#Autowired
private DataSource dataSource;
// password encryptor
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer configurer) throws Exception {
configurer.authenticationManager(authenticationManager);
configurer.userDetailsService(userDetailsService);
configurer.tokenStore(tokenStore);
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.jdbc(dataSource);
}
}
LoginController:
#Controller
public class LoginController {
#GetMapping("/login")
public String login() {
return "login";
}
}
Login page:
<body>
<div class="container">
<form class="form-signin" th:action="#{/login}" method="post">
<h2 class="form-signin-heading">Sign in</h2>
<div th:if="${param.error}">Invalid username and password.</div>
<div th:if="${param.logout}">You have been logged out.</div>
<label for="inputEmail" class="sr-only">Email</label>
<input type="text" id="username"
class="form-control" placeholder="Email" name="username" th:required="required" th:autofocus="autofocus" />
<label for="inputPassword" class="sr-only">Password</label>
<input type="password" id="inputPassword"
class="form-control" placeholder="Password" name="password" th:required="required" />
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
</form>
</div>
<!-- /container -->
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</body>
Thank you for any help. I am happy to provide more details if this is not enough.
EDIT: What I want is basically a steteless authentication between the Auth server and other microservices but sessions-based login on the Auth server itself.
Check this first:
Spring Security OAuth 2 with form login
Provided that an access token is being created with basic form login, I would add custom authentication success handler:
WebSecurityConfig:
#Autowired
private AuthenticationSuccessHandler myAuthenticationSuccessHandler;
//
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/")
.successHandler(myAuthenticationSuccessHandler)
And the handler "MySimpleUrlAuthenticationSuccessHandler":
#Component("myAuthenticationSuccessHandler")
public class MySimpleUrlAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
//logic to grant access
}
I try to make authorization from website using angular $http and spring WebSecurityConfigurerAdapter here is my code:
Java part:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final Logger LOGGER = Logger.getLogger(SecurityConfig.class);
#Autowired
private UserDetailsService customUserDetailsService;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
// auth.inMemoryAuthentication().withUser("user").password("12345").roles("USER");
auth.userDetailsService(customUserDetailsService);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
CsrfTokenResponseHeaderBindingFilter bindingFilter = new CsrfTokenResponseHeaderBindingFilter();
http.addFilterAfter(bindingFilter, CsrfFilter.class);
http.authorizeRequests().antMatchers("/", "/home").permitAll()
.anyRequest().authenticated().and().formLogin().defaultSuccessUrl("/")
.loginProcessingUrl("/authenticate").usernameParameter("username").passwordParameter("password")
.successHandler(new AjaxAuthenticationSuccessHandler(new SavedRequestAwareAuthenticationSuccessHandler()))
.loginPage("/login/existinguser").and().httpBasic().and().logout().logoutUrl("/logout")
.logoutSuccessUrl("/login/existinguser").permitAll();
if ("true".equals(System.getProperty("httpsOnly"))) {
LOGGER.info("launching the application in HTTPS-only mode");
http.requiresChannel().anyRequest().requiresSecure();
}
}
}
JS part:
$scope.login = function (username, password) {
var postData = 'username='+username+'&password='+password;
$http({
method: 'POST',
url: '/authenticate',
data: postData,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"X-Login-Ajax-call": 'true'
}
})
.then(function(response) {
if (response.data == 'ok') {
debugger;
window.location.replace('/');
}
else {
debugger;
}
});
};
Problem is that I receive error:
POST http://localhost:8080/authenticate 404 (Not Found)
Can someone say what I need to do, to make it work?
Hi you have to configure authentication a rest service here you have complete tutorial for rest authentication in spring boot authentication - example uses jquery but it shouldn't be a problem for you to change js part to angular
I think the problem is with the csrf token. If you are sure with your controller, post method, then you should add the csrf token to the header of ajax call like;
var token = $("input[name='_csrf']").val();
var header = "X-CSRF-TOKEN";
$(document).ajaxSend(function(e, xhr, options) {
xhr.setRequestHeader(header, token);
});
Or you can disable csrf token in your SecurityConfig class.
You can do login form in login.ftl file like:
<#-- #ftlvariable name="_csrf" type="org.springframework.security.web.csrf.CsrfToken" -->
<#-- #ftlvariable name="error" type="java.util.Optional<String>" -->
<form role="form" action="/login" method="post">
<#--<label for="email">Email</label>-->
<input type="text" name="email" id="email" placeholder="EMAIL" required autofocus/>
<#--<label for="password">Password</label>-->
<input type="password" name="password" id="password" placeholder="PASSWORD" required/>
<button type="submit"> LOGIN </button>
</form>
<#if error.isPresent()>
<p>The email or password you have entered is invalid, try again.</p>
</#if>
in Java (Security):
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.authorizeRequests()
.antMatchers("/css/*").permitAll()
.antMatchers("/images/*").anonymous()
.antMatchers("/fonts/*").permitAll()
.antMatchers("/bower_components/**").permitAll()
.antMatchers("/scripts/**").permitAll()
.antMatchers("/template/**").permitAll()
.antMatchers("/views/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.failureUrl("/login?error")
.usernameParameter("email")
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.deleteCookies("remember-me")
.logoutSuccessUrl("/login")
.permitAll();
}
and controller:
#RequestMapping(value = "/login", method = RequestMethod.GET)
public ModelAndView getLoginPage(#RequestParam Optional<String> error) {
return new ModelAndView("login", "error", error);
}
It's work for me. I think, I help you.
I am trying to add login functionality with a database lookup with Spring Security, and I am really struggling. I want to look up the user by implementing the UserDetailsService interface. I have read a ton of documentation and Googled for hours, but I am still stuck. Most of the examples that I could find are using XML, so maybe my problems are related to converting those to Java configuration.
UserDetailsService
#Service
public class AccountServiceImpl implements AccountService { // AccountService implements UserDetailsService
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
// The email variable is always ''
List<GrantedAuthority> authList = new ArrayList<>();
authList.add(new Role("ROLE_USER")); // Role implements GrantedAuthority
return new User("test#example.com", "password", true, true, true, true, authList);
}
}
Security config
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private LogoutSuccessHandler logoutSuccessHandler;
#Autowired
private AccountService accountService;
#Override
#Bean
protected AuthenticationManager authenticationManager() throws Exception {
// This bean is required for using method security annotations
return super.authenticationManager();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/login/process")
.failureUrl("/login?error=true")
.defaultSuccessUrl("/", false)
.and()
//.userDetailsService(this.accountService)
.logout()
.logoutUrl("/logout")
.logoutSuccessHandler(this.logoutSuccessHandler)
.invalidateHttpSession(true)
.and()
// Permissions here
.csrf().disable();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("test#example.com").password("password").roles("USER");
}
/*#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
//.userDetailsService(this.accountService);
.inMemoryAuthentication()
.withUser("test#example.com").password("password").roles("USER").and()
.withUser("admin").password("password").roles("USER", "ADMIN");
}*/
/*#Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(this.accountService);
return provider;
}*/
}
As you can see, there is a bunch of outcommented code, which are just some of the things I tried to make it work. I also tried with simple in-memory authentication, but that didn't work either.
login.jsp
<form action="/login/process" method="POST">
<input name="j_username" id="j_username" type="text" />
<input name="j_password" id="j_password" type="password" />
<input type="submit" value="Login" />
</form>
Debug
DEBUG org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter - Request is to process authentication
DEBUG org.springframework.security.authentication.ProviderManager - Authentication attempt using org.springframework.security.authentication.dao.DaoAuthenticationProvider
DEBUG org.springframework.security.authentication.dao.DaoAuthenticationProvider - User '' not found
DEBUG org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter - Authentication request failed: org.springframework.security.authentication.BadCredentialsException: Bad credentials
DEBUG org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter - Updated SecurityContextHolder to contain null Authentication
The above seems to always be the case for all of the configurations that I tried. I am not even sure why the DaoAuthenticationProvider is used, even if I try to override it; I guess it's a default. For some of the configuration above, my UserDetailsService implementation gets called, but with an empty string as the parameter. Also, even if I return a hard coded UserDetails object with the same credentials as the ones I entered in my form, the authentication still fails, and I get redirected back to /login?error=true.
What am I doing wrong here? Please inform me about my mistakes or provide a simple example using Java configuration. Thanks!
When using Spring Security with Java based configuration the name of the request parameters are username and password and not j_username and j_password anymore.
You can either fix your login form
<form action="/login/process" method="POST">
<input name="username" id="j_username" type="text" />
<input name="password" id="j_password" type="password" />
<input type="submit" value="Login" />
</form>
or adapt your configuration to use the old field names.
formLogin().usernameParameter("j_username").passwordParameter("j_password");
Either way will resolve your issue.