为啥应用程序看不到 Spring Security 中的角色(禁止)

Posted

技术标签:

【中文标题】为啥应用程序看不到 Spring Security 中的角色(禁止)【英文标题】:Why the application does not see the Roles in Spring Security (Forbidden)为什么应用程序看不到 Spring Security 中的角色(禁止) 【发布时间】:2020-08-24 11:51:58 【问题描述】:

有一个Spring-MVC 项目,其中有三种类型的用户:CustomerAdminCook。它们都继承自类User。无需ENUM,只需通过静态字符串常量(在User 类中显示)即可创建角色。添加Spring Security 后,授权成功,但是当我尝试执行其中一个类(Customer、Admin 或 Cook)的方法时,它给出 json 错误 403


  "timestamp": "2020-05-08T19:48:43.999+0000",
  "status": 403,
  "error": "Forbidden",
  "message": "Forbidden",
  "path": "/admin/cooks"

请告诉我我做错了什么。哪里出错了。

用户:

package com.tinychiefdelights.model;

import io.swagger.annotations.ApiModel;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import javax.persistence.*;
import javax.validation.constraints.Size;
import java.util.Collection;
import java.util.Collections;

@ApiModel
@Data
@Entity
@Table(name = "pg_user", schema = "public")
public class User implements UserDetails 

    // Roles
    //
    public static final String ROLE_ADMIN = "ADMIN";
    public static final String ROLE_CUSTOMER = "CUSTOMER";
    public static final String ROLE_COOK = "COOK";
    //


    public User()  // Пустой конструктор для Hibernate

    


    // Поля
    private @Id
    @GeneratedValue
    Long id;

    @Column(name = "login")
    private String login;

    @Size(min = 5, max = 30)
    @Column(name = "password")
    private String password;

    @Column(name = "role")
    private String role;

    @Column(name = "name")
    private String name;

    @Column(name = "last_name")
    private String lastName;


    // Методы
    //
    // GrantedAuthority
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() 
        return Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + role));
    


    // userName == login (одно и тоже)
    @Override
    public String getUsername() 
        return login;
    


    // Во всех флагах стоит TRUE, так как они не используются
    @Override
    public boolean isAccountNonExpired() 
        return true;
    


    @Override
    public boolean isAccountNonLocked() 
        return true;
    


    @Override
    public boolean isCredentialsNonExpired() 
        return true;
    


    @Override
    public boolean isEnabled() 
        return true;
    
    //

例如我只添加一个类 Admin:

管理员:

package com.tinychiefdelights.model;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import javax.persistence.*;

@ApiModel
@Data
@Entity
@Table(name = "pg_user", schema = "public")
public class Admin 

    public Admin()  // Пустой конструктор для Hibernate

    

    // Поля

    // name, lastName, login, password берем от класса User через связи;

    @ApiModelProperty
    private @Id
    @GeneratedValue
    Long id;


    // Relationships
    //
    @ApiModelProperty
    @OneToOne
    @JoinColumn(name = "id") // Join without Admin in User class
    private User user;

管理服务:

@Service
public class AdminService extends UserService 

    // Поля
    //
    // Injects in setters
    private AdminRepository adminRepository; // Администратор

    private OrderRepository orderRepository; // Заказ

    private CookRepository cookRepository; // Повар

    private CustomerRepository customerRepository; // Заказчик


    // Getters and Setters
    //
    // Делаем inject через сеттеры
    @Autowired
    public void setAdminRepository(AdminRepository adminRepository) 
        this.adminRepository = adminRepository;
    

    @Autowired
    public void setOrderRepository(OrderRepository orderRepository) 
        this.orderRepository = orderRepository;
    

    @Autowired
    public void setCookRepository(CookRepository cookRepository) 
        this.cookRepository = cookRepository;
    

    @Autowired
    public void setCustomerRepository(CustomerRepository customerRepository) 
        this.customerRepository = customerRepository;
    


    // Методы
    //
    // Вывод списка всех заказов
    public List<Order> getAllOrders() 
        return orderRepository.findAll();
    


    // Вывод информации по конкретному заказу
    public Order getOrderInfo(Long id) 
        try 
            return orderRepository.getById(id);
         catch (NotFoundException e) 
            throw new NotFoundException(id);
        
    


    // Вывод Повара по ID
    public Cook getCook(Long id) 
        try 
            return cookRepository.getByIdAndUserRole(id, "COOK");
         catch (NotFoundException e) 
            throw new NotFoundException(id);
         catch (IllegalArgumentException e) 
            throw new IllegalArgumentException();
        
    


    // Изменить карту повара
    public void editCook(Long id, User user, float rating, String aboutCook) 
        Cook cook = cookRepository.getByIdAndUserRole(id, "COOK");
        try 
            cook.setUser(user);
            cook.setRating(rating);
            cook.setAboutCook(aboutCook);
         catch (IllegalArgumentException e) 
            throw new IllegalArgumentException();
         catch (NotFoundException e) 
            throw new NotFoundException(id);
        
    


    // Вывод всех поваров
    public List<Cook> getAllCooks() 
        return cookRepository.findByUserRole("COOK");
    


    // Удалить Повара
    public void deleteCook(Long id) 
        Cook cook = cookRepository.getByIdAndUserRole(id, "COOK");
        try 
            cookRepository.delete(cook);
         catch (Exception e) 
            throw new NotFoundException(id);
        
    


    // Вывод всех Заказчиков
    public List<Customer> getAllCustomers() 
        return customerRepository.findByUserRole("CUSTOMER");
    


    // Вывод Заказчика по ID
    public Customer getCustomer(Long id) 
        try 
            return customerRepository.getByIdAndUserRole(id, "CUSTOMER");
         catch (NotFoundException e) 
            throw new NotFoundException(id);
         catch (IllegalArgumentException e) 
            throw new IllegalArgumentException();
        
    

管理员控制器:

@Api(value = "Работа с Админом", tags = "Администратор")
@RestController
@RequestMapping("/admin")
@RolesAllowed("ADMIN")
public class AdminController 

    // Constructor
    //
    // Inject через конструктор
    @Autowired
    public AdminController(AdminRepository adminRepository, AdminService adminService, UserService userService) 
        this.adminRepository = adminRepository;
        this.adminService = adminService;
        this.userService = userService;
    


    // Поля
    // All injects into constructor
    private final AdminRepository adminRepository;

    private final AdminService adminService;

    private final UserService userService;


    // Методы
    //
    // GET MAPPING
    //
    // Вывод списка всех заказов
    @GetMapping("/orders")
    List<Order> getAllOrders() 
        return adminService.getAllOrders();
    


    // Вывод информации по конкретному заказу по ID
    @GetMapping("/order/id")
    Order getOrderInfo(@PathVariable Long id) 
        return adminService.getOrderInfo(id);
    


    // Вывод всех Поваров
    @GetMapping("/cooks")
    List<Cook> getAllCooks() 
        return adminService.getAllCooks();
    


    // Вывод Повара по ID
    @GetMapping("/cook/id")
    Cook getCook(@PathVariable Long id) 
        return adminService.getCook(id);
    


    // Вывод всех пользователей
    @GetMapping("/customers")
    List<Customer> getAllCustomer() 
        return adminService.getAllCustomers();
    


    // Вывод Заказчика по ID
    @GetMapping("/customer/id")
    Customer getCustomer(@PathVariable Long id) 
        return adminService.getCustomer(id);
    


    // POST MAPPING
    //


    // PUT MAPPING
    //
    // Изменяем Повара по ID
    @PutMapping("/edit/cook/id")
    void editCook(@PathVariable Long id, User user, @PathVariable float rating, String aboutCook) 
        adminService.editCook(id, user, rating, aboutCook);
    

    // Поменять пароль
    @PutMapping("/change/password")
    void changePassword(@RequestParam String login, @RequestParam String newPass) 
        userService.changePassword(login, newPass);
    


    // DELETE MAPPING
    //
    // Удалить конкретного Повара по ID
    @DeleteMapping("/delete/cook/id")
    void removeCook(@PathVariable Long id) 
        adminService.deleteCook(id);
    

我使用注释@RolesAllowed

SpringWebConfig:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(jsr250Enabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter 


    // Поля
    //
    private UserService userService;

    private PasswordEncoder passwordEncoder;



    // Injects in SETTERS
    //
    @Autowired
    public void setUserService(UserService userService) 
        this.userService = userService;
    

    @Autowired
    public void setPasswordEncoder(PasswordEncoder passwordEncoder) 
        this.passwordEncoder = passwordEncoder;
    



    // Methods
    //
    // Тут мы переопределяем метод конфигураций
    @Override
    protected void configure(HttpSecurity http) throws Exception 
        http
                .authorizeRequests()
                .antMatchers("/", "/home").permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
                .logout()
                .permitAll();
    


    // Тут мы переопределяем для работы с внешней БД
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception 
        auth.userDetailsService(userService).passwordEncoder(passwordEncoder);
    


    // Beans
    //
    @Bean
    public DaoAuthenticationProvider authenticationProvider() 
        DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
        authenticationProvider.setUserDetailsService(userService);
        authenticationProvider.setPasswordEncoder(passwordEncoder);
        return authenticationProvider;
    


    @Bean
    public static PasswordEncoder getPasswordEncoder() 
        return new BCryptPasswordEncoder(8);
    


    // Возвращаем сервис пользовател для userDetService
    @Bean
    public UserDetailsService userDetailsService() 
        return userService;
    

【问题讨论】:

尝试调试用户主体对象以查看其中的内容。您可以将 API 设置为已验证并打印出对象 SecurityContextHolder.getContext().getAuthentication()。您的角色映射可能已关闭/角色为空,但它们位于 authorities 我添加了一个调试屏幕。我已经不能处理这 4 天了。你想说如果我在你的SecurityContextHolder.getContext().getAuthentication() 上更改SecurityContextHolder.getContext (). GetAuthentication (). GetPrincipal () 会更好吗? UserService 是什么,可以贴在这里吗? 您能否粘贴得到响应 403 的确切请求(包括标头)? 需要在 grunted 权限中传递 ROLE_ADMIN.. 我认为您在 userService 类中将 ADMIN 传递给 spring security grunted 权限 【参考方案1】:

您应该将@RolesAllowed("ADMIN") 更改为@RolesAllowed("ROLE_ADMIN") 以匹配角色名称的常规前缀(您在User::getAuthorities 中使用的前缀)。

【讨论】:

Дмитрий, может быть у меня отсутствует SecurityContextHolder.getContext().getAuthentication().getAuthorities() ?【参考方案2】:

要解决这个问题,在最开始的configure 方法的WebSecurityConfig 类中添加以下内容:

http
.cors().disable()
.csrf().disable()

【讨论】:

以上是关于为啥应用程序看不到 Spring Security 中的角色(禁止)的主要内容,如果未能解决你的问题,请参考以下文章

为啥 Spring Security 使用默认的预认证检查?

为啥 Spring Security 的 BindAuthenticator 需要用户读取权限?

为啥 https 中使用 Spring Security 添加端口 80?

为啥 Spring Security getAuthentication() 在服务器上返回 null

spring security:为啥我们不能在@PreAuthorize 中访问 Hibernate 实体参数?

为啥spring-security-oauth oauth 2.0实现中需要scope参数