I am creating an application using Role based restriction to certain REST endpoints.
I have a Roles table, user_roles table and a users table.
ROLE_USER is working fine, and users with that Role can successfully access their pages. Even if I swap it around so that only ROLE_USER can access /admin/**, it works.
However, when I try use ROLE_ADMIN on the admin page, it never works. It always redirects me to the 403 Forbidden page
Security Config:
#Bean
DaoAuthenticationProvider provider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(userDetailsServiceImpl);
provider.setPasswordEncoder(passwordEncoder);
return provider;
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(provider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests()
.antMatchers("/banking/**").hasAnyRole("USER", "ADMIN")
.antMatchers(HttpMethod.GET, "/admin/**").hasRole("ADMIN")
.and()
.formLogin().loginPage("/login").permitAll()
.and()
.logout().permitAll()
.and()
.exceptionHandling().accessDeniedPage("/403");
}
}
MyUserDetails:
public class MyUserDetails implements UserDetails {
private static final long serialVersionUID = -2067381432706760538L;
private User user;
public MyUserDetails(User user) {
this.user = user;
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Set<Role> roles = user.getRoles();
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
for (Role role : roles) {
authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName()));
}
return authorities;
}
UserDetailsServiceImpl:
public class UserDetailsServiceImpl implements UserDetailsService {
#Autowired
private UserService userService;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userService.findByUsername(username);
if(user == null) {
throw new UsernameNotFoundException("User not found");
}
return new MyUserDetails(user);
}
}
Would appreciate any help as I've been stuck on this for a while. Thanks!
Related
SecurityConfiguration
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
CustomUserDetailService customUserDetailService;
#Autowired
CustomSuccessHandler successHandler;
#Autowired
CustomFailureHandler failureHandler;
#Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
#Autowired
CustomBeanDefinition customBeanDefinition;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
UserDetailsService userDetailsService = customBeanDefinition.jpaUserDetails();
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/login").permitAll()
.antMatchers("/signup").permitAll()
.antMatchers("/register").permitAll()
.antMatchers("/book/index").hasAuthority("ADMIN")
.anyRequest().authenticated()
.and()
.csrf().disable()
.formLogin()
.successHandler(successHandler)
.loginPage("/login")
.failureHandler(failureHandler)
.usernameParameter("username")
.passwordParameter("password")
.and().logout().permitAll();
}
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/resources/**", "/static/**", "/css/**", "/js/**", "/img/**", "/icon/**");
}
}
CustomUserDetailService
#Service
public class CustomUserDetailService implements UserDetailsService {
#Autowired
UserRepository userRepository;
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("User not found");
}
List<GrantedAuthority> authorities = getUserAuthority(user.getRoles());
UserDetails userDetails = buildUserForAuthentication(user, authorities);
return userDetails;
}
private List<GrantedAuthority> getUserAuthority(Set<Role> userRoles) {
Set<GrantedAuthority> roles = new HashSet<>();
for (Role role : userRoles) {
roles.add(new SimpleGrantedAuthority(role.getAuthority()));
}
return new ArrayList<>(roles);
}
private UserDetails buildUserForAuthentication(User user, List<GrantedAuthority> authorities) {
return new org.springframework.security.core.userdetails.User(
user.getUsername(), user.getPassword(), authorities);
}
}
CustomBeanDefinition
#Configuration
public class CustomBeanDefinition {
#Bean
public UserDetailsService jpaUserDetails() {
return new CustomUserDetailService();
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
UserService
#Service
public class UserService {
private UserRepository userRepository;
private RoleRepository roleRepository;
private BCryptPasswordEncoder passwordEncoder;
#Autowired
public UserService(UserRepository userRepository,
RoleRepository roleRepository, BCryptPasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.roleRepository = roleRepository;
this.passwordEncoder = passwordEncoder;
}
public void save(String username, String email, String password, String name) {
User user = userRepository.findByUsername(username);
if (user == null) {
user = new User();
user.setName(name);
user.setUsername(username);
user.setEmail(email);
user.setPassword(passwordEncoder.encode(password));
user.setActive(true);
Role role = roleRepository.findByAuthority("ADMIN");
user.setRoles(new HashSet<>(Collections.singletonList(role)));
userRepository.save(user);
System.out.println("...user saved with ADMIN role");
}
}
}
In my application user is currently being saved successfully and binding with specific role.
However, when i try to authenticate my application with saved user, it never goes on success handler, it redirects me on failure handler.
It looks like authentication is not working.
Any help would be appreciated.
Change your Security Configuration, configure() method like below,
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailService).
passwordEncoder(bCryptPasswordEncoder);
}
Used Spring Boot 2 + Spring Security Starter.
Authorizes users, but for some reason gives an error 403.
I tried to configure in different ways, but it does not work.
After successful authorization (the loadUserByUsername method works fine) it shows 403 on all pages with the / admin prefix, and before authorization, switching to any page with this prefix leads to a redirect to / login
#Controller
public class AdminController {
#RequestMapping(value = "/admin", method = {GET, POST})
public String adminMainPage() {
return "redirect:/admin/article";
}
}
#Controller
#RequestMapping("/admin/article")
public class ArticleController {
#RequestMapping(value = "", method = {GET, POST})
public ModelAndView indexAdminPage(...){
...
}
}
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter implements UserDetailsService {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.userDetailsService(this)
.authorizeRequests()
.antMatchers("/", "/login",
"/login*", "/assets/**", "/lib/**", "/page.scripts/*").permitAll()
.antMatchers("/admin/**").hasAnyRole("ADMIN")
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/login")
.usernameParameter("login")
.passwordParameter("password")
.successForwardUrl("/admin")
.permitAll()
.and()
.logout()
.deleteCookies("JSESSIONID")
.permitAll();
}
private Collection<? extends GrantedAuthority> adminGrantedAuthoritySet = new HashSet<>() {{
add(new SimpleGrantedAuthority("ADMIN"));
}};
private final UserRepository userRepository;
public WebSecurityConfig(UserRepository userRepository ) {
this.userRepository = userRepository;
}
#Override
public UserDetails loadUserByUsername(String login) throws UsernameNotFoundException {
Optional<UserEntity> optionalUser = userRepository.findByLogin(login);
if (optionalUser.isEmpty()) {
throw new UsernameNotFoundException("User by login '" + login + "' not found");
} else {
UserEntity userEntity = optionalUser.get();
return new User(login, userEntity.getPassword(), adminGrantedAuthoritySet);
}
}
}
In Spring Security there is a distinction between a role and an authority.
A role is an authority that is prefixed with "ROLE_". In this example the authority "ROLE_ADMIN" is the same as the role "ADMIN".
You are setting your admin authorities to be a list of new SimpleGrantedAuthority("ADMIN"), but you are restricting access to .hasAnyRole("ADMIN").
You need to change one of those configurations.
If you use .hasAnyRole("ADMIN"), then you should change the admin authorities list to use new SimpleGrantedAuthority("ROLE_ADMIN").
Otherwise, if you want your list to be new SimpleGrantedAuthority("ADMIN"), then you should use .hasAnyAuthority("ADMIN").
First, I will advice that you separate UserDetailsService from the WebSecurityConfig.
Have a separate class for UserDetailsService like
#Service("customCustomerDetailsService")
public class CustomCustomerDetailsService implements UserDetailsService {
#Autowired
private CustomerRepository customers;
#Override
public UserDetails loadUserByUsername(String email) {
return this.customers.findByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException("Username: " + email + " not found"));
}
}
Then your UserEntity should implement UserDetails class where you set the authorities.See the answer //userdetails
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.roles.stream().map(SimpleGrantedAuthority::new).collect(toList());
}
#Override
public String getUsername() {
return this.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;
}
#Transient
private List<String> roles = Arrays.asList("ROLE_USER");
public List<String> getRoles() {
return roles;
}
Then you need DAOauthentication manager which makes use of the UserDetailsService like this:
#Bean
public DaoAuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService());
authProvider.setPasswordEncoder(encoder());
return authProvider;
}
#Bean
#Override
public UserDetailsService userDetailsService() {
return new CustomCustomerDetailsService();
}
I don't know think putting everything in the WebSecurityConfig is good practice and it will be complicated and prone to errors!
I'm trying to do custom user with the properties I want and to use authentication , the CustomUser extends the spring User , The user is returned by the CustomProvider which implements UsersDetailsService
#Service
#Qualifier("UserDetailsService")
public class CustomUserDetailsService implements UserDetailsService {
#Autowired
private UserRepository userRepository;
#Override
#Transactional
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
User user=userRepository.findByEmail(email);
return new CustomUser(user.getName(),user.getPassword(),buildUserAuthority(user.getRoles()));
}
private List<GrantedAuthority> buildUserAuthority(Set<Role> userRoles) {
Set<GrantedAuthority> setAuths = new HashSet<GrantedAuthority>();
// add user's authorities
for (Role userRole : userRoles) {
setAuths.add(new SimpleGrantedAuthority(userRole.getRole()));
}
List<GrantedAuthority> Result = new ArrayList<GrantedAuthority>(setAuths);
return Result;
}
#Getter
#Setter
public class CustomUser extends User {
public CustomUser(String username, String password, Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
}
public String firstName;
public String lastName;
}
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("UserDetailsService")
private UserDetailsService customUserDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
/* auth.
jdbcAuthentication()
.usersByUsernameQuery(usersQuery)
.authoritiesByUsernameQuery(rolesQuery)
.dataSource(dataSource)
.passwordEncoder(bCryptPasswordEncoder);
*/
auth.userDetailsService(customUserDetailsService);
}
}
I have 2 issues :
1- I have commented the auth.jdbcAuthentication as I couldn't have the authentication and customProvider to work together, how can I use the database authentication with customuser ?
2- if I comment the jdbcAuthentication the customuser works but when I get the principal the password is null : authentication.getPrincipal().getPassword()
Update :
I have solved 2 by eraseCredentials(false) but still unable to do both ( authentication with custom user )
Old answer:
I have solved it by this :
auth.eraseCredentials(false).userDetailsService(customUserDetailsService).and().jdbcAuthentication()
.usersByUsernameQuery(usersQuery)
.authoritiesByUsernameQuery(rolesQuery)
.dataSource(dataSource)
.passwordEncoder(bCryptPasswordEncoder);
update:
I found that no need to use 2 types of authentication as spring will verify the password with the returned user.
I am new in Java Spring and I want to create a system with registration for users, which are stored in my DB (Postgres), where a password is stored encrypted by BCryptPasswordEncoder. The registration process is working fine, but when I want to log in, I always get an "Invalid username or password." message. I already search everywhere and read a lot of articles, but everything that I did had the same result.
Here is my SecurityConfiguration class:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
#Qualifier("customUserDetailsService")
private CustomUserDetailsService userDetailsService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService()).and().authenticationProvider(authProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/signin", "/confirm", "/error","/signup", "/css/**","/js/**","/images/**").permitAll()
.antMatchers("/admin/**").hasAuthority("ROLE_ADMIN")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/signin")
.usernameParameter("username").passwordParameter("password")
.defaultSuccessUrl("/cockpit.html")
.permitAll()
.and()
.exceptionHandling().accessDeniedPage("/403")
.and()
.csrf()
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/signin").and().exceptionHandling()
.accessDeniedPage("/error");
}
#Bean
public PasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
#Bean
public DaoAuthenticationProvider authProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(encoder());
return authProvider;
}
}
And here is my CustomUserDetailsService class:
#Service("customUserDetailsService")
public class CustomUserDetailsService implements UserDetailsService{
private final UserRepository userRepository;
private final RoleRepository userRolesRepository;
#Autowired
private PasswordEncoder bCryptPasswordEncoder;
#Autowired
public CustomUserDetailsService(UserRepository userRepository,RoleRepository userRolesRepository) {
this.userRepository = userRepository;
this.userRolesRepository=userRolesRepository;
}
#Override
public UserDetails loadUserByUsername(String username) throws
UsernameNotFoundException {
Logger LOGGER = Logger.getLogger(CustomUserDetailsService.class.getName());
User user = userRepository.findByUsername(username);
if (null == user) {
return null;
} else {
List<GrantedAuthority> authorities =
buildUserAuthority(userRolesRepository.findRoleByUserName(username));
LOGGER.info("Loaded account: " + user.getUsername() + " password: " + user.getPassword() + " password matches: " + bCryptPasswordEncoder.matches("password", user.getPassword()));
org.springframework.security.core.userdetails.User userDetails = new org.springframework.security.core.userdetails.User(user.getUsername(), Deuser.getPassword(),authorities);
return userDetails;
}
}
private List<GrantedAuthority> buildUserAuthority(Set<Role> userRoles) {
Set<GrantedAuthority> setAuths = new HashSet<>();
// add user's authorities
for (Role userRole : userRoles) {
setAuths.add(new SimpleGrantedAuthority(userRole.getRole()));
}
return new ArrayList<>(setAuths);
}
public User findByConfirmationToken(String confirmationToken) {
return userRepository.findByConfirmationToken(confirmationToken);
}
public void saveUser(User user){
user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
userRepository.save(user);
}
public void saveRole(User user) {
Role role = new Role();
role.setRole("ROLE_USER");
role.setId(user.getId());
role.setUsername(user.getUsername());
userRolesRepository.save(role);
}
}
I call the method saveUser(user) and saveRole(user) during registration. The LOGGER.info message gives me "false" for bCryptPasswordEncoder.matches("password", user.getPassword()) even I wrote right password.
SOLVED
Okay, I just found out where was the mistake. I called method saveUser twice, during registration, and then during activation, so the password was encrypted twice. I solved that by adding method updateUser without using encryption.
Thank you for your help.
Try like this:
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// Create a default account
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
CustomUserDetails:
#Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User account = userDao.getUserByUsername(username);
System.out.println("User got from DB----------------------" + account.getPassword());
boolean enabled = true;
boolean accountNonExpired = true;
boolean credentialsNonExpired = true;
boolean accountNonLocked = true;
User user = new User(account.getUserName(), account.getPassword(), enabled, accountNonExpired,
credentialsNonExpired, accountNonLocked, getAuthorities(account.getRole()));
System.out.println(user.getPassword());
return user;
}
I'm trying to add bcrypt to a spring app of mine. Authentication works just fine without. But when I try to encode using bcrypt I get "Reason: Bad credentials" when trying to login.
My user model looks as follows.
#Entity
#Table(name="users") // user is a reserved word in postgresql
public class User extends BaseEntity {
private PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
...
#Column(nullable=false)
private String password;
...
public String getPassword() {
return password;
}
public void setPassword(String password) {
String hashedPassword = passwordEncoder.encode(password);
this.password = hashedPassword;
}
...
}
My SecurityConfig looks as follows.
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomUserDetailsService userDetailsService;
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
private BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
...
}
Do the above seem right? Do I need to do more than what I've already done?
My bad for not posting enough code. Naturally my user model didn't tell the entire story. I also have a class called SecurityUser which I've posted below. Due to the copy constructor the password gets hashed twice.
public class SecurityUser extends User implements UserDetails {
private static final long serialVersionUID = 867280338717707274L;
public SecurityUser(User user) {
if(user != null)
{
this.setId(user.getId());
this.setName(user.getName());
this.setEmail(user.getEmail());
this.setPassword(user.getPassword());
this.setRoles(user.getRoles());
}
}
#Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new ArrayList<>();
Set<Role> userRoles = this.getRoles();
if(userRoles != null)
{
for (Role role : userRoles) {
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(role.getName());
authorities.add(authority);
}
}
return authorities;
}
...
}
I've made my passwordEncoder method public and promoted it to a bean so I can autowire it into my UserService which is shown below. That way I only have to change encoder in one place if I ever decide to do so.
#Service
public class UserService {
#Autowired
private UserRepository userRepository;
#Autowired
private PasswordEncoder passwordEncoder;
public User create(User user) {
String hashedPassword = passwordEncoder.encode(user.getPassword());
user.setPassword(hashedPassword);
return userRepository.save(user);
}
...
}
Here is how I would set it up.
User Table has 4 properties (amongst others)
id (auto increment)
username (or email_address) field
password field.
enabled (value will be either 1 or 0)
Role table (3 properties)
id (auto increment)
user_id (user table foreign key)
authority;
Create Java Entities for the two tables.
Spring Security Configuration Class looks like:
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
String usrsByUnameQry = "SELECT u.email_address, u.password, u.enabled FROM user u WHERE u.email_address=?";
3String authByUnameQry = "SELECT u.email_address, r.authority FROM user u, role r WHERE u.id=r.user_id AND u.email_address=?";
auth
.eraseCredentials(false)
.jdbcAuthentication()
.dataSource(dataSource)
.passwordEncoder(passwordEncoder())
.usersByUsernameQuery(usrsByUnameQry)
.authoritiesByUsernameQuery(authByUnameQry);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin()
.usernameParameter("username") //username property defined in html form
.passwordParameter("password") //password property defined in html form
// url that holds login form
.loginPage("/signin")
.loginProcessingUrl("/signin/authenticate")
.failureUrl("/loginfail")
// Grant access to users to the login page
.permitAll()
.and()
.logout()
.logoutUrl("/signout")
.deleteCookies("JSESSIONID")
.logoutSuccessUrl("/signin")
.and()
.authorizeRequests()
.antMatchers("/foo/**").permitAll()//Grant access to all (no auth reuired)
.antMatchers("/").hasAnyAuthority("ROLE_USER","ROLE_ADMIN") //Grant access to only users with role "ROLE_USER" or "ROLE_ADMIN"
}
#Bean(name = "authenticationManager")
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
#Bean
public TextEncryptor textEncryptor(){
return Encryptors.noOpText();
}