Spring Security Custom login using Java Config Based - spring

I am using Spring Security Java based config. But unable to call process action when user submits login form. Here are my config and java file.
please let me know where I am doing something wrong.
Thanks in advance.
1) Spring security Java Config class
#Configuration
#EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
UserService userService;
#Bean
public AuthenticationManager authenticationManager() throws Exception{
AuthenticationManager authenticationManager = new ProviderManager(
Arrays.asList(authenticationProvider()));
return authenticationManager;
}
#Bean
public AuthenticationProvider authenticationProvider() throws Exception {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userService);
authenticationProvider.afterPropertiesSet();
return authenticationProvider;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/**").permitAll()
.antMatchers("/process/success").authenticated()
.and()
.formLogin()
.usernameParameter("username")
.passwordParameter("password")
.loginPage("/")
.failureUrl("/?auth=fail")
.loginProcessingUrl("/process")
.and().logout().logoutUrl("/logout")
.invalidateHttpSession(true).deleteCookies("JSESSIONID")
.permitAll();
}
}
2) Jsp login Page.
<form name="f" action="./process" method="post">
<fieldset>
<legend>Please Login</legend>
<c:if test="${'fail' eq param.auth}">
<div style="color: red">
Login Failed!!!<br /> Reason :
${sessionScope["SPRING_SECURITY_LAST_EXCEPTION"].message}
</div>
</c:if>
<c:if test="${'succ' eq param.out}">
<div style="color: blue">
<h2>You have been successfully logged out.</h2>
${sessionScope["SPRING_SECURITY_LAST_EXCEPTION"].message}
</div>
</c:if>
<div class="alert alert-success">${param.logout}</div>
<label for="username">Username</label> <input type="text"id="username" name="username" /> <label for="password">Password</label>
<input type="password" id="password" name="password" />
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
<div class="form-actions">
<button type="submit" class="btn">Log in</button>
</div>
</fieldset>
</form>
3) Here is Home Controller
#Controller
public class HomeController {
#Autowired
AuthenticationManager authenticationManager;
#RequestMapping(value = "/", method = RequestMethod.GET)
public String index() {
System.out.println("index.....");
return "index";
}
#RequestMapping(value = "/process", method = RequestMethod.POST)
public String process(#PathVariable("username") String userName,
#PathVariable("password") String password,
HttpServletRequest request, RedirectAttributes redirectAttr) {
try {
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(userName, password);
Authentication authenticate = authenticationManager.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(authenticate);
} catch (AuthenticationException e) {
System.out.println(e.getMessage());
}
System.out.println("login....." + request.getSession(false));
return "redirect:/process/success";
}
#RequestMapping(value = "/process/success", method = RequestMethod.GET)
public String success() {
System.out.println("success.....");
return "success";
}
#RequestMapping(value = "/logout", method = RequestMethod.GET)
public String logout(HttpServletRequest request) {
System.out.println("logout....." + request.getSession(false)+ " is new " + request.getSession(false).isNew());
request.getSession(false).invalidate();
return "index";
}
}

The problem is that Spring Security uses filters, and the request for normally intercepted and processed by the UsernamePasswordAuthenticationFilter. So it cannot reach your controller.
Spring Security uses a filter to process login for you and you should not even think to use a controller for that. You should read (again) the reference manual and start with a tutorial.

Related

Simple Spring Security example not logging in

Morning!
Just started learning Spring Security with the help of the Baeldung tutorial at https://www.baeldung.com/spring-security-authentication-with-a-database.
However, it's not quite working. What I'm trying to do is connecting my simple H2 database, containing a User table with id, username and password (in plaintext for simplicity), with my secured web application.
I created WebSecurityConfig (extending WebSecurityConfigurerAdapter, see below), MyUserDetailsService (implementing UserDetailsService, see below) and LoggedInUser (implementing UserDetails, see below) classes.
WebSecurityConfig: This should secure all pages except home, login and register pages, which is working. Also, globalSecurityConfiguration should enable the login function by linking to the userDetailsService.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
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;
#Configuration
#EnableWebSecurity
#ComponentScan(basePackageClasses = MyUserDetailsService.class)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private MyUserDetailsService userDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/home", "/register").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
#Autowired
public void globalSecurityConfiguration(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
}
MyUserDetailsService: This gets the Repository injection to access my database. I check the database for the username, and if it's present, I return a new LoggedInUser.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.List;
#Service
public class MyUserDetailsService implements UserDetailsService {
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String username) {
List<User> users = userRepository.findByUsername(username);
if (users.size() == 0) {
throw new UsernameNotFoundException(username);
}
return new LoggedInUser(users.get(0));
}
}
And finally, the LoggedInUser class:
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
public class LoggedInUser implements UserDetails {
private User user;
public LoggedInUser(User user) {
this.user= user;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return AuthorityUtils.createAuthorityList("ROLE_USER");
}
#Override
public String getPassword() {
return user.getPassword();
}
#Override
public String getUsername() {
return user.getNickname();
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
}
When I try to log in with some nonexistent username, the error message pops as it should. However, when I'm trying to log in with an existing username (with any password, right or wrong), it's not giving any error message but isn't logging me in either (at least I still can't access other secured pages of the app).
I'm omitting User and UserRepository classes since they're just pretty straightforward and well tested. My login page looks like that:
<html xmlns:th="http://www.thymeleaf.org" xmlns:tiles="http://www.thymeleaf.org">
<head>
<title>Spring Security Example</title>
</head>
<body>
<div class="container">
<form name="f" th:action="#{/login}" method="post">
<fieldset>
<legend>Please Login</legend>
<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>
<div class="form-group">
<label for="username">Username</label>
<input type="text" id="username" name="username"/>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" id="password" name="password"/>
</div>
<button type="submit" class="btn btn-primary">Login</button>
</fieldset>
</form>
</div>
</body>
</html>
I know that the loadUserByUsername method isn't touching the password, but from what I've read, checking if the right password was entered happens automatically within the Security framework.
I also tried to implement my own AuthenticationProvider to use instead of the UserDetailsService to check if both username and password inputs match the database entries within the authenticate method, but then I encountered another problem - wrong credentials now get flagged right, but right credentials produced an error Cannot invoke "Object.toString()" because the return value of "org.springframework.security.core.Authentication.getCredentials()" is null. However the line the error mentioned was the one that reads the password from the user input - and since this only happens for passwords matching the correct one, this shouldn't be null. I'm not posting code here since probably this is a different issue though.
Thanks for any help! Remember, this is like the first time I touched any security framework, so better ELI5 :)

OAuth2 Client Principal do not have GrantedAuthorities when authenticated by Other Custom Authorization Server (SpringBoot2 & OAuth2)

i'm using Spring Boot2 as Framework and Thymeleaf as template engine.
in my authorization server, i added user 'admin' as 'ROLE_ADMIN'.
but in Client Application, when i loged in as 'admin' and print Authentication Object from SecurityContextHolder.getContext().getAuthentication(), Granted Authorities property has only 'ROLE_USER'.
following is my authorization server config.
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("admin").password(passwordEncoder().encode("123")).roles("USER", "ADMIN");
auth
.inMemoryAuthentication()
.withUser("user").password(passwordEncoder().encode("123")).roles("USER");
}
and following is Authentication Object from SecurityContextHolder.getContext().getAuthentication()'s logging code.
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
System.out.println(auth.isAuthenticated());
System.out.println(auth.getAuthorities());
System.out.println(auth.getPrincipal());
and result is
// isAuthenticated()
true
// getAuthorites()
[ROLE_USER]
// getPrincipal()
Name: [admin], Granted Authorities: [ROLE_USER], User Attributes: [authorities=[{authority=ROLE_ADMIN}, {authority=ROLE_USER}], ...
following is my thymeleaf code.
<div sec:authorize="isAuthenticated()">
Text visible only to authenticated users.
<!-- Principal name -->
Authenticated username:
<div sec:authentication="name"></div>
<div sec:authorize="hasRole('USER')">Text visible to user.</div>
<!-- i cant see this message -->
<div sec:authorize="hasRole('ADMIN')">Text visible to admin.</div>
Authenticated user roles:
<!-- print '[ROLE_USER]' only -->
<div sec:authentication="principal.authorities"></div>
</div>
<div sec:authorize="!isAuthenticated()">Text visible only to
unauthenticated users.
</div>
so, i want to access Principal.UserAttributes.authorities in thymeleaf.
i'm refering OAuth2AuthenticationToken, OAuth2User.getAttributes() and DefaultOAuth2User.toString()
how can i do this?
I solved.
In Authorization Server, i configed like this.
AuthorizationServer WebSecurityConfigurerAdapter config
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
...
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("admin").password(passwordEncoder().encode("123")).roles("USER", "ADMIN").authorities("USER", "ADMIN");
auth
.inMemoryAuthentication()
.withUser("user").password(passwordEncoder().encode("123")).roles("USER");
}
...
}
and following is my Resource Server's /me mapping controller
ResourceServer /me mapped Controller
#RestController
public class UserController {
#RequestMapping("/me")
public Principal user(Principal principal) {
return principal;
}
}
and following is my Client's WebSecurityConfigurerAdapter config
Client WebSecurityConfigurerAdapter config
#Configuration
#EnableOAuth2Client
public class WebSecurityConfigurerAdapterImpl extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/", "/home", "/error", "/webjars/**", "/resources/**", "/login**").permitAll()
.anyRequest().authenticated()
.and().oauth2Login();
}
and in Client's Controller, i logged like this.
logging Principal in Client Controller
#GetMapping("")
public String git1() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
System.out.println(auth.getPrincipal());
/** Thymeleaf using this **/
Object authenticationProperty = AuthUtils.getAuthenticationProperty(auth, "principal.attributes['authorities']");
System.out.println(authenticationProperty.toString());
return VIEW_PATH + "git1";
}
and following is the result
Name: [admin], Granted Authorities: [ROLE_USER], User Attributes: [authorities=[{authority=USER}, {authority=ADMIN}], details={remoteAddress=127.0.0.1, sessionId=null, tokenValue=82a7a532-a31e-4d0a-bd83-f15a9cbea3bc, tokenType=Bearer, decodedDetails=null}, authenticated=true, userAuthentication={authorities=[{authority=USER}, {authority=ADMIN}], details=null, authenticated=true, principal=admin, credentials=N/A, name=admin}, oauth2Request={clientId=foo, scope=[read], requestParameters={client_id=foo}, resourceIds=[], authorities=[], approved=true, refresh=false, redirectUri=null, responseTypes=[], extensions={}, refreshTokenRequest=null, grantType=null}, clientOnly=false, principal=admin, credentials=, name=admin]
[{authority=USER}, {authority=ADMIN}]
as you can see, i added 'ROLE_USER' and 'ROLE_ADMIN' Authorities in Authorization Server.
in Resource Server's Principal Object granted both 'ROLE_ADMIN' and 'ROLE_USER'.
but in Client's Principal Object doesn't granted 'ROLE_ADMIN'. there is 'ROLE_USER' Only.
and Principal.atttibutes['authorities'] has 'USER', 'ADMIN'.
as #Rahil Husain said, there is DefaultOAuth2UserService and this service grant 'ROLE_USER' only to OAuth2User Object.
first, i added CustomAuthoritiesExtractor via #Componenet annotation (#Bean too.) to Client.
but this doesn't working in my projects.
so, i implemented CustomOAuth2User and CustomOAuth2UserService.
like this.
CustomOAuth2User
public class CustomOAuth2User implements OAuth2User {
private List<GrantedAuthority> authorities;
private Map<String, Object> attributes;
private String name;
public CustomOAuth2User(List<GrantedAuthority> authorities, Map<String, Object> attributes) {
this.authorities = authorities;
this.attributes = attributes;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities;
}
#Override
public Map<String, Object> getAttributes() {
if (this.attributes == null) {
this.attributes = new HashMap<>();
this.attributes.put("name", this.getName());
}
return attributes;
}
#Override
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
and following is CustomOAuth2UserService
CustomOAuth2UserService
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
#Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oAuth2User = super.loadUser(userRequest);
AuthoritiesExtractor authoritiesExtractor = new CustomAuthoritiesExtractor();
List<GrantedAuthority> grantedAuthorityList = authoritiesExtractor.extractAuthorities(oAuth2User.getAttributes());
CustomOAuth2User customOAuth2User = new CustomOAuth2User(grantedAuthorityList, oAuth2User.getAttributes());
customOAuth2User.setName(oAuth2User.getName());
return customOAuth2User;
}
}
and following is my CustomAuthoritiesExtractor. this class not used as #Bean or #Component. directly used in CustomOAuth2Service for mapping CustomOAuth2User object's authorities
CustomAuthoritiesExtractor
public class CustomAuthoritiesExtractor implements AuthoritiesExtractor {
#Override
public List<GrantedAuthority> extractAuthorities(Map<String, Object> map) {
return AuthorityUtils.commaSeparatedStringToAuthorityList(asAuthorities(map));
}
private String asAuthorities(Map<String, Object> map) {
List<String> authorities = new ArrayList<>();
List<LinkedHashMap<String, String>> authz =
(List<LinkedHashMap<String, String>>) map.get("authorities");
for (LinkedHashMap<String, String> entry : authz) {
authorities.add(entry.get("authority"));
}
return String.join(",", authorities);
}
}
and final, i changed Client's endpoint to using my CustomOAuth2User and CustomOAuth2UserService.
so, i changed Client's WebSecurityConfigurerAdapter config like this.
#Configuration
#EnableOAuth2Client
public class WebSecurityConfigurerAdapterImpl extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/", "/home", "/error", "/webjars/**", "/resources/**", "/login**").permitAll()
.anyRequest().authenticated()
.and().oauth2Login()
/** add this config**/
.userInfoEndpoint()
.customUserType(CustomOAuth2User.class, "teemo")
.userService(this.oauth2UserService());
}
private OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() {
return new CustomOAuth2UserService();
}
and following is my thymeleaf.
thymeleaf
<div sec:authorize="isAuthenticated()">
Text visible only to authenticated users.
Authenticated username:
<div sec:authentication="name"></div>
<div sec:authorize="hasRole('USER')">hasRole('USER')</div>
<div sec:authorize="hasRole('ROLE_USER')">hasRole('ROLE_USER')</div>
<div sec:authorize="hasRole('ADMIN')">hasRole('ADMIN')</div>
<div sec:authorize="hasRole('ROLE_ADMIN')">hasRole('ROLE_ADMIN')</div>
<!-- TRUE -->
<div sec:authorize="hasAuthority('USER')">hasAuthority('USER')</div>
<div sec:authorize="hasAuthority('ROLE_USER')">hasAuthority('ROLE_USER')</div>
<!-- TRUE -->
<div sec:authorize="hasAuthority('ADMIN')">hasAuthority('ADMIN')</div>
<div sec:authorize="hasAuthority('ROLE_ADMIN')">hasAuthority('ROLE_ADMIN')</div>
</div>
<div sec:authorize="!isAuthenticated()">Text visible only to
unauthenticated users.
</div>
and following is the result.
Text visible only to authenticated users. Authenticated username:
admin
hasAuthority('USER')
hasAuthority('ADMIN')
anyone who digging like me, i hope help this question and answers.
but i don't know this is de facto-standard way.
just.. working now.
Use #authentication Object
<div th:text="${#authentication.principal.something}">
The value of the "name" property of the authentication object should appear here.
</div>
Example:
<img th:if="${#authentication.principal.image}"
class="img-circle" th:src="${#authentication.principal.image}"
width="100" height="100" alt="place-holder" />
But Add this dependency first
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
<version>-latest-version-here-</version>
</dependency>
As it does not come with thymeleaf-starter in spring boot
You can pass the Principal to your controller as argument, like
public String myController(Principal principal) {
...
}
You will also have to map the authorities to granted Authorities by yourself, e.g. using the AuthoritiesExtractor Interface from Spring, here is an example: Link from Baeldung

Error 403 when I submit the register form in spring boot project

I am learning spring boot and write a register form, but when I run it in idea and submit the form, the browser occurs
There was an unexpected error (type=Forbidden, status=403).
Forbidden
I create the project by using spring initializr in idea, choose web+jpa+h2+thymeleaf.
I defined an Entity called Worker and set error messages in ValidationMessages.properties, here is the Worker entity
#Entity
public class Worker implements UserDetails {
private static final long serialversionUID = 1L;
#Id
#NotNull
#Size(min = 5, max = 16, message = "{username.size}")
private String username;
#NotNull
#Size(min = 2, max = 30, message = "{firstName.size}")
private String firstname;
#NotNull
#Size(min = 2, max = 30, message = "{lastName.size")
private String lastname;
#NotNull
#Size(min = 5, max = 25,message = "{password.size}")
private String password;
#NotNull
#Size(min = 2, max = 30, message = "{profession,size}")
private String profession;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
public String getLastname() {
return lastname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getProfession() {
return profession;
}
public void setProfession(String profession) {
this.profession = profession;
}
//UserDetails methods
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Arrays.asList(new SimpleGrantedAuthority("WORKER"));
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
}
and WorkersRepository
public interface WorkersRepository extends JpaRepository<Worker, String> {
Worker findByUsername(String username);
}
I have added spring security, and wrote the config:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private WorkersRepository workersRepository;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/submit").access("hasRole('WORKER')")
.anyRequest().permitAll()
.and()
.formLogin()
.loginPage("/login")
.and()
.logout()
.logoutSuccessUrl("/")
.and()
.rememberMe()
.tokenValiditySeconds(4838400)
.key("workerKey");
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(new UserDetailsService() {
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return workersRepository.findByUsername(username);
}
});
}
}
If the register input occurs error, the controller returns the registerForm.html to ask user to input again correctly. If the register has no error, the controller redirects to "/", a simple welcome.html. But whether the input is correct or not, I always get the error 403. When I inputhttp://localhost:8080/, I can get the welcome.html,which is a simple page with "welcome!" words. My controller is
private WorkersRepository workersRepository;
#Autowired
public WorkingHoursController(
WorkersRepository workersRepository) {
this.workersRepository = workersRepository;
}
#RequestMapping(method = RequestMethod.GET)
public String welcomePage() {
return "welcome";
}
#RequestMapping(value = "/register", method = RequestMethod.GET)
public String showRegistrationForm(Model model) {
model.addAttribute(new Worker());
return "registerForm";
}
#RequestMapping(value = "/register", method = RequestMethod.POST)
public String registrationProcessing(#Valid Worker worker, Errors errors, RedirectAttributes model) {
if(errors.hasErrors()) {
return "registerForm";
}
workersRepository.save(worker);
model.addAttribute("username", worker.getUsername());
model.addFlashAttribute("worker", worker);
return "redirect:/";
}
...
I wrote the registerForm.html using thymeleaf and add error validations. My registerForm.html is
<form class="form-signin" method="post" th:object="${worker}">
<div class="errors" th:if="${#fields.hasErrors('*')}">
<ul>
<li th:each="err : ${#fields.errors('*')}"
th:text="${err}">Input is in correct.</li>
</ul>
</div>
<img class="mb-4" src="https://getbootstrap.com/assets/brand/bootstrap-solid.svg" alt="" width="72" height="72">
<h1 class="h3 mb-3 font-weight-normal">Please register</h1>
<!-- input username -->
<label for="inputUsername" th:class="${#fields.hasErrors('username')}? 'error'">Username</label>
<input type="text" id="inputUsername" th:field="*{username}" th:class="${#fields.hasErrors('username')}? 'error form-control':'form-control'" placeholder="Username">
...
<!-- input password -->
<label for="inputPassword" th:class="${#fields.hasErrors('password')}? 'error'">Password</label>
<input type="password" id="inputPassword" th:field="*{password}" th:class="${#fields.hasErrors('password')}? 'error form-control':'form-control'" placeholder="Password">
<div class="checkbox mb-3">
<label>
<input type="checkbox" id="remember-me" name="remember-me"> Remember me
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit">Register</button>
Before I add validations in thymeleaf and add spring security, everything seems to work properly.
You did not put any action inside form tag. Perhaps that's why you are getting error. Put action inside form tag like this one
<form class="form-signin" action="#" th:action="#{/register}" method="post" th:object="${worker}">
Please check once whether role should be "WORKER" or "ROLE_WORKER" according to your Spring Security JAR version. Also disable the CSRF in your application, and set global CORS config to accept all requests.
add the following to your config class
#Override
public void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable()
}
This is security stuff, it prevents hackers from using your open account credentials without your permission i.e. you get an email that say click here (that click takes your user info to another site without your consent). only use the above for dev.
For pro; add a csrf token as a hidden input on your form. this token is in your spring server.
<input type="hidden" name="_csrf" th:value="${_csrf.token}"/>

Spring boot security custom messages while user login

I am trying to integrate spring security in my spring boot application.All working ok but how do I display a message if the account is expired or account is locked? Also, I do not want to display error message based on parm like http://localhost:8080/login?error
Here is my current code: login.html
<div th:if="${param.error}" class="alert alert-danger">
Invalid username or password.
</div>
<h3>Sign in to continue</h3>
<form th:action="#{/login}" name="loginForm" method="POST">
<div class="form-group">
<label for="userNameInput">Username</label>
<input type="text" class="form-control" id="userNameInput" name="username" placeholder="Username" />
</div>
<div class="form-group">
<label for="passwordInput">Password</label>
<input type="password" class="form-control" id="passwordInput" name="password" placeholder="Password" />
</div>
<button type="submit" class="btn btn-success">Login</button>
</form>
WebSecurityConfig.java
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomUserDetailsService customUserDetailsService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/", "/css/**", "/js/**","/login/**").permitAll()
.anyRequest().authenticated()
.and().formLogin().loginPage("/login").defaultSuccessUrl("/dashboard")
.and().logout().logoutSuccessUrl("/");
}
#Autowired
public void configAuthBuilder(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailsService).passwordEncoder(new BCryptPasswordEncoder());
}
}
CustomUserDetailsService.java
#Service
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
UserRepository userRepository;
#Override
#Transactional(readOnly = true)
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//query for user from DB
User user = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException(username);
}
Date today = new Date();
//do check if account expired / suspended / deleted
Boolean isAcconutExpired = false;
Boolean status = false;
if (user.getExpireOn() != null && today.before(user.getExpireOn())) {
isAcconutExpired = false;
}
if(user.getStatus() == 1){
status = true;
}
return new org.springframework.security.core.userdetails.User(user.getUsername(),
user.getPassword(),
status,
!isAcconutExpired,
true,
true,
getAuthorities(user));
}
private List<GrantedAuthority> getAuthorities(User user) {
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add(new SimpleGrantedAuthority("USER"));
return authorities;
}
}
UserRepository.java
#Repository
public interface UserRepository extends CrudRepository<User, Long> {
User findByUsername(String username);
}
This message is a bit old, but I'm currently facing the same problem.
So, firstly, you have to create a custom instance of Authentication Provider to let HideUserNotFoundExceptions be passed to the controler:
public AuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider impl = new DaoAuthenticationProvider();
impl.setUserDetailsService(customUserDetailsService);
impl.setPasswordEncoder(new BCryptPasswordEncoder());
impl.setHideUserNotFoundExceptions(false) ;
return impl ;
}
Moreover, you should add this provider in the AuthenticationProviderBuilder, instead of adding customDetailService (adding customDetailService will add an other provider) :
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(daoAuthenticationProvider());
}
With this, you can now catch UserNotFoundException instead of basic BadCredentialsException .
So, it remains to display custom error message for these two exceptions. The Badcredentials exception is throw directly by SpringSecurity with an error message based on I18n message AbstractUserDetailsAuthenticationProvider.badCredentials (see the implementation in spring security here ):
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
Same for expiration and account locked.
So I suggest you to change your CustomUserDetailsService.java class to do the same with a similar code error :
if (user == null) {
throw new UsernameNotFoundException(SpringSecurityMessageSource.getAccessor().getMessage("AbstractUserDetailsAuthenticationProvider.UserUnknown",new Object[] {login},"User is not known"));
}
After this, you can add these lines in your message.properties :
AbstractUserDetailsAuthenticationProvider.UserUnknown = {0} was not found.
AbstractUserDetailsAuthenticationProvider.badCredentials = Password is bad
AbstractUserDetailsAuthenticationProvider.credentialsExpired = User credentials have expired
AbstractUserDetailsAuthenticationProvider.disabled = User is disabled
AbstractUserDetailsAuthenticationProvider.expired = User account has expired
AbstractUserDetailsAuthenticationProvider.locked = User account is locked
And display error message in login.html :
<div class="dialog-row">
<label th:if="${param.error}" th:text="${session['SPRING_SECURITY_LAST_EXCEPTION'].message}" class="text-center redText">Mot de passe inconnu</label>
</div>

Invalid target for Validator in spring error?

Hi all I am getting the following error whenever I am trying to invoke validator in my spring
Servlet.service() for servlet spring threw exception: java.lang.IllegalStateException: Invalid target for Validator
Please have a look and help me out in this error, previously I user the validation for login page and it is working fine but now its not working.
Here is my code snippet .
Controller
#Controller
public class NewUserRegistration
{
#Autowired
private UserService userService;
#Autowired
private NewUserValidator newUserValidator;
#InitBinder
public void initBinder(WebDataBinder binder)
{
binder.setValidator(newUserValidator);
}
#RequestMapping(value="/newUserAdd", method=RequestMethod.POST)
public String addUser(#ModelAttribute("user")#Valid User user,BindingResult result, Model model)
{
return "NewUser";
}
}
Validator
#Component
public class NewUserValidator implements Validator
{
#Override
public boolean supports(Class<?> classz)
{
return NewUserRegistration.class.equals(classz);
}
#Override
public void validate(Object obj, Errors error)
{
//Validation login for fields
}
}
JSP Page
<form:form action="newUserAdd" method="POST" modelAttribute="user">
<center>
<table>
<tr><td>User Id:</td><td><input name="userId" type="text" /></td><td><font color="red"><c:out value="${userIdError}" /></font> </td></tr>
<tr><td>Password:</td><td><input name="userPassword" type="password"/></td><td><font color="red"><c:out value="${userPasswordError}" /></font></td></tr>
<tr><td>Confirm Password:</td><td><input name="userConfirmPassword" type="password"/></td><td><font color="red"><c:out value="${userPasswordError}" /></font></td></tr>
<tr><td>Name:</td><td><input name="userName" type="text"/></td><td><font color="red"><c:out value="${userPasswordError}" /></font></td></tr>
<tr><td></td><td><input type="submit" value="Create"/></td></tr>
</table>
</center>
</form:form>
The problem is actually in Validator class you are using NewUserRegistration's object which is wrong because you want to validate your User's object not your NewUserRegistration's object.
#Override
public boolean supports(Class<?> classz)
{
return NewUserRegistration.class.equals(classz);
}
which should be
#Override
public boolean supports(Class<?> classz)
{
return User.class.equals(classz);
}

Resources