16.1 Enabling global method security
16.1.1 Understanding call authorization


- Preauthorization
- Postauthorization


用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
在大多数情况下,一般只用第一种方式就够了
依赖:
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency></dependencies>
@Configuration@EnableGlobalMethodSecurity(prePostEnabled = true)public class ProjectConfig {}
16.2 Applying preauthorization for authorities and roles

定义两个user,一个可以成功访问端点,一个会抛出异常:
@Configuration@EnableGlobalMethodSecurity(prePostEnabled = true)public class ProjectConfig {@Beanpublic UserDetailsService userDetailsService() {InMemoryUserDetailsManager service = new InMemoryUserDetailsManager();UserDetails u1 = User.withUsername("natalie").password("12345").authorities("read").build();UserDetails u2 = User.withUsername("emma").password("12345").authorities("write").build();service.createUser(u1);service.createUser(u2);return service;}@Beanpublic PasswordEncoder passwordEncoder(){return NoOpPasswordEncoder.getInstance();}}
定义一个preauthorization规则在方法上
@Servicepublic class NameService {@PreAuthorize("hasAuthority('write')")public String getName() {return "Fantastico";}}
定义一个controller实现一个端点使用这个service
@RestControllerpublic class HelloController {@Autowiredprivate NameService nameService;@GetMapping("/hello")public String hello(){return "Hello, " + nameService.getName();}}
运行程序,结果应该是只有Emma才可以成功访问该端点:



运行后:

16.3 Applying postauthorization

定义一个实体类
@Datapublic class Employee {private String name;private List<String> books;private List<String> roles;}
定义一个service
@Servicepublic class BookService {private Map<String, Employee> records =Map.of("emma",new Employee("Emma Thompson",List.of("Karamazov Brothers"),List.of("accountant", "reader")),"natalie",new Employee("Natalie Parker",List.of("Beautiful Paris"),List.of("researcher")));@PostAuthorize("returnObject.roles.contains('reader')")public Employee getBookDetails(String name){return records.get(name);}}
定义一个controller并暴露一个端点:
@RestControllerpublic class BookController {@Autowiredprivate BookService bookService;@GetMapping("/book/details/{name}")public Employee getDetails(@PathVariable String name){return bookService.getBookDetails(name);}}
16.4 Implementing permissions for methods
@Data@AllArgsConstructorpublic class Document {private String owner;}
@Repositorypublic class DocumentRepository {private Map<String, Document> documents = Map.of("abc123", new Document("natalie"),"qwe123", new Document("natalie"),"asd555", new Document("emma"));public Document findDocument(String code) {return documents.get(code);}}
@Servicepublic class DocumentService {@Autowiredprivate DocumentRepository documentRepository;@PostAuthorize("hasPermission(returnObject, 'ROLE_admin')")public Document getDocument(String code) {return documentRepository.findDocument(code);}}
PermissionEvaluator定义了两种方式实现permission logic
- By object and permission
- By object ID, object type, and permission

当调用hasPermission的时候,不需要传入Authentication,因为Spring Security会自动传入
实现授权的规则:
/*** Created by ql on 2022/5/27*/@Componentpublic class DocumentsPermissionEvalutor implements PermissionEvaluator {@Overridepublic boolean hasPermission(Authentication authentication, Object target, Object permission) {Document document = (Document) target;String p = (String) permission;boolean admin = authentication.getAuthorities().stream().anyMatch(a -> a.getAuthority().equals(p));return admin || document.getOwner().equals(authentication.getName());}@Overridepublic boolean hasPermission(Authentication authentication, Serializable serializable, String s, Object o) {return false;}}
为了让spring securit使用我们定义的PermissionEvaluator,我们必须定义一个MethodSecurityExpressionHandler在配置类中
配置用户角色:
启动并运行:
natalie有admin权限,所以可以访问emma的Documment
由于emma只有manager的权限,而且abc123的owner也不是emma,所以权限不足,无法访问


