【问题标题】:DELETE and PUT endpoints don't work after implementing Spring Security实现 Spring Security 后,DELETE 和 PUT 端点不起作用
【发布时间】:2022-01-12 13:14:11
【问题描述】:

我目前正在开发一个带有 React 前端和 Spring Boot 后端的全栈 Web 应用程序。我已经实现了 Spring 安全性和 JWT 进行身份验证,但从那时起我就无法访问我的 API 端点(请参阅控制器)。我已经设法访问了 GET 请求端点,但是尽管在开始请求之前登录了后端,但 PUT 或 DELETE 请求似乎都不起作用。

我在另一篇文章中看到禁用 csrf 解决了这个问题,但我从来没有启用它,所以这对我不起作用。

WebSecurityConfig 文件:

@Configuration
@AllArgsConstructor
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .authorizeRequests()
                    .antMatchers("/api/v*/registration/**")
                    .permitAll()
                .anyRequest()
                .authenticated().and()
                .formLogin();
    }

控制器(REST API)

@RestController
@RequestMapping(path = "/question")
@CrossOrigin("*")
public class QuestionController {

    private final QuestionService questionService;

    @Autowired
    public QuestionController(QuestionService questionService) {
        this.questionService = questionService;
    }

    @CrossOrigin("*")
    @GetMapping("/all")
    public ResponseEntity<List<Question>> getAllQuestions() {
        List<Question> questions = questionService.findAllQuestions();
        return new ResponseEntity<>(questions, HttpStatus.OK);
    }

    @CrossOrigin("*")
    @GetMapping("/find/{id}")
    public ResponseEntity<Question> getQuestionById(@PathVariable("id") Long id) {
        Question question = questionService.findQuestionById(id);
        return new ResponseEntity<>(question, HttpStatus.OK);
    }

    @CrossOrigin("*")
    @PostMapping("/add")
    public ResponseEntity<Question> addQuestion(@RequestBody Question question) {
        Question newQuestion = questionService.addQuestion(question);
        return new ResponseEntity<>(newQuestion, HttpStatus.CREATED);
    }

    @CrossOrigin("*")
    @PutMapping("/update/{id}")
    public ResponseEntity<Question> updateQuestion(@RequestBody Question question) {
        Question updateQuestion = questionService.updateQuestion(question);
        return new ResponseEntity<>(updateQuestion, HttpStatus.OK);
    }

    @CrossOrigin("*")
    @DeleteMapping("(/delete/{id}")
    public ResponseEntity<Question> deleteQuestion(@PathVariable("id") Long id) {
        questionService.deleteQuestion(id);
        return new ResponseEntity<>(HttpStatus.OK);
    }
}

有效的 GET 请求端点示例:

无效的 DELETE 请求端点示例:

编辑:这是实现 UserDetailsS​​ervice 的代码

@Service
@Autowired can be left out by using this annotation.
@AllArgsConstructor
public class BenutzerkontoService implements UserDetailsService {

    private final static String USER_NOT_FOUND_MSG = "User with email %s not found";

    private final BenutzerkontoRepository benutzerkontoRepository;
    private final BCryptPasswordEncoder bCryptPasswordEncoder;
    private final ConfirmationTokenService confirmationTokenService;

    public List<Benutzerkonto> findAllBenutzerkonto() {
        // findAll() returns a list of all user objects
        return benutzerkontoRepository.findAll();
    }

    /**
     * This method is responsible for identifying the given email inside the database.
     *
     * @param email
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
        return benutzerkontoRepository.findByEmail(email).orElseThrow(() -> new UsernameNotFoundException(String.format(USER_NOT_FOUND_MSG, email)));
    }

    /**
     * The following function checks, whether the user already exists (by email) and registers the user with an
     * encoded password, if the email address does not exist already.
     *
     * The user also gets a random JSON Web Token assigned
     *
     * @param benutzerkonto
     * @return
     */
    public String signUpUser(Benutzerkonto benutzerkonto) {
        // Check whether user exists
        boolean userExists = benutzerkontoRepository.findByEmail(benutzerkonto.getEmail()).isPresent();

        if (userExists) {
            throw new IllegalStateException("Email is already taken");
        }

        // Encode the user password
        String encodedPassword = bCryptPasswordEncoder.encode(benutzerkonto.getPassword());

        // Replace the plain text password with the encoded version
        benutzerkonto.setPasswort(encodedPassword);

        // Save user to database
        benutzerkontoRepository.save(benutzerkonto);

        // Create random String via the UUID class for using it as token
        String token = UUID.randomUUID().toString();

        // Instantiate ConfirmationToken class, which defines the token for account confirmation
        ConfirmationToken confirmationToken = new ConfirmationToken(
                token,
                LocalDateTime.now(),
                // Make token invalid after 15 minutes
                LocalDateTime.now().plusMinutes(15),
                benutzerkonto
        );

        // Save token to database
        // TODO: Shouldn't it be saved by a confirmationTokenRepository object? Why does this also work?
        confirmationTokenService.saveConfirmationToken(confirmationToken);

        return token;
    }

    /**
     * This function takes the email address as a parameter and enables/activates the email for logging in.
     *
     * @param email
     * @return
     */
    public int enableAppUser(String email) {
        return benutzerkontoRepository.enableAppUser(email);
    }

    /**
     * This method adds a new user account to the database, but it searches for the passed value of email
     * inside the database first. The user object "benutzerkonto" will only be saved in the database repository,
     * if the email does not exist already.
     *
     * @param benutzerkonto
    */
    public void addNewUser(Benutzerkonto benutzerkonto) {
        // userEmailPresence can be null, if the email does not exist in the database yet, which is why it's an Optional.
        Optional<Benutzerkonto> userEmailPresence = benutzerkontoRepository.findBenutzerkontoByEmail(benutzerkonto.getUsername());
        if (userEmailPresence.isPresent()) {
            throw new IllegalStateException("Email already taken.");
        } else {
            benutzerkontoRepository.save(benutzerkonto);
        }

    }

}

Edit2:这是用户类

@Getter
@Setter
@EqualsAndHashCode
@NoArgsConstructor
@Entity
@Table

public class Benutzerkonto implements Serializable, UserDetails {

    @SequenceGenerator(
            name = "student_sequence",
            sequenceName = "student_sequence",
            allocationSize = 1
    )

    @Id
    @GeneratedValue(
            strategy = GenerationType.SEQUENCE,
            generator = "student_sequence"
    )
    @Column(nullable = false)
    private Long id;
    private String email;
    private String passwort;
    @Column(nullable = false, updatable = false)
    @Enumerated(EnumType.STRING)
    private UserRole rolle;
    private Boolean locked = false;
    // false by default, because user has to confirm via email first
    private  Boolean enabled = false;

    // Constructor
    public Benutzerkonto(String email, String passwort, UserRole rolle) {
        this.email = email;
        this.passwort = passwort;
        this.rolle = rolle;
    }

    @Override
    public String toString() {
        return "Benutzerkonto{" +
                "id=" + id +
                ", email='" + email + '\'' +
                ", passwort='" + passwort + '\'' +
                ", rolle=" + rolle +
                ", locked=" + locked +
                ", enabled=" + enabled +
                '}';
    }

    // Methods of UserDetails interface
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        SimpleGrantedAuthority authority = new SimpleGrantedAuthority(rolle.name());
        return Collections.singletonList(authority);
    }

    @Override
    public String getPassword() {
        return passwort;
    }

    @Override
    public String getUsername() {
        return email;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return !locked;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return enabled;
    }
}

【问题讨论】:

  • 但是,尽管在开始请求之前在后端登录,但 PUT 或 DELETE 请求似乎都不起作用。 当然,您必须先进行身份验证,这就是您使用 @ 实现的987654329@.
  • 您是否在方法deleteQuestion 中添加了断点?您的响应是 404,这意味着您的控制器方法未找到或该方法找不到对象。
  • 没错,我已经通过 Spring Security 登录表单登录了后端,然后访问了删除端点。我还没想通,为什么还没找到呢

标签: java spring-boot api spring-security


【解决方案1】:

因此,除了发送至/api/v*/registration/** 的请求外,其他请求都是安全的。这是什么意思?这意味着在您拥有具有授权角色的授权用户之前,您无法访问任何其他端点。所以你需要做一些事情,比如:

  1. 实现包org.springframework.security.core.userdetailsUserDetails并实现方法:

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
      return roles == null?null:roles.stream().map(m->new SimpleGrantedAuthority(m.getAuthority())).collect(Collectors.toSet());
     }
    
  2. 将角色添加到您的实体类:

    @OneToMany(fetch = FetchType.EAGER,cascade = CascadeType.PERSIST)
    @JoinTable(
         name = "user_role",
         joinColumns = @JoinColumn(
                 name = "user_id",
                 referencedColumnName = "id"
         ),
         inverseJoinColumns = @JoinColumn(
                 name = "role_id",
                 referencedColumnName = "id"
         ))
    private List<Role> roles;
    
  3. 在您的端点中使用这些角色:

    @PreAuthorize(hasRole('ROLE_role_name'))
    @GetMapping(path = EndPoint.PATIENT_HOME, consumes = "application/json", produces = "application/json")
    public ResponseEntity<YourDTO> Home(Principal principal) {
    
        return new ResponseEntity<YourDTO>(yourDTO, HttpStatus.OK);
    }
    

【讨论】:

  • @lu_lu 您是否已将 @PreAuthorize(hasRole('ROLE_role_name')) 添加到失败的端点?
  • 您只需将代码粘贴到 stackoverflow 文本字段中,用鼠标标记它,然后单击 stackoverflow 文本编辑器中的 { } 按钮,它会自动应用语法高亮
  • 不幸的是,关于 getAuthorities 方法,您的第一步会干扰我的用户类 (benutzerkonto) 实现。所以我还不能使用你的解决方案,但是我已经编辑了我的帖子,也许你可以看看它
  • @lu_lu 将单个角色添加到角色列表并删除角色的构造函数参数
  • @lu_lu 这个工作吗?如果是这样,您可以接受答案
猜你喜欢
  • 2015-02-08
  • 2017-07-17
  • 2016-11-01
  • 1970-01-01
  • 1970-01-01
  • 2013-12-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多