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 {
@Bean
public 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;
}
@Bean
public PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
}
定义一个preauthorization规则在方法上
@Service
public class NameService {
@PreAuthorize("hasAuthority('write')")
public String getName() {
return "Fantastico";
}
}
定义一个controller实现一个端点使用这个service
@RestController
public class HelloController {
@Autowired
private NameService nameService;
@GetMapping("/hello")
public String hello(){
return "Hello, " + nameService.getName();
}
}
运行程序,结果应该是只有Emma才可以成功访问该端点:
运行后:
16.3 Applying postauthorization
定义一个实体类
@Data
public class Employee {
private String name;
private List<String> books;
private List<String> roles;
}
定义一个service
@Service
public 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并暴露一个端点:
@RestController
public class BookController {
@Autowired
private BookService bookService;
@GetMapping("/book/details/{name}")
public Employee getDetails(@PathVariable String name){
return bookService.getBookDetails(name);
}
}
16.4 Implementing permissions for methods
@Data
@AllArgsConstructor
public class Document {
private String owner;
}
@Repository
public 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);
}
}
@Service
public class DocumentService {
@Autowired
private 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
*/
@Component
public class DocumentsPermissionEvalutor implements PermissionEvaluator {
@Override
public 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());
}
@Override
public 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,所以权限不足,无法访问