Spring Boot安全中的HTTP 403禁止错误。

7

Spring安全配置类

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter{

    @Autowired
    private UserDetailsService userDetailsService;

    @Bean
    public PasswordEncoder getPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

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

        http
            .cors()
            .and()
            .authorizeRequests()
            .antMatchers("/user", "/login").permitAll()
            .antMatchers("/employee", "/insurance").hasRole("User")
            .anyRequest()
            .authenticated()
            .and()
            .httpBasic()
            .and()
            .csrf().disable();
    }

    protected void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(getPasswordEncoder());
    }
}

UserDetailsService实现类

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserService userService;

    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
        User user = null;
        Set<GrantedAuthority> grantedAuthorities = null;
        try
        {
            user = userService.findByUserName(userName);
            if(user == null)
                throw new UsernameNotFoundException("User " + userName  + " not available");

            grantedAuthorities = new HashSet<>();
            for(Role role: user.getRoles()) {
                grantedAuthorities.add(new SimpleGrantedAuthority(role.getRole().toString()));
            }
        }
        catch(Exception exp) {
            exp.printStackTrace();
        }
        return new org.springframework.security.core.userdetails.User(user.getUserName(), user.getPassword(), grantedAuthorities);
    }
}

员工休息控制器类
@RestController
public class EmployeeController {

    @Autowired
    private EmployeeService employeeService;

    @Autowired
    private InsuranceService insuranceService;

    @PostMapping("/employee")
    public ResponseEntity<Employee> create(@RequestBody Employee employee) throws Exception {
        employee = employeeService.create(employee);
        return new ResponseEntity<Employee>(employee, HttpStatus.CREATED);
    }

    @PutMapping("/employee")
    public ResponseEntity<Employee> update(@RequestBody Employee employee) throws Exception {
        employee = employeeService.update(employee);
        return new ResponseEntity<Employee>(employee, HttpStatus.OK);
    }

    @DeleteMapping("/employee/{id}")
    public ResponseEntity<String> delete(@PathVariable("id") long id) throws Exception {
        employeeService.delete(id);
        return new ResponseEntity<String>("Employee deleted successfully", HttpStatus.OK);
    }

    @GetMapping("/employee/{id}")
    public ResponseEntity<Employee> findEmployeeDetails(@PathVariable("id") long id) throws Exception {
        Employee employee = employeeService.findById(id);
        return new ResponseEntity<Employee>(employee, HttpStatus.OK);
    }

    @GetMapping("/employee")
    public ResponseEntity<List<Employee>> findAll() throws Exception {
        List<Employee> employees = employeeService.findAll();
        return new ResponseEntity<List<Employee>>(employees, HttpStatus.OK);
    }
}

我在使用Postman向/employee URL提交任何HTTP请求方法(POST/GET/PUT)时都会收到403禁止错误。
{
    "timestamp": "2019-09-17T05:37:35.778+0000",
    "status": 403,
    "error": "Forbidden",
    "message": "Forbidden",
    "path": "/hr-core/employee"
}

我使用POSTMAN发送HTTP请求时,在basic auth header(Authorization)中正确填写了用户名和密码,但仍然出现此错误。该用户拥有访问/employee REST端点的USER和ADMIN角色。我已在http安全性中禁用了CSRF。
你可以如何解决这个错误?

为什么路径是/hr-core/employe而不是/employee? - Simon Martinelli
hr-core是Web应用程序的上下文根。我正在Postman中触发此URL - http://localhost:8083/hr-core/employee - Karthik
@harkeshkumar 我想要对/employee URL进行身份验证。删除这行代码会破坏我保护它们的目的。 - Karthik
@Sachith - 我只有一个Spring Boot应用程序,上下文路径为/hr-core。我正在使用Postman发出REST URLS - http://localhost:8083/hr-core/employee。 - Karthik
@Karthik 那么问题在于这些不是角色而是权限。我已经发布了一个答案来解释它们之间的区别。 - g00glen00b
显示剩余13条评论
2个回答

14

在Spring Security中,角色(roles)权限(authorities)有区别。虽然权限可以是任何东西,但是角色是一组以ROLE_开头的特定权限。

假设您有以下权限:

GrantedAuthority authority1 = new SimpleGrantedAuthority("User");
GrantedAuthority authority2 = new SimpleGrantedAuthority("ROLE_Admin");
在这种情况下,authority1 不包含任何角色信息,而 authority2 包含了一个以 ROLE_ 为前缀的角色信息。
这意味着,如果你使用 hasRole("User"),你将无法获得访问权限,因为它没有被定义为角色。另一方面,hasRole("Admin") 将会起作用。
要解决这个问题,你有两个选择:
  1. 确保你的角色名称都以 ROLE_ 为前缀。如果你在数据库中没有以这种方式存储它们,你可以修改你的 UserDetailsServiceImpl
String roleName = "ROLE_" + role.getRole().toString();
grantedAuthorities.add(new SimpleGrantedAuthority(roleName));
  • 或者,您可以改用 hasAuthority("User")

    // ...
    .antMatchers("/employee", "/insurance").hasAuthority("User")
    // ...
    

  • 谢谢您的回答。我只是想让您知道,在安全配置类中更新了.antMatchers("/hr-core/employee/", "/hr-core/insurance/").hasRole("User")之后,REST端点现在是可访问的。但我不确定在安全类中硬编码上下文路径是否是正确的方法。 - Karthik
    @Karthik 你不应该这样做。你原来的antmatcher很好用(除了如果所有子路径也要工作,你可以在末尾添加/**)。现在你已经使那个antmatcher失效了,因为它现在会寻找/hr-core/hr-core/employee/**。由于它不匹配,它将退回到anyRequest().authenticated()。所以现在,你的基于角色的授权被绕过了(你可以尝试访问端点,而没有正确的“User”角色来测试这一点)。 - g00glen00b
    你说得完全正确。基于角色的授权被绕过了。我会根据你的答案更新我的代码并进行检查。再次感谢。 - Karthik
    你救了我的一天! - Hafiz Hamza

    0
    这是我解决REST API访问错误的方法。当我调用API时,它会给我返回403错误。 为了解决这个问题,我做了以下更改。
    1. 使用mvcMatcher而不是antMatcher进行API映射
    2. 角色以名称形式提供,如“USER”或“ADMIN”,而不是“ROLE_USER”或“ROLE_ADMIN”

    以下是代码:

    @Configuration
    @EnableWebSecurity
    public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
        
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
            .csrf().disable().authorizeRequests()
            .and()
            .addFilter(new ApplicationAuthorizationFilter(authenticationManager()))
            .authorizeRequests()
            .antMatchers(ApplicationConstants.DEFAULT_API_CHECK_PATH).permitAll()
            .mvcMatchers("/app/users/**/**").hasAnyRole("USER", "ADMIN")
            .and()
            .sessionManagement()
            .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        }
    
        @Autowired
        public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
            auth
                .inMemoryAuthentication()
                    .withUser("abc").password("xyz").roles("READONLY")  ;
        }
        
        @Bean
        public PasswordEncoder encoder() {
            return new BCryptPasswordEncoder(ApplicationConstants.ENCODER_STRENGTH);
        }
        
        
    }
    

    Spring Security 中有一种机制可以确定在 GrantedAuthority 值之前是否应添加前缀以及该前缀应是什么。默认情况下为空,因为在我的情况下我没有设置任何内容。

    之前我试图将角色名称传递为“ROLE_USER”,但失败了。


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接