Boommeister
Boommeister

Reputation: 2107

Spring Security role based authentication - 403 Forbidden although user has ROLE_ADMIN

I want only users with role "Admin" to be able to get the "Welcome Admin!" message:

@GetMapping("/admin")
public String admin() {
    return "Welcome Admin!";
}

So I added .antMatchers("/admin").access("hasRole('ADMIN')") to my SecurityConfig:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

  @Autowired
  private CustomUserDetailsService userDetailsService;

  @Autowired
  private JwtFilter jwtFilter;

  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userDetailsService);
  }

  @Bean
  public PasswordEncoder passwordEncoder() {
    return PasswordEncoderFactories.createDelegatingPasswordEncoder();
  }

  @Bean(name = BeanIds.AUTHENTICATION_MANAGER)
  @Override
  public AuthenticationManager authenticationManagerBean() throws Exception {
    return super.authenticationManagerBean();
  }

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.cors();

    http.csrf().disable().authorizeRequests()
            .antMatchers("/authenticate", "/users/register","/products").permitAll()
            .antMatchers("/admin").access("hasRole('ADMIN')")        // HERE IT SHOULD CHECK FOR THE ADMIN ROLE
            .anyRequest().authenticated().and().exceptionHandling()
            .and().sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS);

    http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);;
  }
}

The user that I use to access also has the role Admin:

the user

But I always get 403 Forbidden as response:

response

Instead of adding the .antMatchers("/admin").access("hasRole('ADMIN')") to my SecurityConfig, I also tried to restrict the access with Annotations:

@PreAuthorize("hasRole('ROLE_ADMIN')")
//or @Secured({"ROLE_ADMIN"})
@GetMapping("/admin")
public String admin() {
    return "Welcome Admin!";
}

But then I can access it with any user, even if they don't have the Admin Role (response status = 200). What am I doing wrong here?

Additional info:

My UserDetailsService:

@Service
public class CustomUserDetailsService implements UserDetailsService {

@Autowired
private UserRepository userRepository;

@Autowired
private RoleRepository roleRepository;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    User user = null;
    try {
        user = userRepository.findByName(username);
    } catch(Exception e){
        String message = e.getMessage();
        System.out.println(message);
    }

    if (user == null) {
        return new org.springframework.security.core.userdetails.User(
                " ", " ", true, true, true, true,
                getAuthorities(Arrays.asList(
                        roleRepository.findByName("ROLE_USER"))));
    }

    return new org.springframework.security.core.userdetails.User(user.getName(), user.getPassword(), user.isEnabled(), true, true, true, getAuthorities(user.getRoles()));
}

private Collection<? extends GrantedAuthority> getAuthorities(
        Collection<Role> roles) {

    return getGrantedAuthorities(getPrivileges(roles));
}

private List<String> getPrivileges(Collection<Role> roles) {

    List<String> privileges = new ArrayList<>();
    List<Privilege> collection = new ArrayList<>();
    for (Role role : roles) {
        collection.addAll(role.getPrivileges());
    }
    for (Privilege item : collection) {
        privileges.add(item.getName());
    }
    return privileges;
}

private List<GrantedAuthority> getGrantedAuthorities(List<String> privileges) {
    List<GrantedAuthority> authorities = new ArrayList<>();
    for (String privilege : privileges) {
        authorities.add(new SimpleGrantedAuthority(privilege));
    }
    return authorities;
}


public UserDetails loadUserByEmail(String email) throws UsernameNotFoundException {
    User user = userRepository.findByEmail(email);
    return new org.springframework.security.core.userdetails.User(user.getName(), user.getPassword(), new ArrayList<>());
  }
}

My jwtFilter.java:

@Component
public class JwtFilter extends OncePerRequestFilter {

@Autowired
private JwtUtil jwtUtil;
@Autowired
private CustomUserDetailsService service;


@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
    System.out.println("header:" + httpServletRequest.getHeader("Authorization"));
    //get Authorization information from the request itself
    String authorizationHeader = httpServletRequest.getHeader("Authorization");

    String token = null;
    String userName = null;

    //check for its type, it must be Bearer + jwt
    if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
        //get the token itself
        token = authorizationHeader.substring(7);
        //decrypt username
        userName = jwtUtil.extractUsername(token);
    }

    if (userName != null && SecurityContextHolder.getContext().getAuthentication() == null) {

        UserDetails userDetails = service.loadUserByUsername(userName);

        if (jwtUtil.validateToken(token, userDetails)) {

            UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
                    new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());   // HERE IS THE getAuthorities function
            usernamePasswordAuthenticationToken
                    .setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
            SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
        }
    }
    filterChain.doFilter(httpServletRequest, httpServletResponse);
  }
}

I'm creating the roles "Admin" and "User" in an extra class and also assign privileges to them as shown in this tutorial. But actually I never need the privileges, I just couldn't get it done without them, so I left them in. Because without the privileges, I don't know how to get the GrantedAuthorities into my userDetails which are needed for the userDetails.getAuthorities() function in my jwtFilter class...:

@Component
public class SetupDataLoader implements
    ApplicationListener<ContextRefreshedEvent> {

boolean alreadySetup = false;

@Autowired
private UserRepository userRepository;

@Autowired
private UserService userService;

@Autowired
private RoleRepository roleRepository;

@Autowired
private PrivilegeRepository privilegeRepository;

@Autowired
private PasswordEncoder passwordEncoder;

@Override
@Transactional
public void onApplicationEvent(ContextRefreshedEvent event) {

    if (alreadySetup)
        return;
    Privilege readPrivilege
            = createPrivilegeIfNotFound("READ_PRIVILEGE");
    Privilege writePrivilege
            = createPrivilegeIfNotFound("WRITE_PRIVILEGE");

    List<Privilege> adminPrivileges = Arrays.asList(
            readPrivilege, writePrivilege);
    createRoleIfNotFound("ROLE_ADMIN", adminPrivileges);
    createRoleIfNotFound("ROLE_USER", Arrays.asList(readPrivilege));

    Role adminRole = roleRepository.findByName("ROLE_ADMIN");
    Role userRole = roleRepository.findByName("ROLE_USER");
    User harald = new User();
    harald.setName("Harald");
    harald.setPassword(passwordEncoder.encode("test"));
    harald.setEmail("[email protected]");
    harald.setRoles(Arrays.asList(adminRole));
    harald.setEnabled(true);
    userRepository.save(harald);

    User hartmut = new User();
       hartmut.setName("Hartmut");
       hartmut.setPassword(passwordEncoder.encode("test"));
       hartmut.setEmail("[email protected]");
       hartmut.setRoles(Arrays.asList(adminRole));
       hartmut.setEnabled(true);
       userRepository.save(hartmut);
       }

    alreadySetup = true;
}

@Transactional
Privilege createPrivilegeIfNotFound(String name) {

    Privilege privilege = privilegeRepository.findByName(name);
    if (privilege == null) {
        privilege = new Privilege(name);
        privilegeRepository.save(privilege);
    }
    return privilege;
}

@Transactional
Role createRoleIfNotFound(
        String name, Collection<Privilege> privileges) {

    Role role = roleRepository.findByName(name);
    if (role == null) {
        role = new Role(name);
        role.setPrivileges(privileges);
        roleRepository.save(role);
    }

    return role;
  }
}

My Role.java :

@Entity
@Table(name="roles")
@Data
@NoArgsConstructor
public class Role {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

private String name;

@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
        name = "roles_privileges",
        joinColumns = @JoinColumn(
                name = "role_id", referencedColumnName = "id"),
        inverseJoinColumns = @JoinColumn(
                name = "privilege_id", referencedColumnName = "id"))
private Collection<Privilege> privileges;

public Role(String name) {
    this.name = name;
}

@Override
public String toString() {
    return "Role{" +
            "id=" + id +
            ", name='" + name + '\'' +
            '}';
  }
}

and my privilege.java:

@Entity
@Table(name="privileges")
@Data
@NoArgsConstructor
public class Privilege {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

private String name;

public Privilege(String name) {
    this.name = name;
}

@Override
public String toString() {
    return "Privilege{" +
            "id=" + id +
            ", name='" + name + '\'' +
            '}';
  }
}

Upvotes: 2

Views: 8029

Answers (4)

v1j4y
v1j4y

Reputation: 11

Even though in my SpringConfig I gave like below, my values in DB was expected to be prefixed with ROLE_(ex: ROLE_ADMIN).

@Bean
    public SecurityFilterChain configure(HttpSecurity http) throws Exception {
        
        return http
                .csrf(csrf -> csrf.disable())
                .authorizeHttpRequests(auth -> {
                    auth.requestMatchers("/library/api/addMember/**").permitAll();
                    auth.requestMatchers("/library/api/admin/**").hasRole("ADMIN");
                    auth.requestMatchers("/library/api/member/greeting/**").hasRole("MEMBER");
                })
                .httpBasic(Customizer.withDefaults())
                .build();
    }

Upvotes: 1

Binamra Kandel
Binamra Kandel

Reputation: 81

I was able to fix the issue by prepending ROLE_

List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ROLE_" + ADMIN));

Upvotes: 8

mazenaissa
mazenaissa

Reputation: 178

1/ You need to enable the security annotation in your security config with adding this line : @EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true) prePostEnabled let you use @PreAuthorize,@PostAuthorize,etc and securedEnabled for using @Secured

2/ In order to say that only users with the role admin can access "/admin" you need to add an annotation like this : `

@PreAuthorize("hasRole('ADMIN')")
// or @Secured({"ROLE_ADMIN"})
// or @PreAuthorize("hasAuthority('ROLE_ADMIN')")
@GetMapping("/admin")
public String admin() {
    return "Welcome Admin!";
}

.antMatchers("/admin").access("hasRole('ADMIN')") should work too instead of using annotations.

3/ You need to be sure that your method loadByUsername returns the user admin when logging with his authorities list containing the authority "ROLE_ADMIN".

Upvotes: 3

Alok Singh
Alok Singh

Reputation: 508

Annotate public class SecurityConfig extends WebSecurityConfigurerAdapter with @EnableGlobalMethodSecurity(prePostEnabled = true)

And,

@PreAuthorize("hasAuthority('ROLE_ADMIN')")
@GetMapping("/admin")
public String admin() {
    return "Welcome Admin!";
}

Upvotes: 2

Related Questions