스프링 REST + 스프링 보안 예제
89284 단어 springspringboot
프로젝트 디렉토리

메이븐
스프링 보안을 위한 spring-boot-starter-security 포함
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>springrestsecurity</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-rest-security</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
프로젝트 종속성:
> mvn dependency:tree
[INFO] Scanning for projects...
[INFO]
[INFO] -------------------< com.example:springrestsecurity >-------------------
[INFO] Building spring-rest-security 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-dependency-plugin:3.3.0:tree (default-cli) @ springrestsecurity ---
[INFO] com.example:springrestsecurity:jar:0.0.1-SNAPSHOT
[INFO] +- org.springframework.boot:spring-boot-starter-thymeleaf:jar:2.7.1:compile
[INFO] | +- org.springframework.boot:spring-boot-starter:jar:2.7.1:compile
[INFO] | | +- org.springframework.boot:spring-boot-starter-logging:jar:2.7.1:compile
[INFO] | | | +- ch.qos.logback:logback-classic:jar:1.2.11:compile
[INFO] | | | | \- ch.qos.logback:logback-core:jar:1.2.11:compile
[INFO] | | | +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.17.2:compile
[INFO] | | | | \- org.apache.logging.log4j:log4j-api:jar:2.17.2:compile
[INFO] | | | \- org.slf4j:jul-to-slf4j:jar:1.7.36:compile
[INFO] | | +- jakarta.annotation:jakarta.annotation-api:jar:1.3.5:compile
[INFO] | | \- org.yaml:snakeyaml:jar:1.30:compile
[INFO] | +- org.thymeleaf:thymeleaf-spring5:jar:3.0.15.RELEASE:compile
[INFO] | | +- org.thymeleaf:thymeleaf:jar:3.0.15.RELEASE:compile
[INFO] | | | +- org.attoparser:attoparser:jar:2.0.5.RELEASE:compile
[INFO] | | | \- org.unbescape:unbescape:jar:1.1.6.RELEASE:compile
[INFO] | | \- org.slf4j:slf4j-api:jar:1.7.36:compile
[INFO] | \- org.thymeleaf.extras:thymeleaf-extras-java8time:jar:3.0.4.RELEASE:compile
[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:2.7.1:compile
[INFO] | +- org.springframework.boot:spring-boot-starter-json:jar:2.7.1:compile
[INFO] | | +- com.fasterxml.jackson.core:jackson-databind:jar:2.13.3:compile
[INFO] | | | +- com.fasterxml.jackson.core:jackson-annotations:jar:2.13.3:compile
[INFO] | | | \- com.fasterxml.jackson.core:jackson-core:jar:2.13.3:compile
[INFO] | | +- com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:2.13.3:compile
[INFO] | | +- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:2.13.3:compile
[INFO] | | \- com.fasterxml.jackson.module:jackson-module-parameter-names:jar:2.13.3:compile
[INFO] | +- org.springframework.boot:spring-boot-starter-tomcat:jar:2.7.1:compile
[INFO] | | +- org.apache.tomcat.embed:tomcat-embed-core:jar:9.0.64:compile
[INFO] | | +- org.apache.tomcat.embed:tomcat-embed-el:jar:9.0.64:compile
[INFO] | | \- org.apache.tomcat.embed:tomcat-embed-websocket:jar:9.0.64:compile
[INFO] | +- org.springframework:spring-web:jar:5.3.21:compile
[INFO] | | \- org.springframework:spring-beans:jar:5.3.21:compile
[INFO] | \- org.springframework:spring-webmvc:jar:5.3.21:compile
[INFO] | +- org.springframework:spring-context:jar:5.3.21:compile
[INFO] | \- org.springframework:spring-expression:jar:5.3.21:compile
[INFO] +- org.springframework.boot:spring-boot-configuration-processor:jar:2.7.1:compile
[INFO] +- org.projectlombok:lombok:jar:1.18.24:compile
[INFO] +- org.springframework.boot:spring-boot-starter-test:jar:2.7.1:test
[INFO] | +- org.springframework.boot:spring-boot-test:jar:2.7.1:test
[INFO] | +- org.springframework.boot:spring-boot-test-autoconfigure:jar:2.7.1:test
[INFO] | +- com.jayway.jsonpath:json-path:jar:2.7.0:test
[INFO] | | \- net.minidev:json-smart:jar:2.4.8:test
[INFO] | | \- net.minidev:accessors-smart:jar:2.4.8:test
[INFO] | | \- org.ow2.asm:asm:jar:9.1:test
[INFO] | +- jakarta.xml.bind:jakarta.xml.bind-api:jar:2.3.3:compile
[INFO] | | \- jakarta.activation:jakarta.activation-api:jar:1.2.2:compile
[INFO] | +- org.assertj:assertj-core:jar:3.22.0:test
[INFO] | +- org.hamcrest:hamcrest:jar:2.2:test
[INFO] | +- org.junit.jupiter:junit-jupiter:jar:5.8.2:test
[INFO] | | +- org.junit.jupiter:junit-jupiter-api:jar:5.8.2:test
[INFO] | | | +- org.opentest4j:opentest4j:jar:1.2.0:test
[INFO] | | | +- org.junit.platform:junit-platform-commons:jar:1.8.2:test
[INFO] | | | \- org.apiguardian:apiguardian-api:jar:1.1.2:test
[INFO] | | +- org.junit.jupiter:junit-jupiter-params:jar:5.8.2:test
[INFO] | | \- org.junit.jupiter:junit-jupiter-engine:jar:5.8.2:test
[INFO] | | \- org.junit.platform:junit-platform-engine:jar:1.8.2:test
[INFO] | +- org.mockito:mockito-core:jar:4.5.1:test
[INFO] | | +- net.bytebuddy:byte-buddy:jar:1.12.11:compile
[INFO] | | +- net.bytebuddy:byte-buddy-agent:jar:1.12.11:test
[INFO] | | \- org.objenesis:objenesis:jar:3.2:test
[INFO] | +- org.mockito:mockito-junit-jupiter:jar:4.5.1:test
[INFO] | +- org.skyscreamer:jsonassert:jar:1.5.0:test
[INFO] | | \- com.vaadin.external.google:android-json:jar:0.0.20131108.vaadin1:test
[INFO] | +- org.springframework:spring-core:jar:5.3.21:compile
[INFO] | | \- org.springframework:spring-jcl:jar:5.3.21:compile
[INFO] | +- org.springframework:spring-test:jar:5.3.21:test
[INFO] | \- org.xmlunit:xmlunit-core:jar:2.9.0:test
[INFO] +- org.springframework.boot:spring-boot-starter-security:jar:2.7.1:compile
[INFO] | +- org.springframework:spring-aop:jar:5.3.21:compile
[INFO] | +- org.springframework.security:spring-security-config:jar:5.7.2:compile
[INFO] | \- org.springframework.security:spring-security-web:jar:5.7.2:compile
[INFO] +- org.springframework.security:spring-security-test:jar:5.7.2:test
[INFO] | \- org.springframework.security:spring-security-core:jar:5.7.2:compile
[INFO] | \- org.springframework.security:spring-security-crypto:jar:5.7.2:compile
[INFO] +- org.springframework.boot:spring-boot-starter-data-jpa:jar:2.7.1:compile
[INFO] | +- org.springframework.boot:spring-boot-starter-aop:jar:2.7.1:compile
[INFO] | | \- org.aspectj:aspectjweaver:jar:1.9.7:compile
[INFO] | +- org.springframework.boot:spring-boot-starter-jdbc:jar:2.7.1:compile
[INFO] | | +- com.zaxxer:HikariCP:jar:4.0.3:compile
[INFO] | | \- org.springframework:spring-jdbc:jar:5.3.21:compile
[INFO] | +- jakarta.transaction:jakarta.transaction-api:jar:1.3.3:compile
[INFO] | +- jakarta.persistence:jakarta.persistence-api:jar:2.2.3:compile
[INFO] | +- org.hibernate:hibernate-core:jar:5.6.9.Final:compile
[INFO] | | +- org.jboss.logging:jboss-logging:jar:3.4.3.Final:compile
[INFO] | | +- antlr:antlr:jar:2.7.7:compile
[INFO] | | +- org.jboss:jandex:jar:2.4.2.Final:compile
[INFO] | | +- com.fasterxml:classmate:jar:1.5.1:compile
[INFO] | | +- org.hibernate.common:hibernate-commons-annotations:jar:5.1.2.Final:compile
[INFO] | | \- org.glassfish.jaxb:jaxb-runtime:jar:2.3.6:compile
[INFO] | | +- org.glassfish.jaxb:txw2:jar:2.3.6:compile
[INFO] | | +- com.sun.istack:istack-commons-runtime:jar:3.0.12:compile
[INFO] | | \- com.sun.activation:jakarta.activation:jar:1.2.2:runtime
[INFO] | +- org.springframework.data:spring-data-jpa:jar:2.7.1:compile
[INFO] | | +- org.springframework.data:spring-data-commons:jar:2.7.1:compile
[INFO] | | +- org.springframework:spring-orm:jar:5.3.21:compile
[INFO] | | \- org.springframework:spring-tx:jar:5.3.21:compile
[INFO] | \- org.springframework:spring-aspects:jar:5.3.21:compile
[INFO] +- com.h2database:h2:jar:2.1.214:compile
[INFO] +- org.springframework.boot:spring-boot-devtools:jar:2.7.1:compile
[INFO] | +- org.springframework.boot:spring-boot:jar:2.7.1:compile
[INFO] | \- org.springframework.boot:spring-boot-autoconfigure:jar:2.7.1:compile
[INFO] \- javax.validation:validation-api:jar:2.0.1.Final:compile
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.451 s
[INFO] Finished at: 2022-07-18T10:22:10+07:00
[INFO] ------------------------------------------------------------------------
스프링 컨트롤러
Book Controller를 다시 검토하고 나중에 Spring Security와 통합하여 REST 끝점을 보호합니다.
package com.example.springrestsecurity;
import com.example.springrestsecurity.error.BookNotFoundException;
import com.example.springrestsecurity.error.BookUnSupportedFieldPatchException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.Min;
import java.util.List;
import java.util.Map;
@RestController
@Validated
public class BookController {
@Autowired
private BookRepository repository;
@GetMapping("/books")
List<Book> findAll() {
return repository.findAll();
}
@PostMapping("/books")
@ResponseStatus(HttpStatus.CREATED)
Book newBook(@Valid @RequestBody Book newBook) {
return repository.save(newBook);
}
@GetMapping("/books/{id}")
Book findOne(@PathVariable @Min(1) Long id) {
return repository.findById(id)
.orElseThrow(() -> new BookNotFoundException(id));
}
@PutMapping("/books/{id}")
Book saveOrUpdate(@RequestBody Book newBook, @PathVariable Long id) {
return repository.findById(id)
.map(x -> {
x.setName(newBook.getName());
x.setAuthor(newBook.getAuthor());
x.setPrice(newBook.getPrice());
return repository.save(x);
})
.orElseGet(() -> {
newBook.setId(id);
return repository.save(newBook);
});
}
@PatchMapping("/books/{id}")
Book patch(@RequestBody Map<String, String> update, @PathVariable Long id) {
return repository.findById(id)
.map(x -> {
String author = update.get("author");
if (!StringUtils.isEmpty(author)) {
x.setAuthor(author);
return repository.save(x);
} else {
throw new BookUnSupportedFieldPatchException(update.keySet());
}
})
.orElseGet(() -> {
throw new BookNotFoundException(id);
});
}
@DeleteMapping("/books/{id}")
void deleteBook(@PathVariable Long id) {
repository.deleteById(id);
}
}
Bean 유효성 검사(Hibernate 유효성 검사기)
@PostMapping("/books")
@ResponseStatus(HttpStatus.CREATED)
Book newBook(@Valid @RequestBody Book newBook) {
return repository.save(newBook);
}
javax.validation.constraints.* 주석으로 빈에 주석을 답니다.
package com.example.springrestsecurity;
import com.example.springrestsecurity.error.validator.Author;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
@Entity
public class Book {
@Id
@GeneratedValue
private Long id;
@NotEmpty(message = "Please provide a name")
private String name;
@Author
@NotEmpty(message = "Please provide a author")
private String author;
@NotNull(message = "Please provide a price")
@DecimalMin("1.00")
private BigDecimal price;
public Book() {
}
public Book(Long id, String name, String author, BigDecimal price) {
this.id = id;
this.name = name;
this.author = author;
this.price = price;
}
public Book(String name, String author, BigDecimal price) {
this.name = name;
this.author = author;
this.price = price;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
@Override
public String toString() {
return "Book{" +
"id=" + id +
", name='" + name + '\'' +
", author='" + author + '\'' +
", price=" + price +
'}';
}
}
@Valid를 @RequestBody에 추가합니다. 완료, 이제 빈 유효성 검사가 활성화되었습니다.
package com.example.springrestsecurity;
import com.example.springrestsecurity.error.BookNotFoundException;
import com.example.springrestsecurity.error.BookUnSupportedFieldPatchException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.Min;
import java.util.List;
import java.util.Map;
@RestController
@Validated
public class BookController {
@Autowired
private BookRepository repository;
@GetMapping("/books")
List<Book> findAll() {
return repository.findAll();
}
@PostMapping("/books")
@ResponseStatus(HttpStatus.CREATED)
Book newBook(@Valid @RequestBody Book newBook) {
return repository.save(newBook);
}
@GetMapping("/books/{id}")
Book findOne(@PathVariable @Min(1) Long id) {
return repository.findById(id)
.orElseThrow(() -> new BookNotFoundException(id));
}
@PutMapping("/books/{id}")
Book saveOrUpdate(@RequestBody Book newBook, @PathVariable Long id) {
return repository.findById(id)
.map(x -> {
x.setName(newBook.getName());
x.setAuthor(newBook.getAuthor());
x.setPrice(newBook.getPrice());
return repository.save(x);
})
.orElseGet(() -> {
newBook.setId(id);
return repository.save(newBook);
});
}
@PatchMapping("/books/{id}")
Book patch(@RequestBody Map<String, String> update, @PathVariable Long id) {
return repository.findById(id)
.map(x -> {
String author = update.get("author");
if (!StringUtils.isEmpty(author)) {
x.setAuthor(author);
// better create a custom method to update a value = :newValue where id = :id
return repository.save(x);
} else {
throw new BookUnSupportedFieldPatchException(update.keySet());
}
})
.orElseGet(() -> {
throw new BookNotFoundException(id);
});
}
@DeleteMapping("/books/{id}")
void deleteBook(@PathVariable Long id) {
repository.deleteById(id);
}
}
REST 끝점에 POST 요청을 다시 보내십시오. 누락된 데이터 필드로 인해 Bean 유효성 검사가 실패하면 MethodArgumentNotValidException이 트리거됩니다. 기본적으로 Spring은 HTTP 상태 400 잘못된 요청을 다시 보내지만 오류 세부 정보는 보내지 않습니다.

위의 오류 응답은 친숙하지 않습니다. MethodArgumentNotValidException을 포착하고 다음과 같이 응답을 재정의할 수 있습니다.
package com.example.springrestsecurity.error;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@ControllerAdvice
public class CustomGlobalExceptionHandler extends ResponseEntityExceptionHandler {
// Let Spring BasicErrorController handle the exception, we just override the status code
@ExceptionHandler(BookNotFoundException.class)
public void springHandleNotFound(HttpServletResponse response) throws IOException {
response.sendError(HttpStatus.NOT_FOUND.value());
}
@ExceptionHandler(BookUnSupportedFieldPatchException.class)
public void springUnSupportedFieldPatch(HttpServletResponse response) throws IOException {
response.sendError(HttpStatus.METHOD_NOT_ALLOWED.value());
}
// @Validate For Validating Path Variables and Request Parameters
@ExceptionHandler(ConstraintViolationException.class)
public void constraintViolationException(HttpServletResponse response) throws IOException {
response.sendError(HttpStatus.BAD_REQUEST.value());
}
// error handle for @Valid
@Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
HttpHeaders headers,
HttpStatus status, WebRequest request) {
Map<String, Object> body = new LinkedHashMap<>();
body.put("timestamp", new Date());
body.put("status", status.value());
//Get all errors
List<String> errors = ex.getBindingResult()
.getFieldErrors()
.stream()
.map(x -> x.getDefaultMessage())
.collect(Collectors.toList());
body.put("errors", errors);
return new ResponseEntity<>(body, headers, status);
}
}

경로 변수 유효성 검사
BookController.java
package com.example.springrestsecurity;
import com.example.springrestsecurity.error.BookNotFoundException;
import com.example.springrestsecurity.error.BookUnSupportedFieldPatchException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.Min;
import java.util.List;
import java.util.Map;
@RestController
@Validated
public class BookController {
@GetMapping("/books/{id}")
Book findOne(@PathVariable @Min(1) Long id) { //jsr 303 annotations
return repository.findById(id)
.orElseThrow(() -> new BookNotFoundException(id));
}
//...
}
기본 오류 메시지는 양호하며 오류 코드 500만 적합하지 않습니다.

@Validated가 실패하면 ConstraintViolationException이 트리거되고 다음과 같은 오류 코드를 무시할 수 있습니다.
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolationException;
import java.io.IOException;
@ControllerAdvice
public class CustomGlobalExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(ConstraintViolationException.class)
public void constraintViolationException(HttpServletResponse response) throws IOException {
response.sendError(HttpStatus.BAD_REQUEST.value());
}
//..
}

맞춤 검사기
작성자 필드에 대한 사용자 지정 유효성 검사기를 만들어 4명의 작성자만 데이터베이스에 저장할 수 있도록 합니다.
package com.example.springrestsecurity.error.validator;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Target({FIELD})
@Retention(RUNTIME)
@Constraint(validatedBy = AuthorValidator.class)
@Documented
public @interface Author {
String message() default "Author is not allowed.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
package com.example.springrestsecurity.error.validator;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.Arrays;
import java.util.List;
public class AuthorValidator implements ConstraintValidator<Author, String> {
List<String> authors = Arrays.asList("Santideva", "Marie Kondo", "Martin Fowler", "toptech");
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return authors.contains(value);
}
}
package com.example.springrestsecurity;
import com.example.springrestsecurity.error.validator.Author;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.math.BigDecimal;
@Entity
public class Book {
@Id
@GeneratedValue
private Long id;
@NotEmpty(message = "Please provide a name")
private String name;
@Author
@NotEmpty(message = "Please provide a author")
private String author;
@NotNull(message = "Please provide a price")
@DecimalMin("1.00")
private BigDecimal price;
public Book() {
}
public Book(Long id, String name, String author, BigDecimal price) {
this.id = id;
this.name = name;
this.author = author;
this.price = price;
}
public Book(String name, String author, BigDecimal price) {
this.name = name;
this.author = author;
this.price = price;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public BigDecimal getPrice() {
return price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
@Override
public String toString() {
return "Book{" +
"id=" + id +
", name='" + name + '\'' +
", author='" + author + '\'' +
", price=" + price +
'}';
}
}
그것을 테스트하십시오. 사용자 지정 유효성 검사기가 실패하면 MethodArgumentNotValidException이 트리거됩니다.
curl -v -X POST localhost:8080/books
-H "Content-type:application/json"
-d "{\"name\":\"Spring REST tutorials\", \"author\":\"abc\",\"price\":\"9.99\"}"
{
"timestamp":"2019-02-20T13:49:59.971+0000",
"status":400,
"errors":["Author is not allowed."]
}
스프링 시큐리티
새 @Configuration 클래스를 만들고 WebSecurityConfigurerAdapter를 확장합니다. 아래 예에서는 HTTP 기본 인증을 사용하여 REST 끝점을 보호합니다.
package com.example.springrestsecurity.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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.WebSecurityConfigurerAdapter;
@Configuration
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("user").password("{noop}password").roles("USER")
.and()
.withUser("admin").password("{noop}password").roles("USER", "ADMIN");
}
// Secure the endpoins with HTTP Basic authentication
@Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic()
.and()
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/books/**").hasRole("USER")
.antMatchers(HttpMethod.POST, "/books").hasRole("ADMIN")
.antMatchers(HttpMethod.PUT, "/books/**").hasRole("ADMIN")
.antMatchers(HttpMethod.PATCH, "/books/**").hasRole("ADMIN")
.antMatchers(HttpMethod.DELETE, "/books/**").hasRole("ADMIN")
.and()
.csrf().disable()
.formLogin().disable();
}
}
스프링 부트
REST 끝점을 시작하고 데모를 위해 H2 데이터베이스에 3권의 책을 삽입하는 일반적인 Spring Boot 애플리케이션입니다.
package com.example.springrestsecurity;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Profile;
import java.math.BigDecimal;
@SpringBootApplication
public class SpringRestSecurityApplication {
public static void main(String[] args) {
SpringApplication.run(SpringRestSecurityApplication.class, args);
}
@Profile("demo")
@Bean
CommandLineRunner initDatabase(BookRepository repository) {
return args -> {
repository.save(new Book("A Guide to the Bodhisattva Way of Life", "Santideva", new BigDecimal("15.41")));
repository.save(new Book("The Life-Changing Magic of Tidying Up", "Marie Kondo", new BigDecimal("9.69")));
repository.save(new Book("Refactoring: Improving the Design of Existing Code", "Martin Fowler", new BigDecimal("47.99")));
};
}
}
데모


user
로그인과 함께 GET 요청을 보냅니다.

Spring Security 구성을 다시 검토하십시오. POST, PUT, PATCH 또는 DELETE 요청을 보내려면 관리자가 필요합니다.
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.httpBasic()
.and()
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/books/**").hasRole("USER")
.antMatchers(HttpMethod.POST, "/books").hasRole("ADMIN")
.antMatchers(HttpMethod.PUT, "/books/**").hasRole("ADMIN")
.antMatchers(HttpMethod.PATCH, "/books/**").hasRole("ADMIN")
.antMatchers(HttpMethod.DELETE, "/books/**").hasRole("ADMIN")
.and()
.csrf().disable()
.formLogin().disable();
}
}

소스 코드
https://github.com/java-cake/spring-boot/tree/main/springrestsecurity
Reference
이 문제에 관하여(스프링 REST + 스프링 보안 예제), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/fullstackhacker/spring-rest-spring-security-example-1d00텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)