Spring Boot 2.7.5 Security 401 Unuathorized - java

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.

Related

Spring security with user/password

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

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

How to get logged user id in REST API

I have built a Java application with the REST API convention. I working on endpoint which returns objects only if object is connected with user by common id in database(ManyToOne annotation). In order to achieve that i need current logged user id for comapring it with object's user id. If Ids are the same, endpoint returns data. I know solutions as "Principal" or "Authentication" classes but they provide everything except of "id". I used spring security http basic for authentication.
My authentication classes:
#Component
public class CustomAuthenticator implements AuthenticationProvider {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
#Autowired
public CustomAuthenticator(UserRepository userRepository, #Lazy PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
String login = authentication.getName();
String password = authentication.getCredentials().toString();
User user = userRepository.findByLogin(login).orElseThrow(() -> new EntityNotFoundException("User not found"));
if (!passwordEncoder.matches(password, user.getPassword())) {
throw new BadCredentialsException("Bad credentials");
}
return new UsernamePasswordAuthenticationToken(login, password, convertAuthorities(user.getRoles()));
}
private Set<GrantedAuthority> convertAuthorities(Set<UserRole> userRoles) {
Set<GrantedAuthority> authorities = new HashSet<>();
for (UserRole ur : userRoles) {
authorities.add(new SimpleGrantedAuthority(ur.getRole().toString()));
}
return authorities;
}
#Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
}
SECURITY CONFIG CLASS:
#EnableGlobalMethodSecurity(prePostEnabled = true)
#EnableWebSecurity
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final CustomAuthenticator customAuthenticator;
public SecurityConfig(CustomAuthenticator customAuthenticator) {
this.customAuthenticator = customAuthenticator;
}
#Bean
public PasswordEncoder passwordEncoder() {
PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
return passwordEncoder;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/api").permitAll()
.antMatchers("/api/register").permitAll()
//TODO everybody now has access to database, change it later
.antMatchers("/h2-console/**").permitAll()
.anyRequest().authenticated()
.and()
.httpBasic();
http
.csrf().disable()
.headers().frameOptions().disable();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthenticator);
}
}
Does someone know how to resolve that problem ?
You can use UserDetails class and set id for the username field, this class provides by spring security.
If you don't want that solution, you can create a Subclass extend UserDetails class and decide an id field. When receiving the request, parse principal to UserDetails or subclass extends UserDetails to get the id
Ex:
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
UserDetails userPrincipal = (UserDetails)authentication.getPrincipal();

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