繁体   English   中英

从 Raspberry Pi 上的 Spring Boot Rest 服务获取 403 响应

[英]Getting 403 Response from Spring Boot Rest Service on Raspberry Pi

我的设置:

我有一个调用 spring boot rest api 的 vue 前端。 在本地,我在单独的 docker 容器中运行它,在我的本地机器上一切正常。

出于测试目的,我将树莓派连接到我的路由器。 为此,我还在不同的 docker 容器中运行前端和 spring boot。

这些设置之间的唯一区别是,我不能在我的树莓派上使用FROM openjdk:11映像,因为它不支持 arm32v7。 在那里我使用了eclipse-temurin:11容器。

这是我的 docker-compose.yml:

version: "3.4"

services:
  db:
    image: jsurf/rpi-mariadb
    environment:
      MYSQL_ROOT_PASSWORD: 'admin'
      MYSQL_DATABASE: 'ghp_board'
      MYSQL_USER: 'ghp_board_admin'
      MYSQL_PASSWORD: 'ghp_board'
    volumes:
      - ./data/mariadb:/docker-entrypoint-initdb.d
    ports:
      - "33006:3306"

  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile
    ports:
      - "8081:8080"
    container_name: ghp_board_frontend
    volumes:
      - ./frontend:/usr/src/app/ghp_board_frontend
    depends_on:
      - db

  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile
    depends_on:
      - db
    ports:
      - "8886:8080"

这是我用于后端容器的Dockerfile

FROM eclipse-temurin:11

COPY ./system/build/libs/system-0.0.1-SNAPSHOT.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

我想要什么

Spring Boot 应用程序现在有两个控制器(News 和 Demand),它们应该通过 REST API 向前端提供数据。 两个控制器都有几乎相同的端点:

import org.springframework.web.multipart.MultipartFile;
import org.webjars.NotFoundException;

import javax.lang.model.element.NestingKind;
import javax.persistence.EntityNotFoundException;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Optional;

@RestController
@AllArgsConstructor
@CrossOrigin
public class NewsController {

    @Autowired
    private final NewsService newsService;
    private JwtAuthenticationService jwtAuthenticationService;

    @GetMapping(path = "/api/v1/news", produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<?> getAllNews(HttpServletRequest request) {
        return new ResponseEntity<>(newsService.getAllNewsEntries(), HttpStatus.OK);
    }

    @GetMapping(path = "/api/v1/news/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<?> getNews(@PathVariable("id") Long id, HttpServletRequest request) {

        return new ResponseEntity<>(newsService.getNewsEntry(id), HttpStatus.OK);
    }

    // Post -> receive a new NewsEntry
    @PostMapping(
            path = "/api/v1/news",
            consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
            produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<?> createNews(@RequestParam("file") Optional<MultipartFile> file, @RequestParam("newsData") String newsEntryString, HttpServletRequest request) throws IOException {

        ObjectMapper mapper = new ObjectMapper()
                .findAndRegisterModules()
                .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        NewsEntryDto newsEntryDto = mapper.readValue(newsEntryString, NewsEntryDto.class);

        return new ResponseEntity<>(newsService.insertNewNewsEntry(newsEntryDto, file), HttpStatus.OK);
    }

    //Put -> update an existing NewsEntry
    @PutMapping(
            path = "/api/v1/news",
            consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
            produces = MediaType.APPLICATION_JSON_VALUE
    )
    public ResponseEntity<?> updateNews(
            @RequestParam("file") Optional<MultipartFile> file,
            @RequestParam("newsUpdateId") Long newsUpdateId,
            @RequestParam("newsData") String newsUpdateString,
            HttpServletRequest request) throws IOException
    {
        ObjectMapper mapper = new ObjectMapper()
                .findAndRegisterModules()
                .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        NewsEntryDto newsUpdateDto = mapper.readValue(newsUpdateString, NewsEntryDto.class);

        return new ResponseEntity<>(newsService.updateNewsEntry(newsUpdateId, newsUpdateDto, file), HttpStatus.OK);
    }

    //Delete -> delete an existing NewsEntry
    @DeleteMapping(
            path = "/api/v1/news/{id}"
    )
    public ResponseEntity<?> deleteNews(@PathVariable("id") Long newsDeleteId) {
        newsService.deleteNewsEntry(newsDeleteId);
        return new ResponseEntity<>("", HttpStatus.OK);
    }
}
package com.example.system.demand;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import lombok.AllArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.webjars.NotFoundException;

import java.util.List;
import java.util.Optional;

@RestController
@AllArgsConstructor
@CrossOrigin
public class DemandController {

    private DemandService demandService;

    @GetMapping(path = "/api/v1/demand", produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<?> getAllDemands() {
        return new ResponseEntity<>(demandService.getAllDemandEntries(), HttpStatus.OK);
    }

    @GetMapping(path = "/api/v1/demand/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<?> getDemandById(@PathVariable("id") Long id) {
        return new ResponseEntity<>(demandService.getDemandById(id), HttpStatus.OK);
    }

    @PostMapping(
            path = "/api/v1/demand",
            consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
            produces = MediaType.APPLICATION_JSON_VALUE
    )
    public ResponseEntity<?> insertNewDemand(
            @RequestParam Optional<List<MultipartFile>> files,
            @RequestParam String demandData
    ) throws JsonProcessingException {

        ObjectMapper mapper = new ObjectMapper()
                .findAndRegisterModules()
                .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        DemandEntryDto demandEntryDto = mapper.readValue(demandData, DemandEntryDto.class);

        return new ResponseEntity<>(demandService.insertNewDemandEntry(demandEntryDto, files), HttpStatus.OK);
    }

    @ExceptionHandler(NotFoundException.class)
    public ResponseEntity<?> handlerNotFoundException(NotFoundException ex) {
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.NOT_FOUND);
    }
}

我的问题:

当我从前端调用 GET-Endpoint /api/v1/news时,我从数据库中获取所有条目,所以这是有效的。 GET-Endpoint /api/v1/news/{id}和 POST-Endpoint 也可以正常工作。 但是 NewsController 的其他 Endpoints PUT 和 DELETE 以及 DemandController 的所有 Endpoints 正在接收 Responsecode 403 - Forbidden 我真的不明白为什么会这样。

我的WebSecurityConfig不应该有任何解释这种行为的规范:

package com.example.system.security;

import com.example.system.security.jwt.JwtAuthenticationEntrypoint;
import com.example.system.security.jwt.JwtAuthenticationProvider;
import com.example.system.security.jwt.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@SuppressWarnings("SpringJavaAutowiringInspection")
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private JwtAuthenticationEntrypoint unauthorizedHandler;  // Gibt 403 zurück, wenn nicht authorisiert

    @Autowired
    private JwtAuthenticationProvider jwtAuthenticationProvider;

    @Autowired
    public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) {
        authenticationManagerBuilder.authenticationProvider(jwtAuthenticationProvider);
    }

    @Bean
    public JwtAuthenticationTokenFilter authenticationTokenFilterBean() {
        return new JwtAuthenticationTokenFilter();
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                .csrf().disable()
                .cors().and() // Aktivieren von Cross-Site
                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() // Gibt 403 zurück, wenn nicht authorisiert
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                .authorizeRequests()
                    .antMatchers("/")
                    .permitAll()
                    .antMatchers("/home")
                    .permitAll()
                    .antMatchers("/api/v1/login")
                    .permitAll()
                .anyRequest().authenticated();

        httpSecurity.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
        httpSecurity.headers().cacheControl();
    }
}

并且 axois 调用的标头也是相同的:

getNews() {
        const token = localStorage.getItem('token');

        const header = {
            'Content-Type' : 'application/x-www-form-urlencoded',
            'Authorization': `Bearer ${token}`,
            'Access-Control-Allow-Origin' : '*',
            'Access-Control-Allow-Methods':'GET,PUT,POST,DELETE,PATCH,OPTIONS',
        }

        return axios.get(process.env.VUE_APP_BACKEND_URL + "/api/v1/news", {
            headers: header
        });
    },
    getNewsDetail(id) {
        const token = localStorage.getItem('token');

        const header = {
            'Content-Type' : 'application/x-www-form-urlencoded',
            'Authorization': `Bearer ${token}`,
            'Access-Control-Allow-Origin' : '*',
            'Access-Control-Allow-Methods':'GET,PUT,POST,DELETE,PATCH,OPTIONS',
        }

        return axios.get(process.env.VUE_APP_BACKEND_URL + "/api/v1/news/" + id, {
            headers: header
        });
    },
    insertNewsEntry(data) {
        const token = localStorage.getItem('token');
        const header = {
            //'Content-Type' : 'application/json',
            'Content-Type' : 'multipart/form-data',
            'Authorization': `Bearer ${token}`,
            'Access-Control-Allow-Origin' : '*',
            'Access-Control-Allow-Methods':'GET,PUT,POST,DELETE,PATCH,OPTIONS',
        }

        return axios.post(process.env.VUE_APP_BACKEND_URL + "/api/v1/news", data, {
            headers: header
        });

    },
    updateNewsEntry(data) {
        const token = localStorage.getItem('token');
        const header = {
            //'Content-Type' : 'application/json',
            'Content-Type' : 'multipart/form-data',
            'Authorization': `Bearer ${token}`,
            'Access-Control-Allow-Origin' : '*',
            'Access-Control-Allow-Methods':'GET,PUT,POST,DELETE,PATCH,OPTIONS',
        }

        return axios.put(process.env.VUE_APP_BACKEND_URL + "/api/v1/news", data, {
            headers: header
        });
    },
    deleteNewsEntry(id) {
        const token = localStorage.getItem('token');
        const header = {
            //'Content-Type' : 'application/json',
            'Content-Type' : 'multipart/form-data',
            'Authorization': `Bearer ${token}`,
            'Access-Control-Allow-Origin' : '*',
            'Access-Control-Allow-Methods':'GET,PUT,POST,DELETE,PATCH,OPTIONS',
        }

        return axios.delete(process.env.VUE_APP_BACKEND_URL + "/api/v1/news/" + id, {
            headers: header
        });
    }

有谁知道我在这里错过了什么?

对于任何未来的读者,如果评论被删除,我建议使用

http.cors().and().csrf().disable()...而不是.csrf().disable().cors().and()..configure方法中作为cors & 的使用顺序csrf在原始帖子中不正确,问题得到解决后记。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM