简体   繁体   English

Spring 使用 Spring 安全和 Keycloak 启动,仅提供匿名身份验证令牌

[英]Spring Boot with Spring Security and Keycloak only providing Anonymous Authentication Token

so I followed a guide which set up Keycloak to secure my Spring Boot REST API with Spring Security.所以我按照指南设置了 Keycloak 来保护我的 Spring Boot REST API 和 Spring Security。 Previously, my application relied on a standard user details service to authenticate users.以前,我的应用程序依赖于标准用户详细信息服务来验证用户身份。 My code relies on being able to access the currently logged-in user's username.我的代码依赖于能够访问当前登录用户的用户名。 However, now that I switched to Keycloak, I only receive the username 'anonymousUser'.但是,现在我切换到 Keycloak,我只收到用户名“anonymousUser”。

How do I configure this correctly for the following code to return the preferred username:如何为以下代码正确配置它以返回首选用户名:

public static String getCurrentUsername() {

return (String) SecurityContextHolder.getContext().getAuthentication().getPrincipal();

}

I tried to modify the configuration of the client and user on Keycloak, but even for example, adding roles has no effect.我尝试在Keycloak上修改客户端和用户的配置,但即使是例如,添加角色也没有效果。 Spring Boot only returns the role of [ANONYMOUS_USER]. Spring 引导仅返回 [ANONYMOUS_USER] 的角色。

There is no chance you get any info about current user from AnonymousAuthenticationToken : this implementation of Authentication is put in security-context when the request was not successfully authorized (user could not be identified).您不可能从AnonymousAuthenticationToken获得有关当前用户的任何信息:当请求未成功授权(无法识别用户)时,此Authentication实现将置于安全上下文中。 This means that either:这意味着:

  • access token is missing from authorization header授权中缺少访问令牌 header
  • access token is invalid (expired, issued by wrong authorization-server, signature check failed, ...)访问令牌无效(已过期,由错误的授权服务器颁发,签名检查失败,...)
  • resource-server is misconfigured (which is quite likely as you are writing about client configuration for a REST API which actually is a resource-server )资源服务器配置错误(这很可能是因为您正在编写 REST API 实际上是资源服务器客户端配置)

For basic info on how to configure Spring-boot 3 resource-server (and client) with Keycloak, see my answer to this question: Use Keycloak Spring Adapter with Spring Boot 3有关如何使用 Keycloak 配置 Spring-boot 3 资源服务器(和客户端)的基本信息,请参阅我对这个问题的回答: Use Keycloak Spring Adapter with Spring Boot 3

Spring-security default Authentication for successful OAuth2 authorization in resource-servers is JwtAuthenticationToken which return a Jwt instance as principal . Spring-security 资源服务器中成功的 OAuth2 授权的默认AuthenticationJwtAuthenticationToken ,它返回一个Jwt实例作为principal You can get any access-token claim from this Jwt instance.您可以从此Jwt实例获取任何访问令牌声明。 Here is a sample with preferred_username .这是带有preferred_username的示例。 Open an access-token in a tool like https://jwt.io to find the name of the claim with the username value you are looking for (it could be as well sub , email or any private claim you configured in Keycloak)在诸如https://jwt.io之类的工具中打开一个访问令牌,以查找具有您要查找的用户名值的声明的名称(它也可以是subemail或您在 Keycloak 中配置的任何私人声明)

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

If you want getPrincipal() to return the username instead of a Jwt instance, you'll have to provide your own Authentication implementation.如果您希望getPrincipal()返回用户名而不是Jwt实例,则必须提供您自己的Authentication实现。 You might use JwtAuthenticationToken as a base (but this not the best option, see the note below):您可以使用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);
    }   
}

Just adapt the Jwt2AuthenticationConverter from the answer I linked above只需根据我上面链接的答案改编Jwt2AuthenticationConverter

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

Important note重要的提示

You'd better use authentication.getName() instead of authentication.getPrincipal() to access username.您最好使用authentication.getName()而不是authentication.getPrincipal()来访问用户名。 principal is typed as Object in Authentication which makes your expressions very fragile: you can get absolutely any type of data as principal (depending on the type of authentication in the security context) and there are cases where you don't really control it (for instance with the AnonymousAuthenticationToken instance you currently have because of unauthorized request). principalAuthentication中的类型为Object ,这使得您的表达式非常脆弱:您绝对可以获得任何类型的数据作为 principal(取决于安全上下文中的身份验证类型)并且在某些情况下您无法真正控制它(例如由于未经授权的请求,您当前拥有的AnonymousAuthenticationToken实例)。

However, JwtAuthenticationToken::getName returns subject ( sub claim), so you'll still have to provide your own Authentication implementation for successful authorizations to return preferred_username in a new @Override of getName() .但是, JwtAuthenticationToken::getName返回主题( sub声明),因此您仍然必须提供自己的Authentication实现才能成功授权以在新的 @Override getName()中返回preferred_username MyAuthentication would then be: MyAuthentication将是:

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

Use the same Jwt2AuthenticationConverter bean to switch from default JwtAuthenticationToken to MyAuthentication in case of successful request authorization.在请求授权成功的情况下,使用相同的Jwt2AuthenticationConverter bean 从默认的JwtAuthenticationToken切换到MyAuthentication

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM