Spring boot security custom messages while user login - spring

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>

Related

Thymeleaf - Getting full name of authenticated user

According to thymeleaf security page I can get the logged username and roles as below:
Logged user: <span sec:authentication="name">Bob</span>
Roles: <span sec:authentication="principal.authorities">[ROLE_USER, ROLE_ADMIN]</span>
I have a web application where authentication is done through active directory using ActiveDirectoryLdapAuthenticationProvider as below:
#Bean
#Override
public AuthenticationManager authenticationManager() {
return new ProviderManager(Arrays.asList(activeDirectoryLdapAuthenticationProvider()));
}
#Bean
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(adDomain,
adUrl);
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
return provider;
}
Then after the user logged in I have a header page that I use in all my pages with the above sec:authentication="name" thymeleaf tag to show the username, but I wanted to see if there's a way to show the full name instead.
Solution suggested here is not working for me:
I'm using: thymeleaf-extras-springsecurity5
and using: <span th:text ="${#authentication.getPrincipal().getUser().getFirstName()}"></span>
Is giving me: Method getUser() cannot be found on type org.springframework.security.ldap.userdetails.LdapUserDetailsImpl
It seems this information comes from: org.springframework.security.ldap.userdetails.LdapUserDetailsImpl and there just a few options are, like the username, but not the rest of the information an AD can have.
This is how I solved my issue:
First
I added to the provider an UserDetailsContextMapper with org.springframework.security.ldap.userdetails.InetOrgPersonContextMapper as below:
#Bean
public AuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider = new ActiveDirectoryLdapAuthenticationProvider(adDomain, adUrl);
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
provider.setUserDetailsContextMapper(userDetailsContextMapper()); <---
return provider;
}
#Bean
public UserDetailsContextMapper userDetailsContextMapper() {
return new LdapUserDetailsMapper() {
#Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities) {
InetOrgPersonContextMapper personContextMapper = new InetOrgPersonContextMapper();
return personContextMapper.mapUserFromContext(ctx, username, authorities);
}
};
}
Second
Then in my header page I added:
<span th:text ="${#authentication.getPrincipal().getDisplayName()}"></span>
To display the full name.
With this approach you also have access to show all other AD related fields such as:
<span th:text ="${#authentication.getPrincipal().getMail()}"></span>
<span th:text ="${#authentication.getPrincipal().getTelephoneNumber()}"></span>
<span th:text ="${#authentication.getPrincipal().getRoomNumber()}"></span>

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}"/>

How can I display the current logged in User with Spring Boot Thymeleaf?

I am trying to display the details of the current user however I keep getting errors. I tried accessing the authenticated user from the template but that did not work as I was getting this error:
Method getFirstName() cannot be found on org.springframework.security.core.userdetails.User type
I was trying to get the information from a controller and then saving it in a string and passsing the string to a template but that wasn't working either.
Here is my SecurityConfig class:
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserService userService;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(
"/registration",
"/js/**",
"/css/**",
"/img/**",
"/webjars/**").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.invalidateHttpSession(true)
.clearAuthentication(true)
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/login?logout")
.permitAll();
}
#Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
#Bean
public DaoAuthenticationProvider authenticationProvider(){
DaoAuthenticationProvider auth = new DaoAuthenticationProvider();
auth.setUserDetailsService(userService);
auth.setPasswordEncoder(passwordEncoder());
return auth;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
Here is my UserService Class:
public interface UserService extends UserDetailsService {
User findByEmailAddress(String emailAddress);
// User findByFirstName(String firstName);
User save(UserRegistrationDto registration);
}
Here is my UserServiceImpl class:
#Service
public class UserServiceImpl implements UserService {
#Autowired
private UserRepository userRepository;
#Autowired
private BCryptPasswordEncoder passwordEncoder;
#Override
public UserDetails loadUserByUsername(String emailAddress) throws
UsernameNotFoundException {
User user = userRepository.findByEmailAddress(emailAddress);
if (user == null){
throw new UsernameNotFoundException("Invalid username or
password.");
}
return new
org.springframework.security.core.userdetails.User(user.getEmailAddress(),
user.getPassword(),
mapRolesToAuthorities(user.getRoles()));
}
public User findByEmailAddress(String emailAddress){
return userRepository.findByEmailAddress(emailAddress);
}
public User save(UserRegistrationDto registration){
User user = new User();
user.setFirstName(registration.getFirstName());
user.setSurname(registration.getSurname());
user.setEmailAddress(registration.getEmailAddress());
user.setPassword(passwordEncoder.encode(registration.getPassword()));
user.setRoles(Arrays.asList(new Role("ROLE_USER")));
return userRepository.save(user);
}
private Collection<? extends GrantedAuthority>
mapRolesToAuthorities(Collection<Role> roles){
return roles.stream()
.map(role -> new SimpleGrantedAuthority(role.getName()))
.collect(Collectors.toList());
}
}
Here is some code from the template class where I'm trying to get the information:
th:text ="${#authentication.getPrincipal().getFirstName()}">
th:text ="${#authentication.getPrincipal().getUser().getFirstName()}">
This is the login controller. The parts I have commented out was another way I was trying to get the current users details:
#Controller
//#RequestMapping("/login")
public class MainController {
// #GetMapping("/")
// public String root() {
// return "userProfile1";
// }
#GetMapping("/login")
public String login(Model model) {
return "login";
}
// #GetMapping
// public String displayUserAccount(#ModelAttribute("user") #Valid
UserRegistrationDto userDto, BindingResult result, Model model) {
//
//
// model.addAttribute("firstName", ((UserRegistrationDto)
auth).getEmailAddress());
//
// model.addAttribute("emailAddress", userDto.getEmailAddress());
// model.addAttribute("firstName", userDto.getFirstName());
// model.addAttribute("surname", userDto.getSurname());
// model.addAttribute("age", userDto.getAge());
// model.addAttribute("gender", userDto.getGender());
// model.addAttribute("dob", userDto.getDob());
// // return "redirect:/registration?success";
// return "userProfile1";
//
// }
#ResponseBody
public String currentUserName(Authentication auth) {
((UserRegistrationDto) auth).getEmailAddress();
return "userProfile1";
}
}
This is all over the place I'm sorry! Thanks so much for anyone who helps :D
You can use Thymeleaf extras for display authenticated user details.
Thymeleaf Extras Springsecurity4
<div th:text="${#authentication.name} ></div>
The problem is here:
return new
org.springframework.security.core.userdetails.User(user.getEmailAddress(),
user.getPassword(),
mapRolesToAuthorities(user.getRoles()));
You lose the reference to your User entity. Change it to:
return user;
For this to work, you need to update your User entity to implement UserDetails interface:
public class User implements UserDetails {
// some new methods to implement
}
Then, your Thymleaf code should work. Another way of getting the firstName would be:
<span th:text="${#request.userPrincipal.principal.firstName}"></span>
I figured out how to fix my problem.
I created this method in a controller:
#Autowired
UserRepository userR;
#GetMapping
public String currentUser(#ModelAttribute("user") #Valid UserRegistrationDto userDto, BindingResult result, Model model) {
Authentication loggedInUser = SecurityContextHolder.getContext().getAuthentication();
String email = loggedInUser.getName();
User user = userR.findByEmailAddress(email);
String firstname = user.getFirstName();
model.addAttribute("firstName", firstname);
model.addAttribute("emailAddress", email);
return "userProfile1"; //this is the name of my template
}
and then I added this line of code in my html template:
Email: th:text="${emailAddress}"
Reference (4. Spring Security Dialect):
https://www.thymeleaf.org/doc/articles/springsecurity.html
Add dependencies pom.xml
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
</dependency>
and the view (Thymeleaf):
<div sec:authorize="isAuthenticated()">
Authenticated user roles:
Logged user: <span sec:authentication="name"></span> |
Roles: <span sec:authentication="principal.authorities"></span>
</div>
I hope you serve them
You can get the username attribute easily from your Principal class.
#GetMapping(value = "/")
public String index(#AuthenticationPrincipal MyUserPrincipal principal) {
String username = principal.getUsername();
//Do whatever you want here
return "index";
}
However, if you want more details than the ones inside Principal class then you need to explicitly define them in your principal class:
public int getId() {
return member.getId();
}
So now you can invoke it directly:
#GetMapping(value = "/")
public String index(#AuthenticationPrincipal MyUserPrincipal principal) {
int userId = principal.getId();
//Do whatever you want here
return "index";
}
You will need to import the following:
import org.springframework.security.core.annotation.AuthenticationPrincipal;
If you only want to get a Principal class attribute directly from Thymeleaf then you can alternatively do the following:
<span sec:authentication="principal.username">Username</span>

Spring Security Custom login using Java Config Based

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.

Resources