英文:
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:
But I always get 403 Forbidden as a response:
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 "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][1]][1]
**But I always get 403 Forbidden as response:**
[![response][2]][2]
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][3].
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.setName("Hartmut");
hartmut.setPassword(passwordEncoder.encode("test"));
hartmut.setEmail("test@test.com");
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 + '\'' +
'}';
}
}
[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<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("ROLE_" + 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("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".
答案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("hasAuthority('ROLE_ADMIN')")
@GetMapping("/admin")
public String admin() {
return "Welcome Admin!";
}
答案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 -> 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();
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论