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

时间:2019-12-18 12:02:35

标签: java spring spring-boot spring-security spring-security-oauth2

我在授权服务器中创建了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中,我尝试sysout的值Principal只是为了查看其值:

@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'

请帮助。谢谢。

3 个答案:

答案 0 :(得分:1)

据我了解,您正在实现OAuth2 Spring Authentication Server。 Principle只是一个声明方法getName()的接口。对于身份验证和资源服务器,principle由类OAuth2Authentication实现。方法getPrinciple()

public Object getPrincipal() {
    return this.userAuthentication == null ? this.storedRequest.getClientId() : this.userAuthentication.getPrincipal();
}

如果正在验证OAuth2客户端,则它没有userAuthentication,只有clientId。如果正在对用户进行身份验证,则会看到完整的userAuthentication对象。

答案 1 :(得分:1)

摘自Authentication.getPrincipal()文档:

  

正在认证的主体的身份。如果使用用户名和密码进行身份验证请求,则使用用户名。呼叫者应填充身份验证请求的主体。   AuthenticationManager实现通常会返回一个   包含更多信息作为主体使用的身份验证   应用程序。许多身份验证提供程序将创建一个   UserDetails对象作为主体。

因此,您负责在身份验证过程中填充Principal

将其他信息传递到Principal的一种方法是扩展UsernamePasswordAuthenticationFilter并覆盖'attemptAuthentication()'方法:

@Override
public Authentication attemptAuthentication(HttpServletRequest request, 
                 HttpServletResponse response) {
    String username = obtainUsername(request);
    String password = obtainPassword(request);

    UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
    setDetails(request, authRequest);

    Authentication authenticated = this.getAuthenticationManager().authenticate(authRequest);

    return new UsernamePasswordAuthenticationToken(
            new YourObjectForPrincipal(...),
            authenticated.getCredentials(), authenticated.getAuthorities());
}

请注意传递YourObjectForPrincipal对象,该对象可以包含所需的任何数据,作为UsernamePasswordAuthenticationToken的第一个参数。 要获得扩展的用户,只需将其投射到所需的对象上即可:

(YourObjectForPrincipal)authentication.getPrincipal();

这可能不是最佳解决方案,但对我有用。 希望对您有所帮助。

答案 2 :(得分:1)

也许已经晚了,但这是我的解决方案。 由于我在资源服务器中使用了 UserInfoTokenServices ,因此我发现它使用了 FixedPrincipalExtractor ,在此类中,我看到了这样的信息:

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;
    }

因此它直接返回了“用户”值,该值在您的应用中为“ xyz”。 因此,我创建了一个用于实现 PrincipalExtractor 的类,并覆盖了 extractPrincipal 方法:

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);
    }

}

此处参数映射类似于授权服务器中的 Principal ,因此我使用“ principal”键获取了LoginUser类,该类实现了UserDetails并添加了一些其他信息,例如id,email ...你的校长在这里,我使用fastjson进行解析,也可以使用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 ,并设置了自定义 PrincipalExtractor 。不要忘记添加以下属性:

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

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