如何在AuditorAware中获取Keycloak用户名

huangapple go评论68阅读模式
英文:

How to get Keycloak username in AuditorAware

问题

我已经使用Spring Data JPA实现了审计,完全按照这个文档进行操作。当我运行应用程序时,一切正常,但是当我部署WAR文件到Tomcat并尝试创建一个实体时,在getCurrentAuditor方法中出现错误。

我已经使用Keycloak保护了我的应用程序,所以在AuditorAwareConfig中,我试图获取Keycloak用户名,在调试过程中我发现request.getUserPrincipal()为null:

java.lang.NullPointerException: null
	at com.cevital.cirta.util.AuditorAwareConfig.getCurrentAuditor(AuditorAwareConfig.java:20) ~[classes/:0.0.1-SNAPSHOT

AuditorAwareConfig:

public class AuditorAwareConfig implements AuditorAware<String> {
	@Autowired
	private HttpServletRequest request;

	@Override
	public Optional<String> getCurrentAuditor() {
		KeycloakPrincipal<KeycloakSecurityContext> kp = (KeycloakPrincipal<KeycloakSecurityContext>) request.getUserPrincipal();
		String userName = kp.getKeycloakSecurityContext().getToken().getPreferredUsername();
		return Optional.ofNullable(userName);
	}
}
英文:

I have implemented Auditing with Spring Data JPA, following exactly this documentation. Everything works fine when I run the app, but when I deploy the WAR to Tomcat and try to create an entity, I get an error in the getCurrentAuditor method.

I have secured my app with keycloak, so in AuditorAwareConfig i am trying to get the keycloak username, and after debugging i found out that request.getUserPrincipal() is null :

java.lang.NullPointerException: null
	at com.cevital.cirta.util.AuditorAwareConfig.getCurrentAuditor(AuditorAwareConfig.java:20) ~[classes/:0.0.1-SNAPSHOT

AuditorAwareConfig :

public class AuditorAwareConfig implements AuditorAware&lt;String&gt; {
	@Autowired
	private HttpServletRequest request;

	@Override
	public Optional&lt;String&gt; getCurrentAuditor() {
		KeycloakPrincipal&lt;KeycloakSecurityContext&gt; kp = (KeycloakPrincipal&lt;KeycloakSecurityContext&gt;) request.getUserPrincipal();
		String userName = kp.getKeycloakSecurityContext().getToken().getPreferredUsername();
		return Optional.ofNullable(userName);
	}
}

答案1

得分: 4

最近我在我的应用程序中也做了同样的事情,但我没有使用Keycloak适配器,Spring Security 5为我们提供了所有需要与Keycloak或任何OAuth2提供商保护应用程序的功能。

另一个不同之处在于,我使用了Hibernate Envers,它使我也能审计删除操作。

要获取经过身份验证的用户,我是这样操作的。

   public static String extractUsernameFromAuthentication() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        String username;
        if (isNull(authentication)) {
            return null;
        }
        if (authentication instanceof JwtAuthenticationToken) {
            JwtAuthenticationToken token = (JwtAuthenticationToken) authentication;
            username = (String) token.getTokenAttributes().get("preferred_username");
        } else {
            username = authentication.getName();
        }
        return username;
    }

请记住,我没有使用Keycloak适配器。

英文:

I recently did the same thing in my applications but I didn't use the Keycloak adapter, Spring Security 5 provides all we need to secure our applications with Keycloak or with any Oauth2 provider.
Another difference, I use Hibernate Envers, which allow me to also audit delete operations.

To get the authenticated user, this is how I proceed.

   public static String extractUsernameFromAuthentication() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        String username;
        if ( isNull( authentication ) ) {
            return null;
        }
        if ( authentication instanceof JwtAuthenticationToken ) {
            JwtAuthenticationToken token = (JwtAuthenticationToken) authentication;
            username = (String) ( token ).getTokenAttributes().get( &quot;preferred_username&quot; );
        } else {
            username = authentication.getName();
        }
        return username;
    }

Remember, I do not use the Keycloak Adapter.

答案2

得分: 3

TL;DR — 在你的 application.properties 文件中,添加 keycloak.principal-attribute=preferred_username有条件地,你可能还需要设置 keycloak.public-client=false或完全删除该属性)。


详细版本

根据你的其他关于Keycloak的问题...

> ...
>
> 这是我的Spring配置:
>
> application.properties:
>
> yaml &gt; keycloak.realm=cirta &gt; keycloak.auth-server-url=http://localhost:8085/auth &gt; keycloak.resource=cirta-api &gt; keycloak.public-client=true &gt; keycloak.cors=true &gt; keycloak.bearer-only=true &gt;
> ...

你可能需要将其更改为以下内容...

>
> yaml &gt; keycloak.realm=cirta &gt; keycloak.auth-server-url=http://localhost:8085/auth &gt; keycloak.resource=cirta-api &gt; keycloak.public-client=false # 或者删除该属性,因为false是默认值 &gt; keycloak.cors=true &gt; keycloak.bearer-only=true &gt; keycloak.principal-attribute=preferred_username # 添加此行 &gt;
> ...

TL;DR中,我将关于 public-client 的建议限定为“有条件地”。我的意思是,在添加了 keycloak.principal-attribute 属性之后,你可能需要尝试切换 keycloak.public-client 的开启和关闭;具体取决于哪个设置适用于你的特定配置。

有关更多详细信息,请参阅Keycloak文档...

>> ...
>>
>> public-client(公共客户端)
>>
>> 如果设置为true,则适配器将不会向Keycloak发送客户端的凭据。这是可选的。默认值为false
>>
>> ...
>>
>> principal-attribute(主要属性)
>>
>> OpenID Connect ID Token属性,用于填充UserPrincipal名称。如果令牌属性为null,则默认为sub。可能的值包括subpreferred_usernameemailnamenicknamegiven_namefamily_name
>>
>> ...

英文:

TL;DR — In your application.properties file, add keycloak.principal-attribute=preferred_username. Conditionally, you might also need to set keycloak.public-client=false (or remove it completely).


The long-winded version

According to your other Keycloak-related question

> …
>
> This is my Spring Configuration :
>
> application.properties:
>
> yaml
&gt; keycloak.realm=cirta
&gt; keycloak.auth-server-url=http://localhost:8085/auth
&gt; keycloak.resource=cirta-api
&gt; keycloak.public-client=true
&gt; keycloak.cors=true
&gt; keycloak.bearer-only=true
&gt;

> …

You probably need to change that to this…

>
> yaml
&gt; keycloak.realm=cirta
&gt; keycloak.auth-server-url=http://localhost:8085/auth
&gt; keycloak.resource=cirta-api
&gt; keycloak.public-client=false # or delete the property, since false is the default
&gt; keycloak.cors=true
&gt; keycloak.bearer-only=true
&gt; keycloak.principal-attribute=preferred_username # add this
&gt;

> …

In the TL;DR I qualified my public-client suggestion as „Conditionally“. By that I mean after adding the keycloak.principal-attribute property, you might need to experiment with toggling keycloak.public-client on and off; depending on which setting works for your specific setup.

Refer to the Keycloak docs for more details…

>> …
>>
>> public-client
>>
>> If set to true, the adapter will not send credentials for the client to Keycloak. This is OPTIONAL. The default value is false.
>>
>> …
>>
>> principal-attribute
>>
>> OpenID Connect ID Token attribute to populate the UserPrincipal name with. If token attribute is null, defaults to sub. Possible values are sub, preferred_username, email, name, nickname, given_name, family_name.
>>
>> …

答案3

得分: 1

我有同样的问题,我的解决方案是:

为了获取用户,创建了一个配置感知类:

import java.security.Principal;
import java.util.Optional;
import javax.servlet.http.HttpServletRequest;
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
import org.keycloak.representations.AccessToken;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.AuditorAware;

public class AuditorAwareConfig implements AuditorAware<String> {
    @Autowired
    private HttpServletRequest request;

    @Override
    public Optional<String> getCurrentAuditor() {
        AccessToken accessToken = this.getKeycloakToken(request.getUserPrincipal());
        String userName = accessToken.getPreferredUsername();
        
        return Optional.ofNullable(userName);
    }
    
    private AccessToken getKeycloakToken(Principal principal) {
        KeycloakAuthenticationToken keycloakAuthenticationToken = (KeycloakAuthenticationToken) principal;
        return keycloakAuthenticationToken.getAccount().getKeycloakSecurityContext().getToken();
    }
}

启用 JPA 审计的类:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.AuditorAware;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@Configuration
@EnableJpaAuditing(auditorAwareRef = "auditorProvider")
public class AuditConfig {
    
    @Bean
    AuditorAware<String> auditorProvider() {
        return new AuditorAwareConfig();
    }
}

可审计类:

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@JsonIgnoreProperties(
        value = {"createdAt", "createdBy","updatedAt", "updatedBy"},
        allowGetters = true
)
public abstract class Auditable {

    @CreatedDate
    @Column(name="AUD_CREATE_AT", nullable = false, updatable = false)
    private Instant createdAt;
    
    @CreatedBy
    @Column(name="AUD_CREATE_BY", nullable = false, updatable = false)
    private String createdBy;

    @LastModifiedDate
    @Column(name="AUD_UPDATE_AT",nullable = false)
    private Instant updatedAt;
    
    @LastModifiedBy
    @Column(name="AUD_UPDATE_BY",nullable = false)
    private String updatedBy;
    //Getters & Setters
}

在实体类中:

@Entity
@Table(name="FOO")
public class FooEntity extends Auditable implements Serializable
...
英文:

I have the same problem and my solution is:

Configuration Aware class in order to get user:

import java.security.Principal;
import java.util.Optional;
import javax.servlet.http.HttpServletRequest;
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
import org.keycloak.representations.AccessToken;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.AuditorAware;

public class AuditorAwareConfig implements AuditorAware&lt;String&gt; {
    @Autowired
    private HttpServletRequest request;

    @Override
    public Optional&lt;String&gt; getCurrentAuditor() {
        AccessToken accessToken = this.getKeycloakToken(request.getUserPrincipal());
    	String userName = accessToken.getPreferredUsername();
        
        return Optional.ofNullable(userName);
    }
    
    private AccessToken getKeycloakToken(Principal principal) {
		KeycloakAuthenticationToken keycloakAuthenticationToken = (KeycloakAuthenticationToken) principal;
        return keycloakAuthenticationToken.getAccount().getKeycloakSecurityContext().getToken();
	}

Class enabling JPA Auditing:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.AuditorAware;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@Configuration
@EnableJpaAuditing(auditorAwareRef = &quot;auditorProvider&quot;)
public class AuditConfig {
	
	@Bean
    AuditorAware&lt;String&gt; auditorProvider() {
        return new AuditorAwareConfig();
    }

}

Auditable class:

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
@JsonIgnoreProperties(
        value = {&quot;createdAt&quot;, &quot;createdBy&quot;,&quot;updatedAt&quot;, &quot;updatedBy&quot;},
        allowGetters = true
)
public abstract class Auditable {

    @CreatedDate
    @Column(name=&quot;AUD_CREATE_AT&quot;, nullable = false, updatable = false)
    private Instant createdAt;
    
    @CreatedBy
    @Column(name=&quot;AUD_CREATE_BY&quot;, nullable = false, updatable = false)
    private String createdBy;

    @LastModifiedDate
    @Column(name=&quot;AUD_UPDATE_AT&quot;,nullable = false)
    private Instant updatedAt;
    
    @LastModifiedBy
    @Column(name=&quot;AUD_UPDATE_BY&quot;,nullable = false)
    private String updatedBy;
    //Getters &amp; Setters

At entity class:

@Entity
@Table(name=&quot;FOO&quot;)
public class FooEntity extends Auditable implements Serializable
...

答案4

得分: 0

在 Apache Tomcat 9 的更新中,将 keycloak.json 放置在 /WEB-INF/ 目录下,并在第一个部分的 realm、resource 等之后添加以下属性:

"principal-attribute" : "preferred_username",
英文:

For Apache Tomcat 9 update in /WEB-INF/keycloak.json and add the following attribute within first section after realm, resource etc.

&quot;principal-attribute&quot; : &quot;preferred_username&quot;,

huangapple
  • 本文由 发表于 2020年10月1日 18:35:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/64153617.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定