Thymeleaf - Getting full name of authenticated user - spring-boot

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>

Related

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

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>

How to set up auto login for user in Spring Boot after user registration using bcrypt, getting Bad Credentials error

I have a question regarding how I can automatically log in a user after it has registered with my Spring Boot app. The user's password is saved into the MySQL DB using Bcrypt.
This is a method I have to properly create and save a new user, and it seems to work fine :
#Autowired
private UserRepository userRepository;
#Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
public User save(User user, Role role) {
user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
user.setRoles(new HashSet<>(Arrays.asList(role)));
userRepository.save(user);
return user;
}
This is the method I execute to try to log in a newly created user :
public boolean login(String username, String password) {
//password is plaintext and is what was POST-ed from the HTML form
UserDetails userDetails = loadUserByUsername(username);
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(userDetails, password, userDetails.getAuthorities());
authenticationManager.authenticate(usernamePasswordAuthenticationToken);
if (usernamePasswordAuthenticationToken.isAuthenticated()) {
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
logger.debug(String.format("Logged in %s successfully!", username));
return true;
} else {
logger.debug(String.format("Failed to login %s", username));
return false;
}
}
Now, when it hits the line:
authenticationManager.authenticate(usernamePasswordAuthenticationToken);
it will always complain:
00:36:47.635 [http-nio-5000-exec-10] DEBUG o.s.web.servlet.DispatcherServlet - Could not complete request
org.springframework.security.authentication.BadCredentialsException: Bad credentials
I thought this was strange. In my WebSecurityConfigurerAdapter , I have these set up:
#Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userServiceManager).passwordEncoder(bCryptPasswordEncoder());
}
Could someone please let me know if I might have missed anything?
Thanks

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>

spring security, UserDetailsService, authenticationProvider, pass word encoder.. i'm lost

first, i've read/re-read (repeat 10 times), at least 6 books on spring and spring security and have googled my brains out trying to figure it all out.
after working w/ spring for 10 years, i still find that there is so much annotation-defined, injected, component, config annotation magic going on that i have 0 confidence that i understand my applications as i should.
examples online are either xml-config, not complete, done n diff. ways, overly simplistic, using older spring, conflicting and just simply not built to handle a basic realistic use-case.
as an example, the following code is trying to handle a simple logon, authenticated to db table using an encoder for passwords.
form post includes a "client" to which one authenticates to, a persisted IP address and some url path info for deep linking post logon.
(all really basic stuff for today's single-page web apps)
i originally had this working using xml config, but javaConfig has me stuck.
i have no idea how the userDetailsService, AuthenticationManagerBuilder and PasswordEncoder interact in SecurityConfiguration. I get logon data to the service, but am not sure where or when a spring authenticationProvider is applied or if i even need one.
my User implements UserDetails and holds the required fields.
i populate those and granted authorities in my CustomUserDetailsService.
how/when/why do i need an auth.authenticationProvider(authenticationProvider()), if i check db using logon/password in my service?
my UserDetailsService seems to be executing twice now.
how does spring take the submitted password, encode it and compare to that stored in the db?
how does it know to use the same salt as that used when the p/w was created/persisted when the user was created?
why does configureGlobal() define both auth.userDetailsService and auth.authenticationProvider when authenticationProvider() also sets the userDetailsService?
why is my brain so small that i cannot make sense of this ? :)
#Service
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private ClientDAO clientDAO;
#Autowired
private UserDAO userDAO;
public UserDetails loadUserByUsername(String multipartLogon) throws UsernameNotFoundException, DataAccessException {
Boolean canAccess = false;
Long clientId = null;
String userLogon = null;
String password = null;
String id = null;
String entryUrl = null;
String ipAddress = null;
String urlParam = null;
String[] strParts = multipartLogon.split(":");
try {
userLogon = strParts[0];
password = strParts[1];
id = strParts[2];
entryUrl = strParts[3];
ipAddress = strParts[4];
urlParam = strParts[5];
} catch(IndexOutOfBoundsException ioob) { }
Client client = new Client();
if (!"".equals(id)) {
clientId = IdUtil.toLong(id);
client = clientDAO.getClient(clientId);
}
//BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
//String encodedPassword = passwordEncoder.encode(password);
//String encodedPassword = "$2a$22$6UiVlDEOv6IQWjKkLm.04uN1yZEtkepVqYQ00JxaqPCtjzwIkXDjy";
User user = userDAO.getUserByUserLogonPassword(userLogon, password); //encodedPassword?
user.isCredentialsNonExpired = false;
Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
for (UserRole userRole : userDAO.getUserRolesForUser(user)) {
if (userRole.getRole().getActiveStatus()) {
authorities.add(new SimpleGrantedAuthority(userRole.getRole().getRoleName()));
user.isCredentialsNonExpired = true;
}
}
user.setAuthorities(authorities);
user.setPassword(password); //encodedPassword?
user.setUsername(user.getUserLogon());
user.isAccountNonExpired = false;
user.isAccountNonLocked = false;
List<ClientUser> clientUsers = clientDAO.getClientUsersForUser(user);
for (ClientUser clientUser : clientUsers) {
if (clientUser.getClient().getClientId().equals(client.getClientId())) {
canAccess = true;
break;
}
}
user.isEnabled = false;
if (user.getActiveStatus() && canAccess) {
user.isAccountNonExpired = true;
user.isAccountNonLocked = true;
user.isEnabled = true;
Session session = userDAO.getSessionForUser(user);
if (session == null) { session = new Session(); }
session.setUser(user);
session.setDateLogon(Calendar.getInstance().getTime());
session.setClient(client);
session.setEntryUrl(entryUrl);
session.setUrlParam(urlParam);
session.setIPAddress(ipAddress);
session.setActive(true);
userDAO.persistOrMergeSession(session);
}
return user;
}
}
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
CustomUserDetailsService customUserDetailsService;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailsService);
auth.authenticationProvider(authenticationProvider());
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(customUserDetailsService);
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/conv/a/**").access("hasRole('ROLE_ADMIN') or hasRole('ROLE_COURT_ADMIN')")
.antMatchers("/conv/u/**").access("hasRole('ROLE_USER') or hasRole('ROLE_ADMIN') or hasRole('ROLE_COURT_ADMIN')")
.antMatchers("/**").permitAll()
.and()
.formLogin()
.loginPage("/conv/common/logon")
.usernameParameter("multipartLogon")
.loginProcessingUrl("/conv/common/logon")
.defaultSuccessUrl("/conv/")
.failureUrl("/conv/common/logon?error=1")
.and()
.logout()
.logoutUrl("/conv/common/logout")
.logoutSuccessUrl("/conv/")
.permitAll()
.and()
.rememberMe()
.key("conv_key")
.rememberMeServices(rememberMeServices())
.useSecureCookie(true);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers("/common/**")
.antMatchers("/favicon.ico");
}
#Bean
public RememberMeServices rememberMeServices() {
TokenBasedRememberMeServices rememberMeServices = new TokenBasedRememberMeServices("conv_key", customUserDetailsService);
rememberMeServices.setCookieName("remember_me_cookie");
rememberMeServices.setParameter("remember_me_checkbox");
rememberMeServices.setTokenValiditySeconds(2678400); //1month
return rememberMeServices;
}
}
my User implements UserDetails and holds the required fields. i
populate those and granted authorities in my CustomUserDetailsService.
how/when/why do i need an
auth.authenticationProvider(authenticationProvider()), if i check db
using logon/password in my service?
I think what you want is:
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder());
}
The userDetailsService method is a shortcut for creating DaoAuthenticationProvider bean! You should not need both, its just two different ways to configure the same thing. The authenticationProvider method is used for more custom setups.
how does spring take the submitted password, encode it and compare to
that stored in the db? how does it know to use the same salt as that
used when the p/w was created/persisted when the user was created?
If you are using BCrypt, the salt is stored in the encoded password value. The salt is the first 22 characters after the third $ (dollar) sign. The matches method is responsible for checking the password.
why does configureGlobal() define both auth.userDetailsService and
auth.authenticationProvider when authenticationProvider() also sets
the userDetailsService?
See above. This is likely why user details are loaded twice.
Update: It is weird that you get password and other details into your UserDetailsService. This should only load user based on username, something like:
User user = userDAO.getUserByUserLogonPassword(userLogon);
The returned User object should contain the encoded (stored) password, not the entered password. Spring Security does the password checking for you. You should not modify the User object in you UserDetailsService.
Wow, ok that's a lot of questions. I'll speak to this one:
"I have no idea how the userDetailsService, AuthenticationManagerBuilder and PasswordEncoder "
The UserDetailsService sets up the User which you can access from Spring. If you want more user information stored in the context on the user, you need to implement your own user and set that up with your custom user details service. e.g.
public class CustomUser extends User implements UserDetails, CredentialsContainer {
private Long id;
private String firstName;
private String lastName;
private String emailAddress;
....
And then, in your custom UserDetailsService, you set the properties:
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
DatabaseEntity databaseUser = this.userRepository.findByUsernameIgnoreCase(username);
customUser customUser = databaseUser.getUserDetails();
customUser.setId(databaseUser.getId());
customUser.setFirstName(databaseUser.getFirstname());
.....
The password encoder, is the mechanism Spring uses to compare the plain-text password to the encrypted hash in your database. You can use the BCryptPasswordEncoder:
#Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
Aside from passing that to your auth provider, you do need to do any more.
Finally, configureGlobal is where you wire things up. You define your user details service Spring is to use and the authentication provider.
In my case, I use a custom authentication provider to limit failed login attempts:
#Component("authenticationProvider")
public class LimitLoginAuthenticationProvider extends DaoAuthenticationProvider {
And then I wire everything up:
#Autowired
#Qualifier("authenticationProvider")
AuthenticationProvider authenticationProvider;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
LimitLoginAuthenticationProvider provider = (LimitLoginAuthenticationProvider)authenticationProvider;
provider.setPasswordEncoder(passwordEncoder);
auth.userDetailsService(customUserDetailsService()).passwordEncoder(passwordEncoder);
auth.authenticationProvider(authenticationProvider);
}

Resources