Spring security with user/password - java

So I followed some guides on spring security with usernames and passwords, however most of them show using "InMemoryUserDetailsManager" which they say should not be used in production:
#Bean
public InMemoryUserDetailsManager userDetailsManager(){
UserDetails admin = User.withDefaultPasswordEncoder()
.username("ADMIN")
.password("123")
.roles("ADMIN").build();
return new InMemoryUserDetailsManager(admin);
}
My questions, so how should a production level version of this be setup? Is it just not using the default password encoder because it is deprecated or should I use an entirely different method of adding and storing users?

You should implement jdbc authentication DaoAuthenticationProvider. Checkout https://www.baeldung.com/spring-security-jdbc-authentication.
Your user details must be stored in permanent storage, not temporary storage. Also, passwords must be encrypted to avoid compromising security. So using permanent storage you can take backup or data and run queries out of it.

You can implement custom user details service instead of using default.
#Service
public class CustomUserDetailsServiceImpl implements UserDetailsService {
private final UserRepository userRepository;
#Autowired
private PasswordEncoder passwordEncoder;
#Autowired
public CustomUserDetailsServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
if(user == null) {
throw ApiExceptionFactory.getApiException(ApiExceptionType.NOT_FOUND, "user");
}
return new org.springframework.security.core.userdetails.User(
user.getUsername(),
user.getPassword(),
user.getEnabled(),
true,
true,
true,
getAuthorities(user));
}
public Boolean isTokenValid(String token) {
try {
Jws<Claims> claimsJws = Jwts.parser().setSigningKey(SECRET.getBytes()).parseClaimsJws(token);
return true;
} catch (SignatureException | MalformedJwtException | UnsupportedJwtException | IllegalArgumentException ex) {
throw ApiExceptionFactory.getApiException(ApiExceptionType.LOGIN_FAILURE, "invalid credentials");
} catch (ExpiredJwtException ex) {
throw ApiExceptionFactory.getApiException(ApiExceptionType.LOGIN_FAILURE, "token expired");
}
}
#Transactional
public Boolean save(User user){
if(StringUtils.isEmpty(user.getUsername())) {
throw ApiExceptionFactory.getApiException(ApiExceptionType.BAD_REQUEST, "username");
}
if(StringUtils.isEmpty(user.getPassword())) {
throw ApiExceptionFactory.getApiException(ApiExceptionType.BAD_REQUEST, "password");
}
if(StringUtils.isEmpty(user.getEmail())) {
throw ApiExceptionFactory.getApiException(ApiExceptionType.BAD_REQUEST, "email");
}
User registeredUser = new User();
registeredUser.setUsername(user.getUsername());
registeredUser.setPassword(passwordEncoder.encode(user.getPassword()));
registeredUser.setEmail(user.getEmail());
registeredUser.setEnabled(true);
registeredUser.setRoles(user.getRoles());
User savedUser = userRepository.save(registeredUser);
Inventory userInventory = inventoryService.saveInventoryForUser(savedUser.getUsername());
return userInventory != null;
}
private Set<GrantedAuthority> getAuthorities(User user){
Set<GrantedAuthority> authorities = new HashSet<>();
for(Role role : user.getRoles()) {
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(role.getName().getRole());
authorities.add(grantedAuthority);
}
return authorities;
}
}
You can save user details into your repository.
#Repository
public interface UserRepository extends BaseRepository<User> {
User findByUsername(String username);
Boolean existsByUsername(String username);
Boolean existsByEmail(String email);
}
Finally add your user details into authentication manager with password encoder.
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserRepository userRepository;
#Override
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.userDetailsService(userDetailsServiceBean()).passwordEncoder(passwordEncoder());
}
#Override
public UserDetailsService userDetailsServiceBean() {
return new CustomUserDetailsServiceImpl(userRepository);
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
For more details check my github repository

Hello Please Code as the following coding
#Bean
public InMemoryUserDetailsManager createUserDetailsManager() {
UserDetails userDetails1 = createNewUser("username1", "dummy");
UserDetails userDetails2 = createNewUser("username2", "dummydummy");
return new InMemoryUserDetailsManager(userDetails1, userDetails2);
}
private UserDetails createNewUser(String username, String password) {
Function<String, String> passwordEncoder
= input -> passwordEncoder().encode(input);
UserDetails userDetails = User.builder()
.passwordEncoder(passwordEncoder)
.username(username)
.password(password)
.roles("USER","ADMIN")
.build();
return userDetails;
}
Hope it help you

Related

Spring Boot 2.7.5 Security 401 Unuathorized

I'm struggling with 401 unuathorized in postman while i try to enter a secured endpoint, I'm passing correct data, but spring security doesn't pass me on, I think I might miss something in DetailsService or somewhere Creating new user
Trying to authorize
Security config
#EnableWebSecurity
#RequiredArgsConstructor
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig {
private final CustomAuthProvider provider;
private final AuthenticationFailureHandler failureHandler;
#Bean
public AuthenticationManager authenticationManager (HttpSecurity http) throws Exception {
AuthenticationManagerBuilder builder = http.getSharedObject(AuthenticationManagerBuilder.class);
builder.authenticationProvider(provider);
return builder.build();
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests(auth -> {
auth.antMatchers(HttpMethod.POST, "/register").permitAll();
auth.antMatchers(HttpMethod.POST, "/login").hasRole("USER");
auth.antMatchers("/user/**").permitAll();
auth.antMatchers("/getusers").hasRole("USER");
auth.antMatchers("/moderator/**").hasRole("MODERATOR");
auth.antMatchers("/admin/**").hasRole("ADMIN");
})
.httpBasic(withDefaults())
.sessionManagement()
.sessionCreationPolicy(STATELESS);
return http.build();
}
Details Service
#Service
#RequiredArgsConstructor // Generates a constructor with required arguments.
public class DetailsService implements UserDetailsService {
private final UserService userService;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
var user = userService.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("Couldn't find \""+ username +"\" username");
}
if (user.getRoles() == null || user.getRoles().isEmpty()) {
throw new RuntimeException("User \"" + username + "\" has no roles");
}
Collection<GrantedAuthority> authorities = user.getRoles().stream()
.map(role -> new SimpleGrantedAuthority(role.getName()))
.collect(Collectors.toList());
return new User(user.getUsername(), user.getPassword(), user.isActive(),
!user.isExpired(), !user.isCredentialsexpired(), !user.isBlocked(), authorities);
}
Method with creating roles
#RequiredArgsConstructor
#Component
public class ApplicationStartRunner implements CommandLineRunner {
private final RoleRepository roleRepository;
#Override
public void run(String... args) throws Exception {
Role roleUser = new Role(1L, "123", "ROLE_USER");
Role roleModerator = new Role(2L, "456", "ROLE_MODERATOR");
Role roleAdmin = new Role(3L, "789", "ROLE_ADMIN");
roleRepository.saveAll(List.of(roleUser, roleAdmin, roleModerator));
}
EDIT: That's my password encoder bean
#Configuration
public class SecurityConfig {
#Bean
PasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
}
And register method
#Override
public UserDTO createUser(RegisterDTO user) {
if (user.equals(userRepository.findByUsername(user.getUsername()))) {
throw new RuntimeException("This nickname is already taken");
}
if (user.equals(userRepository.findByEmail(user.getEmail()))) {
throw new RuntimeException("This email is already taken");
}
// Encoding password
user.setPassword(encoder.encode(user.getPassword()));
// On creating new Account it's going to have USER role
Role role = roleRepository.findByName("ROLE_USER");
String username = user.getUsername();
String password = user.getPassword();
String email = user.getEmail();
User dto = buildUser(username, password, email, role);
userRepository.save(dto);
return UserDTO.builder()
.username(username)
.password(password)
.email(email)
.build();
}
I think this is pretty clear.
That /getusers endpoint is protected and user must have USER role which obviously don't have. By default I think it should be ROLE_USER as spring is using that ROLE_ prefix. But you can specify any role that should be fetched from DB during user authorization.

Bad credentials though valid on a spring /authenticate token generator endpoint

I'm trying to build a route, processed in Spring, to generate a token if the user/password submited in the body are valid. Only in that scenario it responds with the token.
The problem is that I'm Posting the correct username and password, exactly as stored in DB but I keep getting a "bad credentials" error.
This is the controller:
#RequestMapping(value = "/authenticate", method = RequestMethod.POST)
public ResponseEntity<?> createAuthenticationToken(#RequestBody JwtRequest authenticationRequest) throws Exception {
try {
LOGGER.info("Received a request to generate a token for user: "+authenticationRequest.getUsername()+"/"+authenticationRequest.getPassword());
final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
System.out.println(userDetails);
// Logs: org.springframework.security.core.userdetails.User#7ee29d27: Username: thisWasTheGoodUserName; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: Admin
// And also logs the hibernate query:
// select
// employee0_.id as id1_0_,
// employee0_.employee_name as employee_name2_0_,
// employee0_.pwd as pwd3_0_,
// employee0_.user_name as user_nam4_0_
// from
// employees employee0_
// where
// employee0_.user_name=?
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, authenticationRequest.getPassword(), userDetails.getAuthorities());
// This step gets executed
authenticationManager.authenticate(usernamePasswordAuthenticationToken);
// And the same query to the DB is logged, one that I can run on dbeaver manually and get results from:
// select
// employee0_.id as id1_0_,
// employee0_.employee_name as employee_name2_0_,
// employee0_.pwd as pwd3_0_,
// employee0_.user_name as user_nam4_0_
// from
// employees employee0_
// where
// employee0_.user_name=?
// And throws exception in this authenticate
final String token = jwtTokenUtil.generateToken(userDetails);
TokenSucess tokenSuccessResponse = new TokenSucess(true, new JwtResponse(token));
LOGGER.info("Obtained token with success ");
return new ResponseEntity<TokenSucess>(tokenSuccessResponse, HttpStatus.OK);
} catch (Exception e) {
TokenError tokenErrorResp = new TokenError(false, "Error generating token.");
LOGGER.error("Error generating a token. Details: "+ e);
return new ResponseEntity<TokenError>(tokenErrorResp, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
This is the method I'm using in the service:
#Service
public class JwtUserDetailsService implements UserDetailsService {
#Autowired
private EmployeeRepository employeeRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Employee emp = employeeRepository.findByUserName(username);
if (emp == null ) {
throw new UsernameNotFoundException("Employee not found with username: " + username);
}
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add(new SimpleGrantedAuthority("Admin"));
return new org.springframework.security.core.userdetails.User(emp.getUserName(), emp.getPassword(), authorities);
}
}
The security config:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
#Autowired
private UserDetailsService jwtUserDetailsService;
#Autowired
private JwtRequestFilter jwtRequestFilter;
private static final Logger LOGGER = LoggerFactory.getLogger(WebSecurityConfig.class);
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder());
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
try {
httpSecurity.csrf().disable()
// don't authenticate this particular request
.authorizeRequests().antMatchers("/authenticate").permitAll().
// all other requests need to be authenticated
anyRequest().authenticated().and().
exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// Add a filter to validate the tokens with every request
httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
} catch (Exception e) {
LOGGER.error("Error handling http security configs. Details: "+e.getMessage());
}
}
}
And this is the curl example:
curl --location --request POST 'http://127.0.0.1:<port>/authenticate' \
--header 'Content-Type: application/json' \
--data-raw '{
"username": "thisWasTheGoodUserName",
"password": "123454321"
}'
Already tried with diferent scenarios in DB:
1. thisWasTheGoodUserName/123454321
2. thisWasTheGoodUserName/$2y$12$Mj0PRHipe14Wgm5c/GOuO.RyhjhuwRwoQYUnK8LcgsvHzQ4weYHGm (bcrypted 123454321)
Using several "step into" I found that the problem is happening in the following function - /Users/<user>/.m2/repository/org/springframework/security/spring-security-core/5.0.3.RELEASE/spring-security-core-5.0.3.RELEASE.jar!/org/springframework/security/authentication/dao/DaoAuthenticationProvider.class, even though the passwords are se same string:
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
if (authentication.getCredentials() == null) {
this.logger.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
} else {
String presentedPassword = authentication.getCredentials().toString();
// presentedPassword is exactly the same as the one in userDetails.getPassword() but the matcher returns False...
if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
this.logger.debug("Authentication failed: password does not match stored value");
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
}
Any idea what I could be doing wrong and causing the following Exception?
ERROR c.r.t.s.c.JwtAuthenticationController - Error generating a token. Details: org.springframework.security.authentication.BadCredentialsException: Bad credentials
Note: found the problem causing all this: the stored password in the DB was encrypted using an online bcrypt generator... The value does not match the generated bcrypt encoded password even though they are the same string.
I mean you can do a customized authenticate via AuthenticationProvider. You should comment configureGlobal(). The below is sample of WebSecurityConfig.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
// #Autowired
// private UserDetailsService jwtUserDetailsService;
#Autowired
private JwtRequestFilter jwtRequestFilter;
private static final Logger LOGGER = LoggerFactory.getLogger(WebSecurityConfig.class);
//#Autowired
//public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
// auth.userDetailsService(jwtUserDetailsService).passwordEncoder(passwordEncoder());
//}
//#Bean
//public PasswordEncoder passwordEncoder() {
// return new BCryptPasswordEncoder();
//}
#Autowired
private HttpServletRequest request;
#Autowired
private EmployeeRepository employeeRepository;
#Bean
protected AuthenticationProvider authenticationProvider(){
return new AuthenticationProvider(){
#Override
public Authentication authenticate(Authentication auth) throws AuthenticationException {
String username= request.getParameter("username");
String password = request.getParameter("password");
Employee user= employeeRepository.findByUserName(username);
if (emp == null ) {
throw new UsernameNotFoundException("Employee not found with username: "
+ username);
}
// validate pwd by yourself.
// you should use same bcrypt generator to validate when saving pwd in DB
if(!UserUtils.validatePassword(password, user.getPassword())){
throw new BadCredentialsException("wrong password!");
}
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
authorities.add(new SimpleGrantedAuthority("Admin"));
return new UsernamePasswordAuthenticationToken(username,
null, authorities);
}
#Override
public boolean supports(Class<?> authentication) {
return true;
}
};
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
try {
httpSecurity.csrf().disable()
// don't authenticate this particular request
.authorizeRequests().antMatchers("/authenticate").permitAll().
// all other requests need to be authenticated
anyRequest().authenticated().and().
exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// Add a filter to validate the tokens with every request
httpSecurity.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
} catch (Exception e) {
LOGGER.error("Error handling http security configs. Details: "+e.getMessage());
}
}
}

How to turn off Spring Boot Encoder

I need to turn off encoder in my Spring boot project. It seems easy - just delete methods and variables, but then I get the error:
There is no PasswordEncoder mapped for the id "null"
Now, i have hardcoded user login and (encoded) password in my database. Password is stored as many literals and numbers, and in password word it means "123", and i need password to be "123" in database. Here is my code:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
UserDetailsServiceImpl userDetailsService;
#Bean
public BCryptPasswordEncoder 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.csrf().disable();
http.authorizeRequests().
antMatchers("/", "/login", "/logout", "productPage", "contactsPage", "/register", "/add_person").permitAll();
http.authorizeRequests().
antMatchers("/admin","/view_person")
.access("hasAnyRole('ROLE_ADMIN', 'ROLE_SUPER_ADMIN')");
http.authorizeRequests().
and().exceptionHandling().accessDeniedPage("/403");
http.authorizeRequests().and().formLogin()//
.loginProcessingUrl("/j_spring_security_check")
.loginPage("/login")//
.defaultSuccessUrl("/productPage")//
.failureUrl("/login?error=true")//
.usernameParameter("username")//
.passwordParameter("password")
.and().logout().logoutUrl("/logout").logoutSuccessUrl("/logoutSuccessful")
.and().rememberMe().key("secret").tokenValiditySeconds(500000);
}
}
#Service
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
private AppUserDao appUserDAO;
#Autowired
private AppRoleDao appRoleDAO;
#Override
public UserDetails loadUserByUsername(String name) {
AppUser appUser = this.appUserDAO.findUserAccount(name);
if (appUser == null) {
System.out.println("User not found! " + name);
throw new UsernameNotFoundException("User " + name + " was not found in the database");
}
System.out.println("Found User: " + appUser);
List<String> roleNames = this.appRoleDAO.getRoleNames(appUser.getId());
List<GrantedAuthority> grantList = new ArrayList<>();
if (roleNames != null) {
for (String role : roleNames) {
GrantedAuthority authority = new SimpleGrantedAuthority(role);
grantList.add(authority);
}
}
UserDetails userDetails = new User(appUser.getName(),
appUser.getPassword(), grantList);
return userDetails;
}
}
I can give any other classes if needed
Instead of your BCryptPassworencoder, use the following (though that's absolutely not recommended for production). This will not hash your password inputs, which is what you seem to want.
#Bean
public PasswordEncoder passwordEncoder() {
return new NoOpPasswordEncoder.getInstance();
}

Getting User Roles based on Client Id

I have multiple clients registered for my oauth2 auth server. I want to get the user authorities based on the clientId. Let's say USER-1 has authorities ADMIN for CLIENT-1 and for CLIENT-2 the USER-1 has USER authority.
I have tried this issue. But I always get a null request.
final HttpServletRequest request = ((ServletRequestAttributes)
RequestContextHolder.getRequestAttributes()).getRequest();
I have also added a WebListner, but with no luck.
#Configuration
#WebListener
public class MyRequestContextListener extends RequestContextListener {
}
#Service
public class DomainUserDetailsService implements UserDetailsService {
#Autowired
private UserRepository userRepository;
#Autowired
private AuthorityRepository authorityRepository;
#Override
#Transactional
public UserDetails loadUserByUsername(final String email) throws UsernameNotFoundException {
User user = userRepository.findUserByUserName(email);
if (user == null) {
new UsernameNotFoundException("Username not found");
}
String clientId = "?"; // How to get clientId here?
List<String> roles = authorityRepository.getUserAuthorities(email, clientId);
return new DomainUser(email, user.getCredential(), user.getId(), fillUserAuthorities(roles));
}
public Collection<SimpleGrantedAuthority> fillUserAuthorities(Collection<String> roles) {
Collection<SimpleGrantedAuthority> authorties = new ArrayList<SimpleGrantedAuthority>();
for (String role : roles) {
authorties.add(new SimpleGrantedAuthority(role.toUpperCase()));
}
return authorties;
}
}
If I am going in the wrong direction, any suggestions are acceptable.
Currently, I am using a Custom JWT Token Enhancer to achieve the requirement. But I am not sure if this is the right way of doing this. I am not sure why I'm thinking it is the wrong way. But you can achieve this using the below solution.
public class CustomTokenEnhancer implements TokenEnhancer {
#Autowired
private AuthorityRepository authRepository;
#Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
Map<String, Object> additionalInfo = new HashMap<>();
DomainUser authPrincipal = (DomainUser) authentication.getPrincipal();
List<String> clientBasedRoles = authRepository.getUserAuthorities(authPrincipal.getId(),
authentication.getOAuth2Request().getClientId());
additionalInfo.put("authorities", clientBasedRoles);
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return accessToken;
}
}
Then, in your AuthorizationServerConfigurerAdapter.
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Bean
public TokenEnhancer tokenEnhancer() {
return new CustomTokenEnhancer();
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer(), accessTokenConverter()));
endpoints.authenticationManager(this.authenticationManager).accessTokenConverter(accessTokenConverter())
.tokenEnhancer(tokenEnhancerChain);
}
}

loadUserByUsername execute twice using DaoAuthenticationProvider

I am using DaoAuthenticationProvider for authenciation but when I submit form loadUserByUsername is called twice by super.authenticate(authentication) intially it throws BadCredentialsException and then next time it login successfully
This process is working fine if I do not use passwordencoder but when I use it loadUserByUsername method is called twice.
Below is my code:
SecurityConfig
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("authenticationProvider")
AuthenticationProvider authenticationProvider;
#Autowired
#Qualifier("userDetailsService")
UserDetailsService userDetailsService;
#Bean
public PasswordEncoder passwordEncoder() {
PasswordEncoder encoder = new BCryptPasswordEncoder();
return encoder;
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth)
throws Exception {
auth.authenticationProvider(authenticationProvider)
.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/admin/**")
.access("hasRole('ROLE_ADMIN')").and().formLogin()
.loginPage("/login").failureUrl("/login?error")
.usernameParameter("username").passwordParameter("password")
.and().logout().logoutSuccessUrl("/login?logout").and().csrf()
.and().exceptionHandling().accessDeniedPage("/403");
}
}
Authentication class
#Component("authenticationProvider")
public class LimitLoginAuthenticationProvider extends DaoAuthenticationProvider {
#Autowired
#Qualifier("userDetailsService")
#Override
public void setUserDetailsService(UserDetailsService userDetailsService) {
super.setUserDetailsService(userDetailsService);
}
#Override
public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
try {
System.out.println("inside authenticate");
Authentication auth = super.authenticate(authentication);
return auth;
} catch (BadCredentialsException be) {
System.out.println("First call comes here ");
throw be;
} catch (LockedException e) {
throw e;
}
}
}
MyUserdetailsService class implments UserDetailsService
#Service("userDetailsService")
public class MyUserDetailsService implements UserDetailsService {
#Autowired
private UserDao userDao;
/* below method is called twice if I am using passwordencoder,
initially authentication fails and then again immediately
on second call authentication succeed */
#Transactional(readOnly=true)
#Override
public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException {
com.mkyong.users.model.User user = userDao.findByUserName(username);
List<GrantedAuthority> authorities = buildUserAuthority(user.getUserRole());
return buildUserForAuthentication(user, authorities);
}
private User buildUserForAuthentication(com.mkyong.users.model.User user, List<GrantedAuthority> authorities) {
MyUserDetails myUserDetails = new MyUserDetails (user.getUsername(), user.getPassword(), user.isEnabled(), user.isAccountNonExpired(), user.isAccountNonLocked(), user.isCredentialsNonExpired(), user.getEmailId(),authorities);
return myUserDetails;
}
private List<GrantedAuthority> buildUserAuthority(Set<UserRole> userRoles) {
Set<GrantedAuthority> setAuths = new HashSet<GrantedAuthority>();
// Build user's authorities
for (UserRole userRole : userRoles) {
setAuths.add(new SimpleGrantedAuthority(userRole.getRole()));
}
List<GrantedAuthority> Result = new ArrayList<GrantedAuthority>(setAuths);
return Result;
}
}
Can some please help me. I believe there is some change needed in SecurityConfig class but exactly where I am not able to figure out.
Finally after help from java_dude and SergeBallesta I got the resolution for my query.
After a lot of debug I saw that when isPasswordValid method was getting called inside DaoAuthenticationProvider class instead of calling method 1 it was calling method 2 from org.springframework.security.authentication.encoding.PlaintextPasswordEncoder which one is depreciated and on second call it was calling proper isPasswordValid
method 1.
Method 1
public boolean isPasswordValid(String encPass, String rawPass, Object salt) {
checkSalt(salt);
return delegate.matches(rawPass, encPass);
}
Method 2
public boolean isPasswordValid(String encPass, String rawPass, Object salt) {
String pass1 = encPass + "";
// Strict delimiters is false because pass2 never persisted anywhere
// and we want to avoid unnecessary exceptions as a result (the
// authentication will fail as the encodePassword never allows them)
String pass2 = mergePasswordAndSalt(rawPass, salt, false);
if (ignorePasswordCase) {
// Note: per String javadoc to get correct results for Locale insensitive, use English
pass1 = pass1.toLowerCase(Locale.ENGLISH);
pass2 = pass2.toLowerCase(Locale.ENGLISH);
}
return PasswordEncoderUtils.equals(pass1,pass2);
}
To work authentication properly just add below code in your SecurityConfig class in addittion to my current code in question.
#Bean
public DaoAuthenticationProvider authProvider() {
// LimitLoginAuthenticationProvider is my own class which extends DaoAuthenticationProvider
final DaoAuthenticationProvider authProvider = new LimitLoginAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
** and change this method code**
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authProvider())
.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}

Categories