I am having a challenge extracting original user details from the LdapUserDetailsImpl such as the getUsername() returns null
I have the following Java classes
Custom User Class
public class AppUserDetails extends LdapUserDetailsImpl {
public AppUserDetails() {
super();
}
private String mail; //mail
}
Custom User Details Mapper
public class AppUserDetailsContextMapper extends LdapUserDetailsMapper {
public AppUserDetailsContextMapper() {
super();
}
#Override
public AppUserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities) {
UserDetails details = super.mapUserFromContext(ctx, username, authorities);
String mail = ctx.getStringAttribute("mail");
AppUserDetails appUserDetails = new AppUserDetails();
appUserDetails.setMail(mail);
return appUserDetails;
}
}
Web security configuration
#Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
Environment env;
#Bean
public UserDetailsContextMapper userDetailsContextMapper() {
return new AppUserDetailsContextMapper();
}
#Bean
public ActiveDirectoryLdapAuthenticationProvider activeDirectoryLdapAuthenticationProvider() {
ActiveDirectoryLdapAuthenticationProvider provider =
new ActiveDirectoryLdapAuthenticationProvider(
env.getRequiredProperty("spring.ldap.domain"),
env.getRequiredProperty("spring.ldap.urls")
);
provider.setConvertSubErrorCodesToExceptions(true);
provider.setUseAuthenticationRequestCredentials(true);
provider.setUserDetailsContextMapper(userDetailsContextMapper());
return provider;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationProvider(activeDirectoryLdapAuthenticationProvider())
.userDetailsService(userDetailsService());
}
#Bean
public AuthenticationManager authenticationManager() {
return new ProviderManager(
Collections
.singletonList(activeDirectoryLdapAuthenticationProvider())
);
}
}
However I am having a serious challenge trying to getting Custom User Details in the controller:
#Controller
public class HomeController {
#GetMapping(value = {"/home"})
public String home(Model model) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
System.out.println(authentication.getPrincipal());
AppUserDetails appUserDetails = (AppUserDetails) authentication.getPrincipal();
System.out.println(appUserDetails);
return "home";
}
}
I am getting a NullPointer exception if I try to any property from the LdapUserDetailsImpl class. However, I am accurately getting all the properties from the AppUserDetails - which extends the LdapUserDetailsImpl class.
Where might I be missing it?
Related
I have the following success handler that is part of the WebSecurityConfig.java file:
.successHandler(new AuthenticationSuccessHandler() {
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
CustomOAuth2User oauthUser = (CustomOAuth2User) authentication.getPrincipal();
userAuthService.processOAuthPostLogin(oauthUser.getEmail());
response.sendRedirect("/list");
}
})
At the line where casting is supposed to take place, it gives the error:
java.lang.ClassCastException: class
org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser
cannot be cast to class
com.myapp.myapp.mvc.business.domain.user.CustomOAuth2User
How can I go about solving this?
The CustomOAuth2User class is as follows:
public class CustomOAuth2User implements OAuth2User {
private OAuth2User oauth2User;
public CustomOAuth2User(OAuth2User oauth2User) {
this.oauth2User = oauth2User;
}
#Override
public Map<String, Object> getAttributes() {
return oauth2User.getAttributes();
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return oauth2User.getAuthorities();
}
#Override
public String getName() {
return oauth2User.getAttribute("name");
}
public String getEmail() {
return oauth2User.<String>getAttribute("email");
}
}
I think your code are from codejava.net right? I also got the same issues and I fixed with update on the onAuthenticationSuccess function. I change the following code
CustomOAuth2User oauthUser = (CustomOAuth2User) authentication.getPrincipal();
userAuthService.processOAuthPostLogin(oauthUser.getEmail());
with this code
DefaultOidcUser oauthUser = (DefaultOidcUser) authentication.getPrincipal();
String email = oauthUser.getAttribute("email");
userDetailsService.processOAuthPostLogin(email);
And it's working now :)
You are getting this error because you have not defined the scopes explicitly in your app and by default scopes also include openId.
If you define your scopes in the application.properties like below you can cast the principal to CustomOAuth2User .
spring.security.oauth2.client.registration.google.scope=email,profile
If you wish to include openId in your scopes, then you need to cast your principal to DefaultOidcUser an you can create a CustomOidcUserService service like below
#Service
public class CustomOidcUserService extends OidcUserService {
#Autowired private UserRepository userRepository;
#Autowired private RoleRepository roleRepository;
#Override
public OidcUser loadUser(OidcUserRequest userRequest) throws OAuth2AuthenticationException {
final OidcUser oidcUser = super.loadUser(userRequest);
return oidcUser;
}
Its not clear for me how to glue my CustomPasswordEncoder to the authentication process of spring boot. I define in a configuration that spring boot should use my CustomAuthenticationProvider with my UserDetailsService and my CustomPasswordEncoder
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsServiceImpl userDetailsService;
#Autowired
private CustomAuthenticationProvider customAuthenticationProvider;
#Autowired
protected void configureGlobal(AuthenticationManagerBuilder builder) throws Exception {
builder.authenticationProvider(customAuthenticationProvider)
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder(){
PasswordEncoder encoder = new CustomPasswordEncoder();
return encoder;
}
}
My CustomPasswordEncoder will encode to a md5 value (I know its unsecure, but its a legacy database)
#Component
public class CustomPasswordEncoder implements PasswordEncoder{
#Override
public String encode(CharSequence rawPassword) {
return DigestUtils.md5DigestAsHex(rawPassword.toString().getBytes());
}
#Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
return rawPassword.toString().equals(encodedPassword);
}
}
In the CustomAuthtenticationProvider the authentication check will be done. The delivered password will be encoded by using the passwordEncoder.encode() The user will be fetched from the database, then I am using the passwordEncoder again do a match. If the match is successfull then the authentication object will be generated.
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Autowired
private UserServiceImpl userService;
#Autowired
private CustomPasswordEncoder passwordEncoder;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
System.out.println("authentication = [" + authentication + "]");
String name = authentication.getName();
Object credentials = authentication.getCredentials();
String password = credentials.toString();
//why is this necessary isnt it called automatically?
String passwordEncoded = passwordEncoder.encode((CharSequence) credentials);
Optional<UserEntity> userOptional = userService.findByUsername(name);
if (userOptional.isPresent() && passwordEncoder.matches(passwordEncoded, userOptional.get().getPassword())) {
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
grantedAuthorities.add(new SimpleGrantedAuthority(userOptional.get().getRoles().toString()));
Authentication auth = new
UsernamePasswordAuthenticationToken(name, password, grantedAuthorities);
return auth;
}
else{
throw new BadCredentialsException("Authentication failed for " + name);
}
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
Is this the correct approach? I thought the CustomPasswordEncoder will be used "automatically" or ist that only the case if you use one of the provided authenticationProviders like jdbcAuthenticationProvider. Maybe someone can explain the order of events of the authentication process. I did some research in the net but still I cannot understand this in detail.
First as you can see from the matches method it validates the raw password (thus as entered by the user) with the encoded password. So the code for encoding belongs in the matches method instead of what you have now.
public class CustomPasswordEncoder implements PasswordEncoder{
#Override
public String encode(CharSequence rawPassword) {
return DigestUtils.md5DigestAsHex(rawPassword.toString().getBytes());
}
#Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
String rawEncoded = encode(rawPassword);
return Objects.equals(rawEncoded, encodedPassword);
}
}
Now you can remove the encoding line/step whatever from your code.
However you don't really need a custom AuthenticationProvider as that is generally only needed if you add another authentication mechanism like LDAP or OAuth.
What you need is an adapter for your UserService to a UserDetailsService and use that. I assume that the UserDetailsServiceImpl does exactly that. If not you can use something like the code below.
public class UserDetailsServiceImpl implements UserDetailsService {
private final UserService delegate;
public UserDetailsServiceAdapter(UserService delegate) {
this.delegate=delegate;
}
public UserDetails loadUserByUsername(String username) {
reutrn userService.findByUsername(name)
.map(this::toUserDetails).orElseThrow(() -> new UsernameNotFoundException("Unknown user " + username);
}
private UserDetails toUserDetails(User user) {
Set<GrantedAuthority> authorities = new HashSet<>();
user.getRoles().forEach(r -> authorities.add(new SimpleGrantedAuthority(r));
return new UserDetails(user.getUsername(), user.getPassword(), authorities);
}
}
Now you can use your PasswordEncoder and this adapter in the configuration and you don't need your custom AuthenticationProvider.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsServiceImpl userDetailsService;
#Autowired
protected void configureGlobal(AuthenticationManagerBuilder builder) throws Exception {
builder.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder(){
PasswordEncoder encoder = new CustomPasswordEncoder();
return encoder;
}
}
I am configuring Spring Security for the first time, but it seems Spring can't see my client's raw password as I am getting this error.
o.s.s.c.bcrypt.BCryptPasswordEncoder : Empty encoded password
It seems like an obvious problem, but permit me, I just can't figure it our after many attempts.
My SecurityConfig class is ...
#EnableWebSecurity
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
CustomUserDetailsService userDetailsService;
#Autowired
BCryptPasswordEncoder bCryptPasswordEncoder;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
}
}
This is my UserServiceDetails Service.
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private UserRepository repo;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<Owner> optionalUser = repo.findByUsername(username);
optionalUser
.orElseThrow(() -> new UsernameNotFoundException("Username not
found"));
return optionalUser
.map(CustomUserDetails::new).get();
}
}
I do also have the following bean configured
public class WebMvcConfig implements WebMvcConfigurer {
#Bean
public BCryptPasswordEncoder passwordEncoder() {
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
return bCryptPasswordEncoder;
}
}
This is my userService.
public class CustomUserDetails extends Owner implements UserDetails {
public CustomUserDetails(final Owner owner) {
super();
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return getRoles().stream()
.map(role -> new SimpleGrantedAuthority("ROLE_"+getRoles()))
.collect(Collectors.toList());
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
}
I surely must be missing something, but I can't seem to figure it out. From HttpRequest, I know that the password is being posted to the System, as I logged.
I found out that the OptionalUser is not mapping correctly into the UserDetail object, thereby returning a new and empty UserDetail object. the following code is wrong.
return optionalUser
.map(CustomUserDetails::new).get();
}
So I my new UserDetailsService class is ...
#Service
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private UserRepository repo;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
{
Optional<Owner> optionalUser = repo.findByUsername(username);
Owner user = optionalUser.get();
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), getAuthorities(user));
}
public Collection<? extends GrantedAuthority> getAuthorities(Owner user) {
return user.getRoles().stream()
.map(role -> new SimpleGrantedAuthority("ROLE_"+user.getRoles()))
.collect(Collectors.toList());
}
}
i am a newbie to spring
was not able to implement login to the application, actually could not figure out where its wrong.
followed a youtube video to do all this.
Help would be very much appreciated.
When i try to log in the application wont allow to log in.
console logs shows querys are being executed but cant log into the system.
also the password are saved in plain ASCII.
WebSecurityConfig class
#Configuration
#ComponentScan(basePackageClasses = CustomUserDetailsService.class)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AccessDeniedHandler accessDeniedHandler;
#Autowired
private UserDetailsService userDetailsService;
#Autowired
public void configAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
// roles admin allow to access /actuator/**
// roles user allow to access /Application/**
// custom 403 access denied handler
#Override
protected void configure(HttpSecurity http) throws Exception {
// some antMatchers permit all
}
}
customUserDetailsService class
#Service("customUserDetailsService")
public class CustomUserDetailsService implements UserDetailsService{
private final UserRepo userRepo;
#Autowired
public CustomUserDetailsService(UserRepo userRepo) {
this.userRepo = userRepo;
}
#Override
// userId is reffered as username
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
User user=userRepo.findByUsername(userName);
if(null == user){
System.out.println("\n\n\n No user present with username: "+userName);
throw new UsernameNotFoundException("No user present with username: "+userName);
}else{
CustomUserDetails c =new CustomUserDetails(user);
//System.out.println(c.getAuthorities());
return c;
}
}
}
CustomUserDetails class
public class CustomUserDetails extends User implements UserDetails{
private static final long serialVersionUID = 1L;
public CustomUserDetails(User user){
super(user);
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<UserRole> roles = super.getUserRole();
List<String> userRoles= new ArrayList<String>();
for(UserRole r : roles) {
userRoles.add(r.getRole().toString());
}
String strRoles=StringUtils.collectionToCommaDelimitedString(userRoles);
return AuthorityUtils.commaSeparatedStringToAuthorityList(strRoles);
}
#Override
public boolean isAccountNonExpired() {
return super.isAccountNonExpired();
}
#Override
public boolean isAccountNonLocked() {
return super.isAccountNonLocked();
}
#Override
public boolean isCredentialsNonExpired() {
return super.isCredentialsNonExpired();
}
#Override
public boolean isEnabled() {
return super.isEnabled();
}
#Override
public String getUsername() {
return super.getUsername();
}
#Override
public String getPassword() {
return super.getPassword();
}
}
I store information about users in seperated tables owners,employees,users i am trying to use java configuration in spring security.
I have created three diferent authentication providers for each user type, but only the Users Provider is being triggered. I have read the spring security docs and the only way to do this seems to be is create class with multiple embedded classes extended from WebSecurityConfigurerAdapter but i don't want to do it this way because it requires a lot of duplicating code, is there any other way
I tryed to use the simple userDetailService inside which i send request to all tables in databese but still there is not results, only one query is buing executed and nothing, the only responce i get is:
2016-02-09 23:06:25.976 DEBUG 8780 --- [nio-8080-exec-1]
.s.a.DefaultAuthenticationEventPublisher : No event was found for the
exception
org.springframework.security.authentication.InternalAuthenticationServiceException
2016-02-09 23:06:25.976 DEBUG 8780 --- [nio-8080-exec-1]
o.s.s.w.a.www.BasicAuthenticationFilter : Authentication request for
failed:
org.springframework.security.authentication.InternalAuthenticationServiceException:
No entity found for query; nested exception is
javax.persistence.NoResultException: No entity found for query
But i never throw any exception!! And the most strange is that i can see in the debugger how the execution rapidly stops right after em.createQuery(..).getSingleResult().. and that's it, nothing more! There is no return statement no exception nothing, wtf!!
This is part of my current configuration:
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.authenticationProvider(createAuthenticationProvider(employeeDetailService()))
.authenticationProvider(createAuthenticationProvider(ownerDetailsService()))
.authenticationProvider(createAuthenticationProvider(userDetailsService()));
}
#Bean
public OwnerDetailsService ownerDetailsService() {
return new OwnerDetailsService();
}
#Bean
public EmployeeDetailServiceImpl employeeDetailService() {
return new EmployeeDetailServiceImpl();
}
#Bean
public UserDetailsServiceImpl userDetailsService() {
return new UserDetailsServiceImpl();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(6);
}
#Bean
public AuthenticationSuccessHandler authenticationSuccessHandler() {
return new MySimpleUrlAuthenticationSuccessHendler();
}
private AuthenticationProvider createAuthenticationProvider(UserDetailsService service) {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(service);
provider.setPasswordEncoder(passwordEncoder());
provider.setHideUserNotFoundExceptions(true);
return provider;
}
User detail services:
#Service
public abstract class CustomUserDetailService implements UserDetailsService{
#Autowired
IDBBean dao;
protected CustomUserDetails getUser(GetUserByNameFunction function, String name) {
return createUser(function.get(name));
}
protected CustomUserDetails createUser(Authenticational user) {
return new CustomUserDetails(user, getAuthorities(user.getAuthority()));
}
protected List<GrantedAuthority> getAuthorities(String authority) {
return Collections.singletonList(new SimpleGrantedAuthority(authority));
}
}
Implementations
public class EmployeeDetailServiceImpl extends CustomUserDetailService {
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
return super.getUser(dao::getEmployeeByEmail, email);
}
}
public class OwnerDetailsService extends CustomUserDetailService {
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
return super.getUser(dao::getOwnerByEmail, email);
}
}
public class UserDetailsServiceImpl extends CustomUserDetailService {
#Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
return super.getUser(dao::getUserByEmail, userName);
}
}
Custom user details:
private Long id;
private String userEmail;
public CustomUserDetails(Authenticational user,
Collection<? extends GrantedAuthority> authorities) {
super(
user.getName(),
user.getPassword().toLowerCase(),
user.isEnabled(),
true,
true,
true,
authorities);
upadateValues(user);
}
private void upadateValues(Authenticational user) {
this.id = user.getId();
this.userEmail = user.getEmail();
}
Just to clarify something from the other answer:
Your authentication providers are stored in a list inside ProviderManager that iterates your authentication request through them. If your authentication provider throws AuthenticationException (BadCredentialsException extends AuthenticationException), then the ProviderManager will try another provider. If you set the hideUserNotFoundExceptions property, then it will also wrap and ignore UsernameNotFoundException and try another provider in this case too.
If I were you I would start by placing a debugging point inside ProviderManager's authenticate method. From there you can find out why the other authentication providers are not being called for their authenticate method.
Also I would think about having only one authentication provider with one UserDetailsService. It seems to me that you are doing a lot of complex not really needed operations like passing function to your abstract implementation when all you could do would be to have one UserDetailsService that would ask all your DAOs for a user. Which is basically what you're trying to accomplish but minus 2 authentication providers, minus 1 abstract class and minus 2 UserDetailsService implementations.
Spring Security will not try other authentication providers if a provider throws an AccountStatusException or if a UserDetailsService throws a UserNameNotFoundException or any other AuthenticationException
If you want other providers to be tried, then the loadUserByUserName methods of your UserDetailsServiceImpl and OwnerDetailsService should not throw the UserNameNotFound exception.
You should decide if you either want to return a dummy anonymous UserDetails object that will be used exclusively for fallback or some other mechanism to not throw the exception when a user is not available in your UserDetailsService implementation
this is an example I'v done to provide multi-auth for my application which have two diffirent user : admin and client .
ps: the admin and the client are two diffirent model.
public class CustomUserDetails implements UserDetails{
private Admin admin;
private Client client;
public CustomUserDetails(Admin admin) {
this.admin = admin;
}
public CustomUserDetails(Client client) {
this.client = client;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
#Override
public String getPassword() {
if((admin != null)&&(client==null)) {
return admin.getPassword();
}
else {
return client.getPassword();
}
}
#Override
public String getUsername() {
if((admin != null)&&(client==null)) {
return admin.getEmail();
}
else {
return client.getMail();
}
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
}
the CustomUserDetailsService class :
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
AdminRepository adminRepo;
#Autowired
ClientRepository clientRepo;
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
Admin admin = adminRepo.findByEmail(email);
Client client = clientRepo.findByEmail(email);
if((admin == null)&&(client==null)) {
throw new UsernameNotFoundException("User not found");
}
else if((admin != null)&&(client==null)) {
return new CustomUserDetails(admin);
}
else {
return new CustomUserDetails(client);
}
}
}