扫码关注《Java学研大本营》,加入读者群,分享更多精彩
包括Spring Security 的 spring-boot-starter-security
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.7.1
com.example
springrestsecurity
0.0.1-SNAPSHOT
spring-rest-security
Demo project for Spring Boot
1.8
org.springframework.boot
spring-boot-starter-thymeleaf
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-configuration-processor
true
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-starter-security
org.springframework.security
spring-security-test
test
org.springframework.boot
spring-boot-starter-data-jpa
com.h2database
h2
org.springframework.boot
spring-boot-devtools
true
javax.validation
validation-api
2.0.1.Final
org.springframework.boot
spring-boot-maven-plugin
org.projectlombok
lombok
项目依赖:
> 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 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 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 验证 JSR-303 实现(如 Hibernate Validator)在类路径上可用, 默认情况下,Spring Boot 会自动获取并下载 Hibernate Validator。
下面的 POST 请求将被传递,我们需要对 book 对象进行 bean 验证,以确保 name、author 和 price 等字段不为空。
@PostMapping("/books")
@ResponseStatus(HttpStatus.CREATED)
Book newBook(@Valid @RequestBody Book newBook) {
return repository.save(newBook);
}
使用 javax.validation.constraints.* 注释对 bean 进行注释。
Book.java
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 。 完成,现在启用 bean 验证。
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 {
@Autowired
private BookRepository repository;
@GetMapping("/books")
List 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 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 Bad Request ,但没有错误详细信息。
上面的错误响应是不友好的,我们可以像这样捕获 MethodArgumentNotValidException 并覆盖响应:
CustomGlobalExceptionHandler.java
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
我们也可以应用 javax.validation.constraints. * 直接在路径变量甚至请求参数上进行注释。 应用 @Validated ,并添加 javax.validation.constraints. * 像这样的路径变量注释:
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 ,我们可以像这样覆盖错误代码:
CustomGlobalExceptionHandler.java
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());
}
//..
}
我们将为 author 字段创建一个自定义验证器,只允许 4 个作者保存到数据库中。
Author.java
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 {};
}
AuthorValidator.java
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 {
List authors = Arrays.asList("Santideva", "Marie Kondo", "Martin Fowler", "toptech");
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return authors.contains(value);
}
}
Book.java
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 Basic 身份验证来保护 REST 端点。
SpringSecurityConfig.java
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();
}
}
普通 Spring Boot 应用程序启动 REST 端点并将 3 本书插入 H2 数据库进行演示。
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")));
};
}
}
普通 GET 和 POST 会返回 401,所有端点都受到保护,需要身份验证。
发送 GET 请求以及 user
登录
尝试使用“user”登录发送 POST 请求,它将返回 403,禁止错误。 这是因为用户无权发送 POST 请求。
再次查看 Spring Security 配置。 要发送 POST、PUT、PATCH 或 DELETE 请求,我们需要 admin
SpringSecurityConfig.java
@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();
}
}
尝试使用管理员登录发送 POST 请求
https://github.com/java-cake/spring-boot/tree/main/springrestsecurity
购买链接:https://item.jd.com/13495830.html
《Java编程讲义》根据目前Java开发领域的实际需求,从初学者角度出发,详细讲解了Java技术的基础知识。
全书共15章,包括Java开发入门,Java语言基础,Java控制结构,数组,面向对象编程,继承和多态,抽象类、接口和内部类,异常处理,Java常用类库,集合与泛型,Lambda表达式,输入-输出流,多线程,JDBC数据库技术,网络编程等内容。内容全面覆盖.1ava开发必备的基础知识点,结合生活化案例展开讲解,程序代码给出了详细的注释,能够使初学者轻松领会Java技术精髓,快速掌握Java开发技能。
《Java编程讲义》适合作为高等院校相关专业的教材及教学参考书,也适合作为Java开发入门者的自学用书,还可供开发人员查阅、参考。
精彩回顾
想要代码干净又整洁?这里有十大原则
通过自学成为开发者的 9 种方法
怎么做一个有产品意识的软件工程师?
扫码关注《Java学研大本营》,加入读者群,分享更多精彩