Spring-Boot with CustomAuthenticationProvider and CustomPasswordEncoder - java

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;
}
}

Related

How Authenticate() method get called in AuthenticationProvider class in Spring security?

In spring security, I created my own customAuthenticationprovider. when i call auth.authenticationProvider(customAuthenticationProvider) it should call the authenticate() present inside the customAuthenticationprovider class right. But there is no such flow i am able to figure out where it is calling the authenticate method.
All i want to know how authenticate() method get called. I know it is authenticationProvider which is making call to authenticate but how and where is the code?
#Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(customAuthenticationProvider);
}
My
#Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
#Autowired
private UserDetailsService userDetailsService;
#Autowired
private PasswordEncoder passwordEncoder;
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String username = authentication.getName();
String password = String.valueOf(authentication.getCredentials());
UserDetails ud = userDetailsService.loadUserByUsername(username); // our own userDetailService is being used.
if (ud != null) {
if (passwordEncoder.matches(ud.getPassword(), password)) { // our own passwordEncoder is being used.
var a = new UsernamePasswordAuthenticationToken(username, password, ud.getAuthorities());
return a;
}
}
throw new BadCredentialsException("Error!");
}
#Override
public boolean supports(Class<?> authType) {
return UsernamePasswordAuthenticationToken.class.equals(authType);
}
}

Spring Security - Active Directory Get Custom User details

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?

Spring boot bcrypt.BCryptPasswordEncoder and Authentication issue

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());
}
}

Redirect AfterLogin With Facebook

I using ConnectionSignUp and SignInAdapter to do login function for website. I also use spring security.But I do not know how to finish loading the current page after logging in.
After login, the website will reload the form page : localhost:9090/#=
ConnectionSignUp.java
public class FacebookSignInAdapter implements SignInAdapter {
#Autowired
UsersRepository usersService;
#Override
public String signIn(String localUserId, Connection<?> connection, NativeWebRequest request) {
SecurityContextHolder.getContext()
.setAuthentication(new UsernamePasswordAuthenticationToken(connection.getKey(),
null, Arrays.asList(new SimpleGrantedAuthority("ROLE_FACEBOOK"))));
return null;
}}
FacebookConnectionSignup.java
public class FacebookConnectionSignup implements ConnectionSignUp {
#Autowired
UsersService usersService;
#Autowired
RolesService rolesService;
#Override
public String execute(Connection<?> connection) {
Users user = null;
try {
user = usersService.findByUserName(connection.getKey().toString());
if(user == null ) {
user = new Users();
user.setUserName(connection.getKey().toString());
user.setPassword(randomAlphabetic(8));
user.setEmail(connection.getKey()+"#gmail.com");
user.setFirstName(connection.getDisplayName());
user.setAvatar(connection.getImageUrl());
user.setStatus("active");
user.setCreatedDate(new Date());
user.setLoggedInDate(new Date());
user.setIsOnline((byte) 1);
HashSet<Roles> roleses = new HashSet<>();
roleses.add(rolesService.findByName("ROLE_FACEBOOK"));
user.setRoleses(roleses);
usersService.saveorupdate(user);
}
} catch (Exception e) {
System.out.println(e.getMessage());
}
return user.getUserName();
}}
WebSecurityConfig.java
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AjaxAuthenticationFailureHandler ajaxAuthenticationFailureHandler;
#Autowired
private AjaxAuthenticationSuccessHandler ajaxAuthenticationSuccessHandler;
#Autowired
private ConnectionFactoryLocator connectionFactoryLocator;
#Autowired
private UsersConnectionRepository usersConnectionRepository;
#Autowired
private FacebookConnectionSignup facebookConnectionSignup;
#Autowired
private UserDetailsService userDetailsService;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/home", "/").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.and().formLogin().loginPage("/403").loginProcessingUrl("/login").usernameParameter("userName").passwordParameter("password")
.failureHandler(ajaxAuthenticationFailureHandler).successHandler(ajaxAuthenticationSuccessHandler)
.and().logout().logoutSuccessUrl("/")
.and().rememberMe().and()
.exceptionHandling().accessDeniedPage("/403");
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
}
#Bean
public ProviderSignInController providerSignInController() {
((InMemoryUsersConnectionRepository) usersConnectionRepository).setConnectionSignUp(facebookConnectionSignup);
return new ProviderSignInController(connectionFactoryLocator, usersConnectionRepository,
new FacebookSignInAdapter());
}}
Now I want to finish loading the current page after logging in.
Hope everyone will help.
Thank you very much.

Can Spring Security accept multiple passwords for the same user?

I have Spring Security working within my application to authenticate a user with one password. I'm trying to meet a requirement that an override password will also authenticate that same user.
How can I do this with Spring Security?
It is possible, you will have to implement your own AuthenticationProvider possibly by extending the existing DaoAuthenticationProvider (see additionalAuthenticationChecks() in there).
Also the user is only associated with a single password by default (UserDetails.getPassword()), so you will need to have an extension of that class holding multiple passwords, and a corresponding implementation of UserDetailsService that knows how to load the user along with its passwords.
It is easy to do by providing multiple 'AuthenticationProvider' with 'UserDetailsService'.
private DaoAuthenticationProvider userAuthProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setHideUserNotFoundExceptions(false);
provider.setPasswordEncoder(passwordEncoder);
provider.setUserDetailsService(userDetailsService);
return provider;
}
private DaoAuthenticationProvider superVisorAuthProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setHideUserNotFoundExceptions(false);
provider.setUserDetailsService(supervisorDetailService);
return provider;
}
then
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(userAuthProvider());
auth.authenticationProvider(superVisorAuthProvider());
}
As has been mentioned - you can overwrite 'additionalAuthenticationChecks'
Hope this helps somebody.
#Slf4j
#Service
class FlexibleAuthenticationProvider extends DaoAuthenticationProvider implements AuthenticationProvider {
#Autowired
UserDetailsService userDetailsService
#Autowired
PasswordEncoder passwordEncoder
#PostConstruct
def init() {
super.setPasswordEncoder(passwordEncoder)
super.setUserDetailsService(userDetailsService)
}
#Override
protected void additionalAuthenticationChecks(
UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
try {
super.additionalAuthenticationChecks(userDetails, authentication)
} catch (AuthenticationException e) {
log.error('Unable to authenticate with regular credentials')
try {
def mutableUserDetails = new MutableUser(userDetails)
mutableUserDetails.password = 'alternatepassword'
return super.additionalAuthenticationChecks(mutableUserDetails, authentication)
} catch (AuthenticationException err) {
log.error('Token based authentication failed')
}
throw e
}
}
static class MutableUser implements UserDetails {
private String password
private final UserDetails delegate
MutableUser(UserDetails user) {
this.delegate = user
this.password = user.password
}
String getPassword() {
return password
}
void setPassword(String password) {
this.password = password
}
....
}
}
#Configuration
class AuthWebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
FlexibleAuthenticationProvider flexibleAuthenticationProvider
....
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(flexibleAuthenticationProvider)
}
....
}

Categories

Resources