Unable to figureout on my own "[ UserDetailsService returned null, which is an interface contract violation ]"? - spring-boot

I am new to spring boot and spring security and not able to understand the following error on my own.
my spring boot application simply contains two URLs one which is accessed by anyone i.e, only by whose name password is saved in database and another which can only ADMIN can access(to add user and there roll in MySql database).
But when i am passing username and password it saying:-
org.springframework.security.authentication.InternalAuthenticationServiceException: UserDetailsService returned null, which is an interface contract violation
I am posting all necessary class below:-
CustomUserDetailService:-
#Service
public class CustomUserDetailService implements UserDetailsService {
#Autowired
private UserRepository repository;
#Override
public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
User user= repository.findByname(name);
CustomUserDetail userDetail=null;
if(user!=null){
CustomUserDetail userDetails=new CustomUserDetail();
userDetails.setUser(user);
}else{
throw new UsernameNotFoundException("User not exist with name :" +name);
}
return null;
}
}
CustomUserDetail
public class CustomUserDetail implements UserDetails {
private User user;
/*Getter and Setter*/
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
/*Overriden methods from userDetail*/
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return user.getRoles().stream().map(role -> new SimpleGrantedAuthority("Role_"+role))
.collect(Collectors.toList());
}
#Override
public String getPassword() {
return user.getPassword();
}
#Override
public String getUsername() {
return user.getName();
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
}
BasicConfig:-
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class BasicConfig extends WebSecurityConfigurerAdapter{
#Autowired
private UserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
auth.userDetailsService(userDetailsService).passwordEncoder(encodePWD());
}
#Override
protected void configure(HttpSecurity http) throws Exception
{
http.csrf().disable();
http
.authorizeRequests()
.antMatchers("/user/").permitAll()
.and()
.authorizeRequests()
.antMatchers("/MiniApi/**").hasAnyRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin()
.permitAll();
}
#Bean
public BCryptPasswordEncoder encodePWD()
{
return new BCryptPasswordEncoder();
}
}
Controller Classes:-
I am not making two #Restcontroller classes and only single #RequestMapping() is acting as base URL
AdminController:-
#RestController
#RequestMapping("/MiniApi")
public class Admincontroller
{
#Autowired
private UserRepository userRepository;
#Autowired
private BCryptPasswordEncoder passwordEncoder;
#PreAuthorize("hasAnyRole('ADMIN')")
#PostMapping("/admin/add")
public String addUser(#RequestBody User user)
{
String pwd = user.getPassword();
String encryptPwd = passwordEncoder.encode(pwd);
user.setPassword(encryptPwd);
userRepository.save(user);
return "User added successfully...";
}
}
AnyOne:-
public class AnyOne {
#GetMapping("/anyone")
public String Anyone()
{
return "processing......";
}
}
Change what i made:-
If i am removing return statement from CustomUserDetailService i am getting return statement missing error and then i added return userDetails;
it gives me :-
First it askes me for username and password i provided it and then this
HTTP Status 403 – Forbidden

You are returning null instead of userDetails
#Service
public class CustomUserDetailService implements UserDetailsService {
#Autowired
private UserRepository repository;
#Override
public UserDetails loadUserByUsername(String name) throws
UsernameNotFoundException {
User user= repository.findByname(name);
CustomUserDetail userDetail=null;
if(user != null){
CustomUserDetail userDetails=new CustomUserDetail();
userDetails.setUser(user);
return userDetails;
}
throw new
UsernameNotFoundException("User not exist with name :" +name);
}
}

Related

why iam i getting 403 - forbidden

I was working on Spring Boot Security project and i did the authentication on some endpoints to prevent from access from USERS and granted the authority to ADMIN when ever i hit this URL : admin/users i got the login page i put the credentials and instead of returning the actual content its return type=Forbidden, status=403
here is my SecurityConfig
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// inject spring user details interface
#Autowired
private UserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(encoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/api/**" , "/").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and().formLogin();
}
#Bean
public BCryptPasswordEncoder encoder(){
return new BCryptPasswordEncoder();
}
}
admin users controller
#RestController
#RequestMapping("admin/users")
public class UserController {
//
#Autowired
private UserRepository userRepository;
#Autowired
private BCryptPasswordEncoder passwordEncoder;
// get all users
#PreAuthorize("hasAnyRole('ADMIN')")
#GetMapping
public List<User> getAllUsers(){
return userRepository.findAll();
}
// test
#PreAuthorize("hasAnyRole('ADMIN')")
#GetMapping("/test")
public String message() {
return "secured end point";
}
//create new users
#PreAuthorize("hasAnyRole('ADMIN')")
#PostMapping
public User registerUser(#RequestBody User user){
String password = user.getPassword();
String encodedPassword = passwordEncoder.encode(password);
user.setPassword(encodedPassword);
return userRepository.save(user);
}
}
userDetail
public class CustomUserDetails implements UserDetails {
/**
*
*/
private static final long serialVersionUID = 1256711395932122675L;
private User user;
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return user.getRoles().stream().map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.collect(Collectors.toList());
}
#Override
public String getPassword() {
return user.getPassword();
}
#Override
public String getUsername() {
return user.getUsername();
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
// TODO Auto-generated method stub
return true;
}
#Override
public boolean isCredentialsNonExpired() {
// TODO Auto-generated method stub
return true;
}
#Override
public boolean isEnabled() {
// TODO Auto-generated method stub
return true;
}
}
userDetailsService
[![#Service
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
CustomUserDetail userDetail = null;
if(user != null) {
userDetail = new CustomUserDetail();
userDetail.setUser(user);
}
else {
throw new UsernameNotFoundException("user not exist with username " + username);
}
System.out.println("user details" + userDetail);
return userDetail;
}
}][2]][2]
I fixed the issue by simply correcting the getAuthorities() I removed "ROLE_" in simpeGrantedAuthority() , extracted the return into a variable in userDetail.java and used hasAuthority() instead of hasRole()
the code which was getting 403 - forbidden
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return user.getRoles().stream().map(role -> new SimpleGrantedAuthority("ROLE_" + role))
.collect(Collectors.toList());
}
**the solution **
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<SimpleGrantedAuthority> authorities = user.getRoles().stream().map(role ->
new SimpleGrantedAuthority(role.getRole()))
.collect(Collectors.toList());
return authorities;
}

Spring Security configuration not authorizing properly

Here is the config snippet:
#Configuration
#EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Bean
#Override
protected UserDetailsService userDetailsService() {
return super.userDetailsService();
}
#Autowired
private UserDetailsService userDetailsService;
#Bean
AuthenticationProvider authenticationProvider(){
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(new BCryptPasswordEncoder());
return provider;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/register", "/registration")
.permitAll()
.antMatchers("/home")
.hasAuthority("USER")
.antMatchers("/admin")
.hasAuthority("ADMIN")
.anyRequest()
.authenticated()
.and()
.httpBasic();
}
}
Here is another relevant class named CustomUserDetails:
#Component
public class CustomUserDetails implements UserDetails {
private User user;
public CustomUserDetails(User user) {
super();
this.user = user;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.singleton(new SimpleGrantedAuthority("USER"));
//return Collections.emptyList();
}
#Override
public String getPassword() {
return user.getPassword();
}
#Override
public String getUsername() {
return user.getEmail();
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
}
Here is the #service:
#Service
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
User user = userRepository.findByEmail(email);
if(user ==null) {
throw new UsernameNotFoundException("User Not Found");
}
return new CustomUserDetails(user);
}
}
The URL /home must be accessible to only USERS, but when I am sending a GET request to it, it is accessible and doesn't require any authorization or even authentication. I think I am overlooking something please help.

Redirecting to white level error page after authentication

I'm implementing Spring Security in a project. we are using hibernate as ORM. The problem I'm getting is the page is redirecting to white level error page saying Access Is Denied though we are entering the right credentials.
The roles and users are stored in the db. I have craeted a CustomUserDetails class CustomUserDetails class too. But didn't getting any reason why it's not wrking.
CustomUserDetails
public class CustomUserDetals implements UserDetails {
#Autowired
private User user;
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return user.getRole().stream().map(role-> new SimpleGrantedAuthority("ROLE_"+role))
.collect(Collectors.toList());
}
#Override
public String getPassword() {
// TODO Auto-generated method stub
return user.getPassword();
}
#Override
public String getUsername() {
// TODO Auto-generated method stub
return user.getUserName();
}
#Override
public boolean isAccountNonExpired() {
// TODO Auto-generated method stub
return true;
}
#Override
public boolean isAccountNonLocked() {
// TODO Auto-generated method stub
return true;
}
#Override
public boolean isCredentialsNonExpired() {
// TODO Auto-generated method stub
return true;
}
#Override
public boolean isEnabled() {
// TODO Auto-generated method stub
return true;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
SeccrityConfig
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter{
#Autowired
private UserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(encodePwd());
}
#Bean
public BCryptPasswordEncoder encodePwd() {
return new BCryptPasswordEncoder();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests().antMatchers("/user/admin/").authenticated().anyRequest().hasAnyRole("superadmin").and()
.authorizeRequests().antMatchers("/user/welcome/").authenticated().anyRequest().hasAnyRole("user").and()
.authorizeRequests().antMatchers("/").authenticated().anyRequest().permitAll().and().formLogin()
.permitAll();
}
}
CustomUserDetailsService
Service
#Transactional
public class CustomUserDetailsService implements UserDetailsService{
#Autowired
private UserRoleRepo repo;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user= repo.findUserWithUsername(username);
CustomUserDetals details= null;
if(user!= null) {
details= new CustomUserDetals();
details.setUser(user);
}else {
throw new UsernameNotFoundException("User Not Exist With Name"+ username);
}
return details;
}
}
RepositoryMethod
#Override
public User findUserWithUsername(String username) {
Query query= getSession().createQuery(loadUserName);
query.setString(0, username);
User u= null;
Iterator<User> iterator= query.iterate();
while(iterator.hasNext()) {
u= iterator.next();
System.out.println(u.getUserName()+" "+u.getMobileNo()+" "+u.getRole().toString());
}
return u;
}
Controller
#RestController
#RequestMapping("/user")
public class UserRoleController {
#Autowired
private BCryptPasswordEncoder encoder;
#Autowired
private UserRoleService service;
/*
* #PostMapping("/add") public ResponseEntity<String> saveUser(#RequestBody User
* user) { String pass = user.getPassword();
* user.setPassword(encoder.encode(pass)); String temp = service.saveUser(user);
* return new ResponseEntity<String>(temp, HttpStatus.OK); }
*/
#GetMapping("/welcome")
#PreAuthorize("hasAnyRole('user')")
public String iMUser() {
System.out.println("WORKED");
return "Worked";
}
#GetMapping("/admin")
#PreAuthorize("hasAnyRole('superadmin')")
public ResponseEntity<String> saveUser(){
User user= new User();
Set<Role> role= new HashSet<Role>();
Role role1= new Role();
role1.setRole("user");
user.setUserName("amalswain");
user.setPassword(encoder.encode("password"));
user.setMobileNo(7606844075L);
role.add(role1);
role.add(role1);
role1.setUser(user);
user.setRole(role);
String temp= service.saveUser(user);
return new ResponseEntity<String>(temp,HttpStatus.OK);
}
}
403- Forbidden
In configure method from SecurityConfig try this:
http.csrf().disable()
.authorizeRequests()
.antMatchers("/user/admin/").hasRole("superadmin")
.antMatchers("/user/welcome/").hasAnyRole("user")
.antMatchers("/").permitAll()
.and()
.formLogin().permitAll();

How to get a username from post method in spring security?

I am using spring-boot and spring-security in app. My goal is to get the user name of the currently registered user from post method. Get method is working nicely but the post method isn't working. Why? How can I solve this problem?
Test Controller
#GetMapping("/test")
public String test(Authentication authentication) {
System.out.println(authentication.getName()); // <--------- It's working
return "testfile";
}
#PostMapping("/test")
public String testPost(Authentication authentication) {
System.out.println(authentication.getName()); // <--------- NOLL ERROR!
return "testfile";
}
Error
java.lang.NullPointerException: null
User
#Entity
#Table(name="user")
public class User {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="id")
private long id;
#Column(name="mail")
private String mail;
#Column(name="password")
private String password;
}
UserDAO
#Repository
public class UserDAO {
#Autowired
private EntityManager entityManager;
public List<User> findAll() {
return entityManager.unwrap(Session.class).createQuery("from User", User.class).getResultList();
}
public User findByMail(String mail){
Session currentSession = entityManager.unwrap(Session.class);
Query theQuery = currentSession.createQuery("from User where mail=:mail", User.class);
theQuery.setParameter("mail", mail);
List<User> users = theQuery.getResultList();
if(users.isEmpty()){
return new User();
}
return users.get(0);
}
public void saveOrUpdate(User user) {
Session currentSession = entityManager.unwrap(Session.class);
currentSession.saveOrUpdate(user);
}
}
UserService
public interface UserService extends UserDetailsService{
public List<User> findAll();
public User findByMail(String mail);
public void saveOrUpdate(User user);
}
UserServiceImpl
#Service
public class UserServiceImpl implements UserService{
#Autowired
private UserDAO userDAO;
#Autowired
private UserRoleDAO userRoleDAO;
#Autowired
private BCryptPasswordEncoder passwordEncoder;
#Override
#Transactional
public List<User> findAll() {
return userDAO.findAll();
}
#Override
#Transactional
public User findByMail(String mail){
return userDAO.findByMail(mail);
}
#Override
#Transactional
public void saveOrUpdate(User user) {
user.setPassword(passwordEncoder.encode(user.getPassword()));
userDAO.saveOrUpdate(user);
}
#Override
#Transactional
public UserDetails loadUserByUsername(String mail) throws UsernameNotFoundException {
User user = userDAO.findByMail(mail);
List<UserRole> userRole = userRoleDAO.findByUserId(user.getId());
if (user == null) {
throw new UsernameNotFoundException("Invalid username or password.");
}
return new org.springframework.security.core.userdetails.User(user.getName(), user.getPassword(), mapRolesToAuthorities(userRole));
}
private Collection<? extends GrantedAuthority> mapRolesToAuthorities(Collection<UserRole> roles) {
return roles.stream().map(role -> new SimpleGrantedAuthority(role.getRole())).collect(Collectors.toList());
}
}
SecurityConfig
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private Environment env;
#Autowired
private DataSource dataSource;
#Autowired
private UserService userService;
RedirectAuthenticationSuccessHandler redirectAuthenticationSuccessHandler = new RedirectAuthenticationSuccessHandler();
RedirectAuthenticationFailureHandler redirectAuthenticationFailureHandler = new RedirectAuthenticationFailureHandler();
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource)
.usersByUsernameQuery(env.getProperty("my.usersbyusernamequery"))
.authoritiesByUsernameQuery(env.getProperty("my.authoritiesbyusernamequery"));
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/anypage1/**").hasRole("MANAGER")
.antMatchers("/anypage2/**").hasRole("ADMIN")
.antMatchers("/test").hasRole("ADMIN")
.authenticated()
.antMatchers("/**").permitAll()
.and()
.formLogin().loginPage("/login").failureHandler(redirectAuthenticationFailureHandler)
.loginProcessingUrl("/login-control").successHandler(redirectAuthenticationSuccessHandler).permitAll()
.and()
.logout().logoutUrl("/logout").permitAll().and().exceptionHandling().accessDeniedPage("/access-denied");
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(HttpMethod.POST, "/anypage3").antMatchers(HttpMethod.POST, "/anypage4")
.antMatchers(HttpMethod.POST, "/test");
}
#Bean
public BCryptPasswordEncoder passwordEncoder()
{
return new BCryptPasswordEncoder();
}
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider auth = new DaoAuthenticationProvider();
auth.setUserDetailsService(userService);
auth.setPasswordEncoder(passwordEncoder());
return auth;
}
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(HttpMethod.POST, "/anypage3").antMatchers(HttpMethod.POST, "/anypage4")
.antMatchers(HttpMethod.POST, "/test");
}
You ignore /test in post method, so it will not be filtered by spring security filter, try to remove this.
You can get username from SecurityContextHolder
User user =
(User)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String name = user.getUsername(); //get current logged in username
In loadUserByUsername method you can manually set the Authentication token on SecurityContextHolder and same you can use in controller
UsernamePasswordWithAttributesAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken( loadUserByUsername(username), password, authorities );
SecurityContextHolder.getContext().setAuthentication(authenticationToken);

Spring Security + MongoDB, request get 401 all the time

I am learning how to use Spring and how to implement Spring Security in my project, using Roles and MongoDB.
I have a User model with a role list, and I want to let only ADMIN user to use some endpints from Controller.
Here is the User class and Role enum:
#Document(collection = "Users")
public class User {
#Id
private String id;
private String firstName;
private String lastName;
private String email;
private String password;
private List<Role> roles;
//constructor + getters and setters
}
public enum Role {
ADMIN,
BASIC_USER
}
I use the this UserDetails implementation: I think here is the problem...
public class CustomUserDetails implements UserDetails {
private User user;
public CustomUserDetails() {
}
public CustomUserDetails(User user) {
this.user = user;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.user.getRoles().stream().map(role -> new SimpleGrantedAuthority(String.format("ROLE_%s", role))).collect(Collectors.toList());
}
#Override
public String getPassword() {
return this.user.getPassword();
}
#Override
public String getUsername() {
return this.user.getEmail();
}
#Override
public boolean isAccountNonExpired() {
return true;
}
#Override
public boolean isAccountNonLocked() {
return true;
}
#Override
public boolean isCredentialsNonExpired() {
return true;
}
#Override
public boolean isEnabled() {
return true;
}
public User getUser() {
return user;
}
}
The UserDetailsService looks like this:
public class CustomUserDetailsServiceImpl implements UserDetailsService {
#Autowired
private UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
final User user = userRepository.findByEmail(email);
if (Objects.isNull(user)) {
throw new UsernameNotFoundException("User not found");
}
return new CustomUserDetails(user);
}
}
I made this configuration:
#Configuration
#EnableWebSecurity(debug = true)
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(createUserDetailsService()).passwordEncoder(passwordEncoder());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/users/**").permitAll()
.antMatchers( "/users/**").authenticated().anyRequest().hasAnyRole("ADMIN")
.antMatchers("/users/me").authenticated().anyRequest().hasAnyRole("ADMIN", "BASIC_USER")
.antMatchers( "/users/**").permitAll()
.and()
.formLogin().disable()
.httpBasic();
}
#Override
public void configure(WebSecurity web) throws Exception {
/* To allow Pre-flight [OPTIONS] request from browser */
web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
}
/**
* Create password encoder bean used for encrypting the password
*
* #return
*/
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* Create user service bean used for find the user by email
* #return
*/
#Bean
public UserDetailsService createUserDetailsService() {
return new CustomUserDetailsServiceImpl();
}
}
When I make any call with Postman at localhost:8080/users using Basic auth with the admin details from DB, all the time I get 401 Unauthorized Status
I think the problem is that I use an Enum for Roles and I don't know how to correctly build UserDetails implementation.
If helps, this is the UserController
#RestController
#RequestMapping("/users")
public class UserController {
#Autowired
private UserService userService;
#GetMapping(path = "")
public List<User> getUsers(){
return this.userService.getUsers();
}
}
When I used MySQL for this project (With the same classes for security) the apps worked perfectly. In that implementation I used Roles, Users and Users_Roles tables.

Resources