Я использую Java 17. Новейшая версия Spring Security 6.1.4.
Я пытаюсь создать собственный диспетчер авторизации, и думаю, мне удалось сделать это без особых хлопот.
@Component
public class CustomAuthorizationManager implements AuthorizationManager<MethodInvocation> {
@Autowired
private RoleRepository roleRepository;
private boolean onlyBUCheck;
private GrantedAuthority buToCheck = null;
private GrantedAuthority authorityToCheck = null;
public CustomAuthorizationManager(){
System.out.println("created through here");
};
@Override
public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation invocation) {
//calling buCheck and authorityCheck
}
private AuthorizationDecision buCheck(Authentication authentication){
//Logic
}
private AuthorizationDecision authorityCheck(Authentication authentication){
//Logic
}
public void partOfBU(String bu){
Assert.notNull(bu, "role cannot be null");
this.onlyBUCheck = true;
this.buToCheck = () -> bu;
}
public void hasAuthority(String bu, String authority) {
Assert.notNull(authority, "role cannot be null");
this.onlyBUCheck = false;
this.buToCheck = () -> bu;
this.authorityToCheck = () -> authority;
}
Итак, после того, как я создал конечную точку, чтобы проверить ее.
@PostMapping("/wtf")
@PreAuthorize("customAuthorizationManager.partOfBU(bu.id())") //I get autocomplete here
public ResponseEntity<Object> aaa(@P("bu") @RequestBody BusinessUnitDTO businessUnitDTO){
System.out.println(businessUnitDTO);
return new ResponseEntity<>(HttpStatus.OK);
}
Поскольку у меня появляется автозаполнение на @PreAuthorize, я думаю, все настроено правильно. Но когда я запускаю его, метод partOfBU() не выполняется. Просто метод check() (который, конечно, не работает, потому что все не настроено).
Если я просто выполню @EnableMethodSecurity(prePostEnabled = true), я получу это исключение org.springframework.expression.spel.SpelEvaluationException: EL1008E: Property or field 'customAuthorizationManager' cannot be found on object of type 'org.springframework.security.access.expression.method.MethodSecurityExpressionRoot' - maybe not public or not valid?
Насколько я понимаю, это означает, что bean-компонент customAuthorizationManager куда-то не был внедрен, и поэтому я не могу использовать его в @PreAuthorize.
Итак, я нашел это в документах https://docs.spring.io/spring-security/reference/servlet/authorization/method-security.html#custom-authorization-managers
@Configuration
@EnableMethodSecurity(prePostEnabled = false)
class MethodSecurityConfig {
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE) //Tried both with and without this. no idea what it does
Advisor preAuthorize(CustomAuthorizationManager manager) {
return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(manager);
}
}
Это решило исключение, и теперь только partOfBU() не выполняется, хотя я его там вызываю. (Наверное, потому что я вколол его в нужное место)
Попробовал это тоже, и я получаю тот же результат, поэтому, думаю, часть после точки по какой-то причине игнорируется.
@PostMapping("/wtf")
@PreAuthorize("customAuthorizationManager") //not calling the partOfBU() method
public ResponseEntity<Object> aaa(@P("bu") @RequestBody BusinessUnitDTO businessUnitDTO){
System.out.println(businessUnitDTO);
return new ResponseEntity<>(HttpStatus.OK);
}
Оказывается, @PreAuthorize не волнует, что там. Он просто вызывает attemptAuthorization() в AuthorizationManagerBeforeMethodInterceptor (где мы устанавливаем собственный AuthorizationManager)
private void attemptAuthorization(MethodInvocation mi) {
this.logger.debug(LogMessage.of(() -> {
return "Authorizing method invocation " + mi;
}));
AuthorizationDecision decision = this.authorizationManager.check(this.authentication, mi);
this.eventPublisher.publishAuthorizationEvent(this.authentication, mi, decision);
if (decision != null && !decision.isGranted()) {
this.logger.debug(LogMessage.of(() -> {
return "Failed to authorize " + mi + " with authorization manager " + this.authorizationManager + " and decision " + decision;
}));
throw new AccessDeniedException("Access Denied");
} else {
this.logger.debug(LogMessage.of(() -> {
return "Authorized method invocation " + mi;
}));
}
}
Я думаю, что моя ошибка заключается в том, что я перепутал, как должен выглядеть менеджер авторизации методов и тот, который используется в файлах конфигурации с помощью requestMatchers.
Я реализовал свою, глядя на реализацию AuthorityAuthorizationManager вместо PreAuthorizeAuthorizationManager. Постараюсь приблизить реализацию к той, что в PreAuthorizeAuthorizationManager, и вернусь с результатами. (Просто нужно разобраться, как использовать SpEL и связанные с ним классы)





Хорошо удалось заставить его работать. Я опубликую это здесь, если у кого-то возникнет такая же проблема в будущем. Возможно, это старый способ решения проблем (насколько я понял из весенней документации)
Теперь я последовал руководству dan1st, который предложил довольно внимательно вносить изменения самостоятельно.
Я сделал это с помощью кастома AuthorizationManager. Есть и другие способы заменить эту часть, но я сделал это так.
Сначала мы начнем с класса конфигурации. (если ты не знаешь, что это за гугл spring configuration class)
@Configuration
@EnableMethodSecurity(prePostEnabled = false) //1
class MethodSecurityConfig {
@Bean
public Advisor preAuthorize(CustomAuthorizationManager manager) { //2
return AuthorizationManagerBeforeMethodInterceptor.preAuthorize(manager);
}
}
PreAuthorize МЕТОД. Если вы хотите использовать другие, вам также придется перезаписать их методы. (Показано в старой документации) По какой-то причине в новых версиях они решили сделать пример меньше и, следовательно, его труднее понять.Теперь как создать собственный AuthorizationManager. Я пытался скопировать из нескольких других реализаций и пришел к выводу, что существует разница между методами и теми, которые определены в классе конфигурации.
методы: @PreAuthorize, @PostAuthorize, @PreFilter, @PostFilter и другие...
config: те, которые определены в классе конфигурации, например
@Configuration
public class WebSecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(requests -> {
requests.anyRequest().hasAuthority("test"); //.hasAuthority() here is what I'm talking about
}).build();
}
Если вы хотите сделать конфигурацию, я бы сказал, попробуйте скопировать из реализации AuthenticatedAuthorizationManager. На самом деле я не вникал в них слишком глубоко, так как мне нужно было использовать первый метод.
Если вы хотите использовать метод, который я скопировал PreAuthorizeAuthorizationManager. Это выглядит довольно сложно, но я передам вам все, что узнал, размышляя над ним за последние несколько дней.
У меня было два варианта авторизации моего приложения. Либо храните массу информации в SecurityContextHolder, либо запрашивайте базу данных для каждого запроса, требующего авторизации. Я выбрал 2-й вариант.
@Component("customAuthorizationManager")
public class CustomAuthorizationManager implements AuthorizationManager<MethodInvocation> {
private final RoleService roleService;
//Injecting RoleService
public CustomAuthorizationManager(RoleService roleService) {
this.roleService = roleService;
}
@Override
public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation invocation) {
ExpressionParser parser = new SpelExpressionParser(); //1
Expression expression = parser.parseExpression(invocation.getMethod().getAnnotation(PreAuthorize.class).value());//2
CustomMethodSecurityExpressionHandler c = new CustomMethodSecurityExpressionHandler(roleService); //3
EvaluationContext ec = c.createEvaluationContext(authentication,invocation); //4
boolean granted = Boolean.TRUE.equals(expression.getValue(ec, Boolean.class)); //5
return new AuthorizationDecision(granted);
}
}
Теперь будет вызываться метод check(). (Даже если вы вызовете какие-либо другие методы в CustomAuthorizationManager в @PreAuthorize Spring, это не будет волновать и просто вызовет метод check())
Итак, после того, как я разобрался с этим, позвольте мне попытаться объяснить, что происходит внутри.
Я следовал вышеупомянутому руководству, предоставленному Дэном. В основном модифицируя то, что там было, под мои нужды.
public class CustomMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler {
private final RoleService roleService;
//Injecting the RoleService again (manually this time as this isn't a component)
//and cuz it's needed further down the line
public CustomMethodSecurityExpressionHandler(RoleService roleService) {
this.roleService = roleService;
}
@Override
public EvaluationContext createEvaluationContext(Supplier<Authentication> authentication, MethodInvocation mi) {
StandardEvaluationContext context = (StandardEvaluationContext) super.createEvaluationContext(authentication, mi);
MethodSecurityExpressionOperations delegate = (MethodSecurityExpressionOperations) context.getRootObject().getValue();
CustomMethodSecurityExpressionRoot root = new CustomMethodSecurityExpressionRoot(delegate.getAuthentication());
root.setRoleService(roleService);
context.setRootObject(root);
return context;
}
}
Теперь метод, который мы переопределяем, отличается от статьи. Я предполагаю, что это может работать с тем же, что используется в статье, но я не пробовал (поскольку эта реализация должна была быть для @EnableGlobalMethodSecurity, но в Spring Security 6 предлагается использовать @EnableMethodSecurity) Я сделал это именно так, потому что именно так это было предложено в последней весенней документации (если вы прочитаете об этом больше, я думаю, что не рекомендуется делать это этими двумя способами, но да)
Я предполагаю, что большая часть метода говорит Spring использовать созданные нами пользовательские штуки вместо значений по умолчанию.
Вы также можете увидеть последнюю вещь, которую нам нужно реализовать — CustomMethodSecurityExpressionRoot.
public class CustomMethodSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations {
private RoleService roleService;
//No idea why these are needed. Just followed the guide
private HttpServletRequest request;
private Object filterObject;
private Object returnObject;
private Object target;
public CustomMethodSecurityExpressionRoot(Authentication authentication) {
super(authentication);
}
//Here is where you add the methods you'll be using in @PreAuthorize together with their logic
//Custom method you'll be using in @PreAuthorize
public boolean partOfBU(Long buIdToCheck){
//your logic
}
//Custom method you'll be using in @PreAuthorize
public boolean authorityCheck(Long buIdToCheck, String authorityToCheck){
//your logic
}
public void setRoleService(RoleService roleService){
this.roleService = roleService;
}
@Override
public void setFilterObject(Object filterObject) {
this.filterObject = filterObject;
}
@Override
public Object getFilterObject() {
return this.filterObject;
}
@Override
public void setReturnObject(Object returnObject) {
this.returnObject = returnObject;
}
@Override
public Object getReturnObject() {
return this.returnObject;
}
@Override
public Object getThis() {
return target;
}
}
По сути, здесь вы будете добавлять собственные методы, которые будете использовать. Понятия не имею, зачем используются другие вещи и даже нужны ли они.
И как теперь это назвать?
@PostMapping("/wtf")
@PreAuthorize("partOfBU(#bu.id())") //1
public ResponseEntity<Object> aaa(@P("bu") @RequestBody BusinessUnitDTO businessUnitDTO){
return new ResponseEntity<>(HttpStatus.OK);
}
И это весь шабанг. Вероятно, мой способ устарел, но он, по крайней мере, работает.