来自授权服务器的Spring Security-主体与资源服务器不同

我在授权服务器中创建了userinfo终结点。

@GetMapping("/userinfo")
public Principal me(Principal principal) {
    return principal;
}

它返回以下JSON:

{
    ...
    "userAuthentication": {
        ...
        "principal": {
            "id": 2,
            "username": "xyz",
            "password": "......",
            "accountNonExpired": true,
            "accountNonLocked": true,
            "credentialsNonExpired": true,
            "enabled": true,
            "authorities": [
                {
                "authority": "ROLE_DONOR"
                }
            ],
            "createdAt": "2019-11-08T20:50:46"
        },
        ...
        "name": "xyz"
    },
    ...
    "principal": {
        "id": 2,
        "username": "xyz",
        "password": "......",
        "accountNonExpired": true,
        "accountNonLocked": true,
        "credentialsNonExpired": true,
        "enabled": true,
        "authorities": [
        {
            "authority": "ROLE_DONOR"
        }
        ],
        "createdAt": "2019-11-08T20:50:46"
    },
    ...
    "name": "xyz"
}

在我的资源服务器用户服务API中,我尝试了sysoutPrincipal的值,只是为了查看它的值:

@GetMapping("/{id}")
public ResourceResponseDto findById(@PathVariable("id") long id, Principal principal) throws IOException {
    ObjectMapper objectMapper = new ObjectMapper();
    String x = objectMapper.writeValueAsString(principal);
    System.out.println(x);
    return ...;
}
principal的值不同。其取值相当于上述principal.username,省略了其他字段:

{
    "userAuthentication": {
        ...
        "principal": "xyz",
        ...
        "name": "xyz"
    },
    ...
    "principal": "xyz",
    ...
    "name": "xyz"
}

这是如何发生的?

我需要获取id的值,但它不见了。principal对象的字段已消失。这会导致我的其他方法出错:

@GetMapping("/{id}")
@PreAuthorize("hasRole('ADMIN') or #id == principal.id")
public ResourceResponseDto findById(@PathVariable("id") long id) {
    //
}

我收到此错误;

Failed to evaluate expression 'hasRole('ADMIN') or #id == principal.id'

请帮帮忙。谢谢。


解决方案

可能很晚了,但这是我的解决方案。 因为我在我的资源服务器中使用了UserInfoTokenServices,所以我发现它使用了FixedEpalExtractor,在这个类中我看到了以下内容:

private static final String[] PRINCIPAL_KEYS = new String[] { "user", "username",
            "userid", "user_id", "login", "id", "name" };

    @Override
    public Object extractPrincipal(Map<String, Object> map) {
        for (String key : PRINCIPAL_KEYS) {
            if (map.containsKey(key)) {
                return map.get(key);
            }
        }
        return null;
    }

所以它直接返回应用程序中的‘USER’值‘xyz’。 因此,我创建了一个类来实现ArchalExtractor,并重写他俩的方法:

import java.util.Map;
import org.springframework.boot.autoconfigure.security.oauth2.resource.PrincipalExtractor;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSON;
import com.project.LoginUser;

@Component
public class CustomPrincipalExtractor implements PrincipalExtractor {

    @Override
    @SuppressWarnings("unchecked")
    public Object extractPrincipal(Map<String, Object> map) {
        Map<String, Object> principal = (Map<String, Object>) map.get("principal");
        return JSON.parseObject(JSON.toJSONString(principal), LoginUser.class);
    }

}

此处的参数映射类似于授权服务器中的主体,因此我的LoginUser类具有‘main’键,它实现了UserDetail,并添加了一些附加信息,如id、电子邮件...就像你的校长一样。在这里,我使用fast json来解析它,您也可以使用ObjectMapper。 然后在您的资源服务器中定义UserInfoTokenServices Bean。以下是我的代码:

@EnableResourceServer
@EnableWebSecurity
@RequiredArgsConstructor
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    private final ResourceServerProperties sso;
    private final CustomPrincipalExtractor customPrincipalExtractor;

    @Primary
    @Bean
    public UserInfoTokenServices tokenService() {
        UserInfoTokenServices userInfoTokenServices = new UserInfoTokenServices(sso.getUserInfoUri(), sso.getClientId());
        userInfoTokenServices.setPrincipalExtractor(customPrincipalExtractor);
        return userInfoTokenServices;
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        ...
    }
}

在这里,我创建了UserInfoTokenServices,并设置了我的自定义ArchalExtractor。不要忘记添加这些属性:

security.oauth2.resource.user-info-uri = http://domain:port/your/user-info-url
security.oauth2.resource.prefer-token-info = false

现在您可以在资源服务器中获取您自己的主体对象。

相关文章