Search code examples
javaspringspring-bootspring-securityspring-data-rest

Error when restricting POST and REST requests via Spring Security and SpEL expressions


Here is the GitHub of my mini-project if needed.

I can't figure out SpEL expressions. When I try to implement the code from this tutorial, I get the following errors:

1.

@PreAuthorize("hasRole('ROLE_USER')")
public interface TodoRepo extends PagingAndSortingRepository<TodoEntity, Long> {
    @Override
    @PreAuthorize("#todo?.entity == null or #todo?.entity?.name == authentication?.name")
    TodoEntity save(@Param("todo") TodoEntity employee);

    @Override
    @PreAuthorize("@todoRepo.findById(#id)?.entity?.name == authentication?.name")
    void deleteById(@Param("id") Long id);

    @Override
    @PreAuthorize("#todo?.entity?.name == authentication?.name")
    void delete(@Param("todo") TodoEntity employee);
}

Error:

Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1008E: Property or field 'name' cannot be found on object of type 'com.egor.todo.entity.UserEntity' - maybe not public or not valid?
at org.springframework.expression.spel.ast.PropertyOrFieldReference.readProperty(PropertyOrFieldReference.java:217) ~[spring-expression-5.3.23.jar:5.3.23]
at org.springframework.expression.spel.ast.PropertyOrFieldReference.getValueInternal(PropertyOrFieldReference.java:104) ~[spring-expression-5.3.23.jar:5.3.23]
at org.springframework.expression.spel.ast.PropertyOrFieldReference.access$000(PropertyOrFieldReference.java:51) ~[spring-expression-5.3.23.jar:5.3.23]
at org.springframework.expression.spel.ast.PropertyOrFieldReference$AccessorLValue.getValue(PropertyOrFieldReference.java:406) ~[spring-expression-5.3.23.jar:5.3.23]
at org.springframework.expression.spel.ast.CompoundExpression.getValueInternal(CompoundExpression.java:92) ~[spring-expression-5.3.23.jar:5.3.23]
at org.springframework.expression.spel.ast.OpEQ.getValueInternal(OpEQ.java:42) ~[spring-expression-5.3.23.jar:5.3.23]
at org.springframework.expression.spel.ast.OpEQ.getValueInternal(OpEQ.java:32) ~[spring-expression-5.3.23.jar:5.3.23]
at org.springframework.expression.spel.ast.SpelNodeImpl.getValue(SpelNodeImpl.java:188) ~[spring-expression-5.3.23.jar:5.3.23]
at org.springframework.expression.spel.ast.OpOr.getBooleanValue(OpOr.java:56) ~[spring-expression-5.3.23.jar:5.3.23]
at org.springframework.expression.spel.ast.OpOr.getValueInternal(OpOr.java:51) ~[spring-expression-5.3.23.jar:5.3.23]
at org.springframework.expression.spel.ast.OpOr.getValueInternal(OpOr.java:37) ~[spring-expression-5.3.23.jar:5.3.23]
at org.springframework.expression.spel.ast.SpelNodeImpl.getTypedValue(SpelNodeImpl.java:117) ~[spring-expression-5.3.23.jar:5.3.23]
at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:309) ~[spring-expression-5.3.23.jar:5.3.23]
at org.springframework.security.access.expression.ExpressionUtils.evaluateAsBoolean(ExpressionUtils.java:30) ~[spring-security-core-5.7.4.jar:5.7.4]
... 17 common frames omitted
@PreAuthorize("hasRole('ROLE_USER')")
    public interface TodoRepo extends PagingAndSortingRepository<TodoEntity, Long> {
        @Override
        @PreAuthorize("#todo?.getEntity() == null or #todo?.getEntity()?.getName() == authentication?.name")
        TodoEntity save(@Param("todo") TodoEntity employee);
    
        @Override
        @PreAuthorize("@todoRepo.findById(#id)?.getEntity()?.getName() == authentication?.name")
        void deleteById(@Param("id") Long id);
    
        @Override
        @PreAuthorize("#todo?.getEntity()?.getName() == authentication?.name")
        void delete(@Param("todo") TodoEntity employee);
    }

Error:

Caused by: org.springframework.security.access.AccessDeniedException: Доступ запрещен
    at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:73) ~[spring-security-core-5.7.4.jar:5.7.4]
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.attemptAuthorization(AbstractSecurityInterceptor.java:239) ~[spring-security-core-5.7.4.jar:5.7.4]
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:208) ~[spring-security-core-5.7.4.jar:5.7.4]
    at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:58) ~[spring-security-core-5.7.4.jar:5.7.4]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.23.jar:5.3.23]
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215) ~[spring-aop-5.3.23.jar:5.3.23]
    at jdk.proxy2/jdk.proxy2.$Proxy107.save(Unknown Source) ~[na:na]
    at com.egor.todo.DatabaseLoader.run(DatabaseLoader.java:40) ~[classes/:na]
    at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:771) ~[spring-boot-2.7.5.jar:2.7.5]
    ... 5 common frames omitted

Entities:

TodoEntity.java

package com.egor.todo.entity;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import java.util.Objects;

@Entity
public class TodoEntity {
    private @Id @GeneratedValue long id;
    private String name;
    private String description;
    private boolean done;

    private @ManyToOne UserEntity entity;

    public UserEntity getEntity() {
        return entity;
    }

    public void setEntity(UserEntity entity) {
        this.entity = entity;
    }

    public TodoEntity() {
    }

    public TodoEntity(String name, String description, boolean done, UserEntity entity) {
        this.name = name;
        this.description = description;
        this.done = done;
        this.entity = entity;
    }

    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 getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public boolean isDone() {
        return done;
    }

    public void setDone(boolean done) {
        this.done = done;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        TodoEntity that = (TodoEntity) o;
        return id == that.id && done == that.done && Objects.equals(name, that.name) && Objects.equals(description, that.description);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name, description, done);
    }

    @Override
    public String toString() {
        return "TodoEntity{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", description='" + description + '\'' +
                ", done=" + done +
                '}';
    }
}

UserEntity.java

package com.egor.todo.entity;

import com.fasterxml.jackson.annotation.JsonIgnore;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.util.Arrays;
import java.util.Objects;

@Entity
public class UserEntity {

    public static final PasswordEncoder PASSWORD_ENCODER = new BCryptPasswordEncoder();

    private @Id @GeneratedValue Long id;

    private String name;

    private @JsonIgnore String password;

    private String[] roles;

    public void setPassword(String password) {
        this.password = PASSWORD_ENCODER.encode(password);
    }

    public UserEntity() {}

    public UserEntity(String name, String password, String... roles) {

        this.name = name;
        this.setPassword(password);
        this.roles = roles;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        UserEntity entity = (UserEntity) o;
        return Objects.equals(id, entity.id) &&
                Objects.equals(name, entity.name) &&
                Objects.equals(password, entity.password) &&
                Arrays.equals(roles, entity.roles);
    }

    @Override
    public int hashCode() {

        int result = Objects.hash(id, name, password);
        result = 31 * result + Arrays.hashCode(roles);
        return result;
    }

    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 getPassword() {
        return password;
    }

    public String[] getRoles() {
        return roles;
    }

    public void setRoles(String[] roles) {
        this.roles = roles;
    }

    @Override
    public String toString() {
        return "UserEntity{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", roles=" + Arrays.toString(roles) +
                '}';
    }
}

I'm trying:

Change call field to getter and back.

Problem is not solved. What?

Changelog:

  1. I'm added a entity code.

Solution

  • As it turns out, I entered the wrong parameter when calling the "public UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities)" constructor. It was necessary to enter the same value in the "principal" field as in the "name" field of the UserEntity constructor.