我在授权服务器中创建了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'
请帮助。谢谢。
答案 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
现在,您可以在资源服务器中获得自己的主体对象。