spring-projects / spring-security

Spring Security
http://spring.io/projects/spring-security
Apache License 2.0
8.55k stars 5.79k forks source link

fallback for method based authorization #15257

Closed Junhyunny closed 1 week ago

Junhyunny commented 1 week ago

I am following an example in the spring security document to handle fallback for method based authorization.

In this example, @HandleAuthorizationDenied annotation is on the POJO User class's getEmail method like this.

public class User {
    // ...

    @PostAuthorize(value = "hasAuthority('user:read')")
    @HandleAuthorizationDenied(handlerClass = EmailMaskingMethodAuthorizationDeniedHandler.class)
    public String getEmail() {
        return this.email;
    }
}

This is not working in my test code. Here is my codes.

User class

package com.example.demo.domain;

import com.example.demo.handler.EmailMaskingMethodAuthorizationDeniedHandler;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.authorization.method.HandleAuthorizationDenied;

public class User {
    private final String email;

    public User(String email) {
        this.email = email;
    }

    @PostAuthorize(value = "hasAuthority('USER::READ')")
    @HandleAuthorizationDenied(handlerClass = EmailMaskingMethodAuthorizationDeniedHandler.class)
    public String getEmail() {
        return email;
    }
}

UserService class

package com.example.demo.service;

import com.example.demo.domain.User;
import org.springframework.stereotype.Service;

@Service
public class UserService {

    public User getUser() {
        return new User("junhyunny@naver.com");
    }
}

UserServiceTests class

package com.example.demo;

import com.example.demo.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class UserServiceTests {

    @Autowired
    UserService sut;

    @Test
    void test() {
        var result = sut.getUser();

        System.out.println(result.getEmail());
    }
}

Result is this.

junhyunny@naver.com

Is there a something what I miss? Please let me know. Is it working well? I tried this example with spring data jpa because I doubted that there are some mechanism working together with jpa repository. However the trial with jpa was also failed.

I think that @PostAuthorize annotation is working based on Spring AOP, so we need to this annotation should be on the UserService bean's getUser method.

Like this.

@Service
public class UserService {

    @PostAuthorize(value = "hasAuthority('USER::READ')")
    @HandleAuthorizationDenied(handlerClass = EmailMaskingMethodAuthorizationDeniedHandler.class)
    public User getUser() {
        return new User("junhyunny@naver.com");
    }
}

And MethodAuthorizationDeniedHandler instance is changed like this.

@Component
public class EmailMaskingMethodAuthorizationDeniedHandler implements MethodAuthorizationDeniedHandler {

    @Override
    public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
        return "***";
    }

    @Override
    public Object handleDeniedInvocationResult(MethodInvocationResult methodInvocationResult, AuthorizationResult authorizationResult) {
        User user = (User) methodInvocationResult.getResult(); // get user
        return new User(user.getEmail().replaceAll("(^[^@]{3}|(?!^)\\G)[^@]", "$1*")); // return new user instance
    }
}

When I changed the code like above. Test result is this.

jun******@naver.com

The example in the document is wrong or not? If sample code in the example is wrong, can I change like this?

marcusdacoregio commented 1 week ago

Hi @Junhyunny, thanks for the report. Yes, the feature relies on Spring AOP. The documentation says:

Let’s consider the example from the previous section

The previous section uses @AuthorizeReturnObject in the service class, effectively generating a proxy for the User class. Can you try adding the annotation and see if it works?

Junhyunny commented 1 week ago

Great! It is worked. Thanks, I am going to close this issue.