16.1 Enabling global method security

16.1.1 Understanding call authorization

image.png
image.png

  • Preauthorization
  • Postauthorization

image.png
image.png
用Postauthorization要谨慎!因为如果在异常期间该方法改变了某个东西,这个改变可能会一直存在无论授权是否成功。即使用@Transactional也无法回滚,因为postauthorization的工作机制在事务提交之后

16.1.2 Enabling global method security in your project

Global method security默认是关闭的,开启的方式是加上:@EnableGlobalMethodSecurity
Global method security提供了三个方式定义授权的规则:

  • the pre-/ postauthorization annnotations
  • the JSR 250 annotation, @RolesAllowed
  • the @Secured annotation

在大多数情况下,一般只用第一种方式就够了

依赖:

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-security</artifactId>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.springframework.boot</groupId>
  8. <artifactId>spring-boot-starter-web</artifactId>
  9. </dependency>
  10. </dependencies>
  1. @Configuration
  2. @EnableGlobalMethodSecurity(prePostEnabled = true)
  3. public class ProjectConfig {
  4. }

16.2 Applying preauthorization for authorities and roles

image.png
定义两个user,一个可以成功访问端点,一个会抛出异常:

  1. @Configuration
  2. @EnableGlobalMethodSecurity(prePostEnabled = true)
  3. public class ProjectConfig {
  4. @Bean
  5. public UserDetailsService userDetailsService() {
  6. InMemoryUserDetailsManager service = new InMemoryUserDetailsManager();
  7. UserDetails u1 = User.withUsername("natalie")
  8. .password("12345")
  9. .authorities("read")
  10. .build();
  11. UserDetails u2 = User.withUsername("emma")
  12. .password("12345")
  13. .authorities("write")
  14. .build();
  15. service.createUser(u1);
  16. service.createUser(u2);
  17. return service;
  18. }
  19. @Bean
  20. public PasswordEncoder passwordEncoder(){
  21. return NoOpPasswordEncoder.getInstance();
  22. }
  23. }

定义一个preauthorization规则在方法上

  1. @Service
  2. public class NameService {
  3. @PreAuthorize("hasAuthority('write')")
  4. public String getName() {
  5. return "Fantastico";
  6. }
  7. }

定义一个controller实现一个端点使用这个service

  1. @RestController
  2. public class HelloController {
  3. @Autowired
  4. private NameService nameService;
  5. @GetMapping("/hello")
  6. public String hello(){
  7. return "Hello, " + nameService.getName();
  8. }
  9. }

运行程序,结果应该是只有Emma才可以成功访问该端点:
image.png
image.png
image.png
image.png
运行后:
image.png
image.png
image.png

16.3 Applying postauthorization

image.png
定义一个实体类

  1. @Data
  2. public class Employee {
  3. private String name;
  4. private List<String> books;
  5. private List<String> roles;
  6. }

定义一个service

  1. @Service
  2. public class BookService {
  3. private Map<String, Employee> records =
  4. Map.of("emma",
  5. new Employee("Emma Thompson",
  6. List.of("Karamazov Brothers"),
  7. List.of("accountant", "reader")),
  8. "natalie",
  9. new Employee("Natalie Parker",
  10. List.of("Beautiful Paris"),
  11. List.of("researcher")));
  12. @PostAuthorize("returnObject.roles.contains('reader')")
  13. public Employee getBookDetails(String name){
  14. return records.get(name);
  15. }
  16. }

定义一个controller并暴露一个端点:

  1. @RestController
  2. public class BookController {
  3. @Autowired
  4. private BookService bookService;
  5. @GetMapping("/book/details/{name}")
  6. public Employee getDetails(@PathVariable String name){
  7. return bookService.getBookDetails(name);
  8. }
  9. }

启动项目并测试:
image.png
image.png
image.png

16.4 Implementing permissions for methods

  1. @Data
  2. @AllArgsConstructor
  3. public class Document {
  4. private String owner;
  5. }
  1. @Repository
  2. public class DocumentRepository {
  3. private Map<String, Document> documents = Map.of("abc123", new Document("natalie"),
  4. "qwe123", new Document("natalie"),
  5. "asd555", new Document("emma"));
  6. public Document findDocument(String code) {
  7. return documents.get(code);
  8. }
  9. }
  1. @Service
  2. public class DocumentService {
  3. @Autowired
  4. private DocumentRepository documentRepository;
  5. @PostAuthorize("hasPermission(returnObject, 'ROLE_admin')")
  6. public Document getDocument(String code) {
  7. return documentRepository.findDocument(code);
  8. }
  9. }

PermissionEvaluator定义了两种方式实现permission logic

  • By object and permission
  • By object ID, object type, and permission

image.png
当调用hasPermission的时候,不需要传入Authentication,因为Spring Security会自动传入

实现授权的规则:

  1. /**
  2. * Created by ql on 2022/5/27
  3. */
  4. @Component
  5. public class DocumentsPermissionEvalutor implements PermissionEvaluator {
  6. @Override
  7. public boolean hasPermission(Authentication authentication, Object target, Object permission) {
  8. Document document = (Document) target;
  9. String p = (String) permission;
  10. boolean admin = authentication.getAuthorities().stream().anyMatch(a -> a.getAuthority().equals(p));
  11. return admin || document.getOwner().equals(authentication.getName());
  12. }
  13. @Override
  14. public boolean hasPermission(Authentication authentication, Serializable serializable, String s, Object o) {
  15. return false;
  16. }
  17. }

为了让spring securit使用我们定义的PermissionEvaluator,我们必须定义一个MethodSecurityExpressionHandler在配置类中
image.png
配置用户角色:
image.png
启动并运行:
image.png
natalie有admin权限,所以可以访问emma的Documment
image.png
由于emma只有manager的权限,而且abc123的owner也不是emma,所以权限不足,无法访问
image.png