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

huangapple go评论66阅读模式
英文:

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:

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

But I always get 403 Forbidden as a response:

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

Instead of adding the .antMatchers("/admin").access("hasRole('ADMIN')") to my SecurityConfig, I also tried to restrict 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("test@test.com");
    harald.setRoles(Arrays.asList(adminRole));
    harald.setEnabled(true);
    userRepository.save(harald);

    User hartmut = new User();
       hartmut

<details>
<summary>英文:</summary>

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

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

So I added `.antMatchers(&quot;/admin&quot;).access(&quot;hasRole(&#39;ADMIN&#39;)&quot;)` 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(&quot;/authenticate&quot;, &quot;/users/register&quot;,&quot;/products&quot;).permitAll()
                .antMatchers(&quot;/admin&quot;).access(&quot;hasRole(&#39;ADMIN&#39;)&quot;)        // 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][1]][1]

**But I always get 403 Forbidden as response:**

[![response][2]][2]

Instead of adding the `.antMatchers(&quot;/admin&quot;).access(&quot;hasRole(&#39;ADMIN&#39;)&quot;)` to my SecurityConfig, I also tried to restrict the access with Annotations:

    @PreAuthorize(&quot;hasRole(&#39;ROLE_ADMIN&#39;)&quot;)
    //or @Secured({&quot;ROLE_ADMIN&quot;})
    @GetMapping(&quot;/admin&quot;)
    public String admin() {
        return &quot;Welcome Admin!&quot;;
    }

**But then I can access it with any user, even if they don&#39;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(
                    &quot; &quot;, &quot; &quot;, true, true, true, true,
                    getAuthorities(Arrays.asList(
                            roleRepository.findByName(&quot;ROLE_USER&quot;))));
        }

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

    private Collection&lt;? extends GrantedAuthority&gt; getAuthorities(
            Collection&lt;Role&gt; roles) {

        return getGrantedAuthorities(getPrivileges(roles));
    }

    private List&lt;String&gt; getPrivileges(Collection&lt;Role&gt; roles) {

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

    private List&lt;GrantedAuthority&gt; getGrantedAuthorities(List&lt;String&gt; privileges) {
        List&lt;GrantedAuthority&gt; authorities = new ArrayList&lt;&gt;();
        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&lt;&gt;());
      }
    }


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(&quot;header:&quot; + httpServletRequest.getHeader(&quot;Authorization&quot;));
        //get Authorization information from the request itself
    	String authorizationHeader = httpServletRequest.getHeader(&quot;Authorization&quot;);

        String token = null;
        String userName = null;

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

        if (userName != null &amp;&amp; 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&#39;m creating the roles &quot;Admin&quot; and &quot;User&quot; in an extra class and also assign `privileges` to them as shown in [this tutorial][3].
But actually I never need the privileges, I just couldn&#39;t get it done without them, so I left them in. Because without the privileges, I don&#39;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&lt;ContextRefreshedEvent&gt; {

    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(&quot;READ_PRIVILEGE&quot;);
        Privilege writePrivilege
                = createPrivilegeIfNotFound(&quot;WRITE_PRIVILEGE&quot;);

        List&lt;Privilege&gt; adminPrivileges = Arrays.asList(
                readPrivilege, writePrivilege);
        createRoleIfNotFound(&quot;ROLE_ADMIN&quot;, adminPrivileges);
        createRoleIfNotFound(&quot;ROLE_USER&quot;, Arrays.asList(readPrivilege));

        Role adminRole = roleRepository.findByName(&quot;ROLE_ADMIN&quot;);
        Role userRole = roleRepository.findByName(&quot;ROLE_USER&quot;);
        User harald = new User();
        harald.setName(&quot;Harald&quot;);
        harald.setPassword(passwordEncoder.encode(&quot;test&quot;));
        harald.setEmail(&quot;test@test.com&quot;);
        harald.setRoles(Arrays.asList(adminRole));
        harald.setEnabled(true);
        userRepository.save(harald);

        User hartmut = new User();
           hartmut.setName(&quot;Hartmut&quot;);
           hartmut.setPassword(passwordEncoder.encode(&quot;test&quot;));
           hartmut.setEmail(&quot;test@test.com&quot;);
           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&lt;Privilege&gt; 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=&quot;roles&quot;)
    @Data
    @NoArgsConstructor
    public class Role {

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

    private String name;

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(
            name = &quot;roles_privileges&quot;,
            joinColumns = @JoinColumn(
                    name = &quot;role_id&quot;, referencedColumnName = &quot;id&quot;),
            inverseJoinColumns = @JoinColumn(
                    name = &quot;privilege_id&quot;, referencedColumnName = &quot;id&quot;))
    private Collection&lt;Privilege&gt; privileges;

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

    @Override
    public String toString() {
        return &quot;Role{&quot; +
                &quot;id=&quot; + id +
                &quot;, name=&#39;&quot; + name + &#39;\&#39;&#39; +
                &#39;}&#39;;
      }
    }

and my `privilege.java`:

    @Entity
    @Table(name=&quot;privileges&quot;)
    @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 &quot;Privilege{&quot; +
                &quot;id=&quot; + id +
                &quot;, name=&#39;&quot; + name + &#39;\&#39;&#39; +
                &#39;}&#39;;
      }
    }


  [1]: https://i.stack.imgur.com/uTJ6B.png
  [2]: https://i.stack.imgur.com/5EdJz.png
  [3]: https://www.baeldung.com/role-and-privilege-for-spring-security-registration

</details>


# 答案1
**得分**: 6

我能够通过在前面添加 `ROLE_` 来解决这个问题

```java
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ROLE_" + ADMIN));
英文:

I was able to fix the issue by prepending ROLE_

List&lt;GrantedAuthority&gt; authorities = new ArrayList&lt;&gt;();
authorities.add(new SimpleGrantedAuthority(&quot;ROLE_&quot; + ADMIN));

答案2

得分: 2

1/ 在您的安全配置中启用安全注解,添加以下代码行:

@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)

prePostEnabled 允许您使用 @PreAuthorize、@PostAuthorize 等注解,securedEnabled 则用于使用 @Secured 注解。

2/ 为了确保只有具有 "admin" 角色的用户可以访问 "/admin",您需要添加类似以下的注解:

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

或者可以尝试使用 .antMatchers("/admin").access("hasRole('ADMIN')") 替代注解的方式。

3/ 确保您的 loadByUsername 方法在登录时返回具有 "ROLE_ADMIN" 权限的管理员用户。

英文:

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(&quot;hasRole(&#39;ADMIN&#39;)&quot;)
// or @Secured({&quot;ROLE_ADMIN&quot;})
// or @PreAuthorize(&quot;hasAuthority(&#39;ROLE_ADMIN&#39;)&quot;)
@GetMapping(&quot;/admin&quot;)
public String admin() {
return &quot;Welcome Admin!&quot;;
}



.antMatchers(&quot;/admin&quot;).access(&quot;hasRole(&#39;ADMIN&#39;)&quot;) 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".

答案3

得分: 1

Sure, here's the translation:

public class SecurityConfig extends WebSecurityConfigurerAdapter 上加上注解 @EnableGlobalMethodSecurity(prePostEnabled = true)

还有,

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

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

And,

@PreAuthorize(&quot;hasAuthority(&#39;ROLE_ADMIN&#39;)&quot;)
@GetMapping(&quot;/admin&quot;)
public String admin() {
return &quot;Welcome Admin!&quot;;
}

答案4

得分: 1

即使在我的SpringConfig中配置如下,我在数据库中的值仍然希望以ROLE_为前缀(例如: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();
}
英文:

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 -&gt; csrf.disable())
.authorizeHttpRequests(auth -&gt; {
auth.requestMatchers(&quot;/library/api/addMember/**&quot;).permitAll();
auth.requestMatchers(&quot;/library/api/admin/**&quot;).hasRole(&quot;ADMIN&quot;);
auth.requestMatchers(&quot;/library/api/member/greeting/**&quot;).hasRole(&quot;MEMBER&quot;);
})
.httpBasic(Customizer.withDefaults())
.build();
}

huangapple
  • 本文由 发表于 2020年9月4日 18:56:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/63739817.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定