java 带有Spring Security和Keycloak的Sping Boot 仅提供匿名身份验证令牌

r1zhe5dt  于 2023-01-15  发布在  Java
关注(0)|答案(1)|浏览(160)

所以我按照一个指南设置了Keycloak,以通过Spring Security保护我的Sping Boot REST API。以前,我的应用程序依赖于一个标准的用户详细信息服务来验证用户。我的代码依赖于能够访问当前登录用户的用户名。然而,现在我切换到Keycloak,我只接收到用户名“anonymousUser”。
如何正确配置此选项,使以下代码返回首选用户名:
public static String getCurrentUsername() {
return (String) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}
我尝试在Keycloak上修改客户端和用户的配置,但即使是举个例子,添加角色也没有效果,Sping Boot 只返回[ANONYMOUS_USER]的角色。

s4n0splo

s4n0splo1#

您不可能从AnonymousAuthenticationToken获取当前用户的任何信息:Authentication的此实现在请求未成功授权(无法识别用户)时置于安全上下文中。这意味着:

  • 授权标头中缺少访问令牌
  • 访问令牌无效(过期、由错误的授权服务器颁发、签名检查失败...)
  • resource-server配置错误(这很可能是因为您正在编写REST API的客户端配置,而REST API实际上是resource-server

关于如何使用Keycloak配置Spring-boot 3资源服务器(和客户端)的基本信息,请参阅我对这个问题的回答:Use Keycloak Spring Adapter with Spring Boot 3
在resource-servers中成功进行OAuth2授权的Spring-security默认AuthenticationJwtAuthenticationToken,它返回Jwt示例principal。您可以从此Jwt示例中获取任何访问令牌声明。下面是preferred_username的示例。打开一个访问-https://jwt.io等工具中的一个令牌,用于查找具有您正在查找的用户名值的声明的名称(它也可以是subemail或您在Keycloak中配置的任何私有声明)

((Jwt) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getClaims().get(StandardClaimNames.PREFERRED_USERNAME);

如果你希望getPrincipal()返回用户名而不是Jwt示例,你必须提供你自己的Authentication实现,你可以使用JwtAuthenticationToken作为基础(但这不是最佳选择,请参阅下面的注解):

public class MyAuthentication extends JwtAuthenticationToken {
    public MyAuthentication(Jwt jwt, Collection<? extends GrantedAuthority> authorities) {
        super(jwt, authorities);
    }
    
    @Override
    public String getPrincipal() {
        return getToken().getClaimAsString(StandardClaimNames.PREFERRED_USERNAME);
    }   
}

只需根据我上面链接的答案调整Jwt2AuthenticationConverter即可

@Bean
public Jwt2AuthenticationConverter authenticationConverter(Jwt2AuthoritiesConverter authoritiesConverter) {
    return jwt -> new MyAuthentication(jwt, authoritiesConverter.convert(jwt));
}

重要提示

您最好使用authentication.getName()而不是authentication.getPrincipal()来访问用户名。principalAuthentication中被键入为Object,这使得您的表达式非常脆弱:您可以获得任何类型的数据作为主体(取决于安全上下文中的身份验证类型),并且在某些情况下您实际上无法控制它(例如,由于未授权请求,您当前拥有的AnonymousAuthenticationToken示例)。
但是,JwtAuthenticationToken::getName返回subject(sub claim),因此您仍然需要提供自己的Authentication实现,以便成功授权在getName()的新@Override中返回preferred_usernameMyAuthentication将为:

public class MyAuthentication extends JwtAuthenticationToken {
    public MyAuthentication(Jwt jwt, Collection<? extends GrantedAuthority> authorities) {
        super(jwt, authorities);
    }
    
    //Note that this time getName() is overriden instead of getPrincipal()
    @Override
    public String getName() {
        return getToken().getClaimAsString(StandardClaimNames.PREFERRED_USERNAME);
    }   
}

如果请求授权成功,使用相同的Jwt2AuthenticationConverter bean从默认JwtAuthenticationToken切换到MyAuthentication

相关问题