英文:
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<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);
}
}
答案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( "preferred_username" );
} 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 > keycloak.realm=cirta > keycloak.auth-server-url=http://localhost:8085/auth > keycloak.resource=cirta-api > keycloak.public-client=true > keycloak.cors=true > keycloak.bearer-only=true >
> ...
你可能需要将其更改为以下内容...
>
> yaml > keycloak.realm=cirta > keycloak.auth-server-url=http://localhost:8085/auth > keycloak.resource=cirta-api > keycloak.public-client=false # 或者删除该属性,因为false是默认值 > keycloak.cors=true > keycloak.bearer-only=true > keycloak.principal-attribute=preferred_username # 添加此行 >
> ...
在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
。可能的值包括sub
、preferred_username
、email
、name
、nickname
、given_name
、family_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
> keycloak.realm=cirta
> keycloak.auth-server-url=http://localhost:8085/auth
> keycloak.resource=cirta-api
> keycloak.public-client=true
> keycloak.cors=true
> keycloak.bearer-only=true
>
> …
You probably need to change that to this…
>
> yaml
> keycloak.realm=cirta
> keycloak.auth-server-url=http://localhost:8085/auth
> keycloak.resource=cirta-api
> keycloak.public-client=false # or delete the property, since false is the default
> keycloak.cors=true
> keycloak.bearer-only=true
> keycloak.principal-attribute=preferred_username # add this
>
> …
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<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();
}
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 = "auditorProvider")
public class AuditConfig {
@Bean
AuditorAware<String> auditorProvider() {
return new AuditorAwareConfig();
}
}
Auditable class:
@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
At entity class:
@Entity
@Table(name="FOO")
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.
"principal-attribute" : "preferred_username",
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论