如何从 PermissionEvaluator 内部访问 Keycloak 的 AuthorizationContext?
How to access Keycloak's AuthorizationContext from inside a PermissionEvaluator?
我正在尝试使用 spring 引导和 Keycloak 在我的应用程序中实现细粒度权限。
因此我想使用以下注释:
@PreAuthorize("hasPermission('myResource', 'myScope')")
所以我实现了以下内容:
@Slf4j
public class KeycloakPermissionEvaluator implements PermissionEvaluator {
@Override
public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
// This should probably never be the case but let's check it anyways
if (authentication == null) {
log.warn("Permission denied because of illegal 'authentication' argument (null)");
return false;
}
// targetDomainObject will be the resourceName.
// It can be null in case we only want to check permissions for a specific scope else it should be a String
if (targetDomainObject != null && !(targetDomainObject instanceof String)) {
log.warn("Permission denied because of illegal 'targetDomainObject' argument ({})", targetDomainObject.getClass().getName());
return false;
}
// permission will be the scopeName.
// It can be null in case we only want to check permissions for a specific resource else it should be a String
if (permission != null && !(permission instanceof String)) {
log.warn("Permission denied because of illegal 'permission' argument ({})", permission.getClass().getName());
return false;
}
return hasPermission((KeycloakPrincipal) authentication.getPrincipal(), (String) targetDomainObject, (String) permission);
}
@Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
// This should probably never be the case but let's check it anyways
if (authentication == null) {
log.warn("Permission denied because of illegal 'authentication' argument (null)");
return false;
}
// targetId needs to be set else we should be using hasPermission(Authentication, Object, Object)
// It will be used to build the resourceName
if (!(targetId instanceof String)) {
log.warn("Permission denied because of illegal 'targetId' argument ({}). Did you mean to use hasPermission(resource, scope) ?", targetId == null ? "null" : targetId.getClass().getName());
return false;
}
// targetType will be used to build the resourceName
if (targetType == null) {
log.warn("Permission denied because of illegal 'targetType' argument (null)");
return false;
}
// permission will be the scopeName.
// It can be null in case we only want to check permissions for a specific resource else it should be a String
if (permission != null && !(permission instanceof String)) {
log.warn("Permission denied because of illegal 'permission' argument ({})", permission.getClass().getName());
return false;
}
String resourceName = String.format("%s/%s", targetType, targetId);
return hasPermission((KeycloakPrincipal) authentication.getPrincipal(), resourceName, (String) permission);
}
private boolean hasPermission(KeycloakPrincipal principal, String resourceName, String scopeName) {
AuthorizationContext authorizationContext = principal.getKeycloakSecurityContext().getAuthorizationContext();
if (resourceName == null) {
if (scopeName == null) {
log.warn("Permission denied because both resourceName and scopeName are null.");
return false;
}
return authorizationContext.hasScopePermission(scopeName);
} else if (scopeName == null) {
return authorizationContext.hasResourcePermission(resourceName);
} else {
return authorizationContext.hasPermission(resourceName, scopeName);
}
}
}
但这似乎不起作用。 authorizationContext
最终成为 null
。
这是 hasPermission
中身份验证对象的样子
这是我的安全配置
@Slf4j
@KeycloakConfiguration
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
/**
* Registers the KeycloakAuthenticationProvider with the authentication manager.
*/
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
/**
* Defines the session authentication strategy.
*/
@Bean
@Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new NullAuthenticatedSessionStrategy();
}
@Override
protected AuthenticationEntryPoint authenticationEntryPoint() {
return (request, response, authException) -> {
log.warn("401 Unauthorized while processing " + request.getRequestURI(), authException);
response.sendError(401, "Unauthorized");
};
}
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
// @formatter:off
http
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().headers()
.frameOptions().sameOrigin()
.and().authorizeRequests()
// Public
.antMatchers(
"/v3/api-docs/**",
"/swagger-ui.html",
"/swagger-ui/**").permitAll()
// Private
.anyRequest().authenticated();
// @formatter:on
}
}
和我的方法安全配置
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
@Autowired
private ApplicationContext applicationContext;
@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(new KeycloakPermissionEvaluator());
expressionHandler.setApplicationContext(applicationContext);
return expressionHandler;
}
}
Keycloak 配置:
{
"realm": "${env.KEYCLOAK_REALM}",
"auth-server-url": "${env.KEYCLOAK_AUTH_SERVER}",
"ssl-required": "external",
"enable-cors": true,
"resource": "my-server",
"credentials": {
"secret": "${env.KEYCLOAK_SECRET}"
},
"confidential-port": 0,
"policy-enforcer": {
"enforcement-mode": "PERMISSIVE",
"paths": [
{
"name": "Default",
"path": "*/*"
}
]
}
}
这似乎与 KEYCLOAK-12260
有关
我添加了一个控制器来展示我的问题:
@RestController
@RequestMapping("/test")
public class TestController {
@Autowired
HttpServletRequest autowWiredRequest;
@GetMapping
public String hello(Principal principal) {
KeycloakAuthenticationToken principal1 = (KeycloakAuthenticationToken) this.autowWiredRequest.getUserPrincipal();
KeycloakAuthenticationToken principal2 = (KeycloakAuthenticationToken) ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getUserPrincipal();
KeycloakAuthenticationToken principal3 = (KeycloakAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
boolean allTheSame = (principal1 == principal2 && principal2 == principal3);
KeycloakPrincipal<?> keycloakPrincipal = (KeycloakPrincipal<?>) principal1.getPrincipal();
KeycloakSecurityContext keycloakSecurityContext = keycloakPrincipal.getKeycloakSecurityContext();
AuthorizationContext authorizationContext = keycloakSecurityContext.getAuthorizationContext();
return authorizationContext == null ? "null" : "OMG we're there!";
}
}
这是调试的样子:
这里还有一些有趣的东西:似乎还有另一个 SecurityContext 包含我想要的所有内容
经过大量挖掘和同事的一些帮助,发现某些对象具有多个 bean 的事实引导我们 that page
因此将以下行添加到 SecurityContext 使 AuthorizationContext 可用。
@Bean
public FilterRegistrationBean keycloakAuthenticationProcessingFilterRegistrationBean(
KeycloakAuthenticationProcessingFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
registrationBean.setEnabled(false);
return registrationBean;
}
@Bean
public FilterRegistrationBean keycloakPreAuthActionsFilterRegistrationBean(
KeycloakPreAuthActionsFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
registrationBean.setEnabled(false);
return registrationBean;
}
@Bean
public FilterRegistrationBean keycloakAuthenticatedActionsFilterBean(
KeycloakAuthenticatedActionsFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
registrationBean.setEnabled(false);
return registrationBean;
}
@Bean
public FilterRegistrationBean keycloakSecurityContextRequestFilterBean(
KeycloakSecurityContextRequestFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
registrationBean.setEnabled(false);
return registrationBean;
}
现在我希望与之合作的东西还没有奏效,但至少我在这里的问题得到了回答。
编辑:我走得更远,遇到了更多阻力。我直接在Keycloak Jira上提了个issue:KEYCLOAK-16515
我正在尝试使用 spring 引导和 Keycloak 在我的应用程序中实现细粒度权限。
因此我想使用以下注释:
@PreAuthorize("hasPermission('myResource', 'myScope')")
所以我实现了以下内容:
@Slf4j
public class KeycloakPermissionEvaluator implements PermissionEvaluator {
@Override
public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
// This should probably never be the case but let's check it anyways
if (authentication == null) {
log.warn("Permission denied because of illegal 'authentication' argument (null)");
return false;
}
// targetDomainObject will be the resourceName.
// It can be null in case we only want to check permissions for a specific scope else it should be a String
if (targetDomainObject != null && !(targetDomainObject instanceof String)) {
log.warn("Permission denied because of illegal 'targetDomainObject' argument ({})", targetDomainObject.getClass().getName());
return false;
}
// permission will be the scopeName.
// It can be null in case we only want to check permissions for a specific resource else it should be a String
if (permission != null && !(permission instanceof String)) {
log.warn("Permission denied because of illegal 'permission' argument ({})", permission.getClass().getName());
return false;
}
return hasPermission((KeycloakPrincipal) authentication.getPrincipal(), (String) targetDomainObject, (String) permission);
}
@Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
// This should probably never be the case but let's check it anyways
if (authentication == null) {
log.warn("Permission denied because of illegal 'authentication' argument (null)");
return false;
}
// targetId needs to be set else we should be using hasPermission(Authentication, Object, Object)
// It will be used to build the resourceName
if (!(targetId instanceof String)) {
log.warn("Permission denied because of illegal 'targetId' argument ({}). Did you mean to use hasPermission(resource, scope) ?", targetId == null ? "null" : targetId.getClass().getName());
return false;
}
// targetType will be used to build the resourceName
if (targetType == null) {
log.warn("Permission denied because of illegal 'targetType' argument (null)");
return false;
}
// permission will be the scopeName.
// It can be null in case we only want to check permissions for a specific resource else it should be a String
if (permission != null && !(permission instanceof String)) {
log.warn("Permission denied because of illegal 'permission' argument ({})", permission.getClass().getName());
return false;
}
String resourceName = String.format("%s/%s", targetType, targetId);
return hasPermission((KeycloakPrincipal) authentication.getPrincipal(), resourceName, (String) permission);
}
private boolean hasPermission(KeycloakPrincipal principal, String resourceName, String scopeName) {
AuthorizationContext authorizationContext = principal.getKeycloakSecurityContext().getAuthorizationContext();
if (resourceName == null) {
if (scopeName == null) {
log.warn("Permission denied because both resourceName and scopeName are null.");
return false;
}
return authorizationContext.hasScopePermission(scopeName);
} else if (scopeName == null) {
return authorizationContext.hasResourcePermission(resourceName);
} else {
return authorizationContext.hasPermission(resourceName, scopeName);
}
}
}
但这似乎不起作用。 authorizationContext
最终成为 null
。
这是 hasPermission
中身份验证对象的样子
这是我的安全配置
@Slf4j
@KeycloakConfiguration
public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
/**
* Registers the KeycloakAuthenticationProvider with the authentication manager.
*/
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
/**
* Defines the session authentication strategy.
*/
@Bean
@Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new NullAuthenticatedSessionStrategy();
}
@Override
protected AuthenticationEntryPoint authenticationEntryPoint() {
return (request, response, authException) -> {
log.warn("401 Unauthorized while processing " + request.getRequestURI(), authException);
response.sendError(401, "Unauthorized");
};
}
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
// @formatter:off
http
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().headers()
.frameOptions().sameOrigin()
.and().authorizeRequests()
// Public
.antMatchers(
"/v3/api-docs/**",
"/swagger-ui.html",
"/swagger-ui/**").permitAll()
// Private
.anyRequest().authenticated();
// @formatter:on
}
}
和我的方法安全配置
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
@Autowired
private ApplicationContext applicationContext;
@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(new KeycloakPermissionEvaluator());
expressionHandler.setApplicationContext(applicationContext);
return expressionHandler;
}
}
Keycloak 配置:
{
"realm": "${env.KEYCLOAK_REALM}",
"auth-server-url": "${env.KEYCLOAK_AUTH_SERVER}",
"ssl-required": "external",
"enable-cors": true,
"resource": "my-server",
"credentials": {
"secret": "${env.KEYCLOAK_SECRET}"
},
"confidential-port": 0,
"policy-enforcer": {
"enforcement-mode": "PERMISSIVE",
"paths": [
{
"name": "Default",
"path": "*/*"
}
]
}
}
这似乎与 KEYCLOAK-12260
有关我添加了一个控制器来展示我的问题:
@RestController
@RequestMapping("/test")
public class TestController {
@Autowired
HttpServletRequest autowWiredRequest;
@GetMapping
public String hello(Principal principal) {
KeycloakAuthenticationToken principal1 = (KeycloakAuthenticationToken) this.autowWiredRequest.getUserPrincipal();
KeycloakAuthenticationToken principal2 = (KeycloakAuthenticationToken) ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getUserPrincipal();
KeycloakAuthenticationToken principal3 = (KeycloakAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
boolean allTheSame = (principal1 == principal2 && principal2 == principal3);
KeycloakPrincipal<?> keycloakPrincipal = (KeycloakPrincipal<?>) principal1.getPrincipal();
KeycloakSecurityContext keycloakSecurityContext = keycloakPrincipal.getKeycloakSecurityContext();
AuthorizationContext authorizationContext = keycloakSecurityContext.getAuthorizationContext();
return authorizationContext == null ? "null" : "OMG we're there!";
}
}
这是调试的样子:
这里还有一些有趣的东西:似乎还有另一个 SecurityContext 包含我想要的所有内容
经过大量挖掘和同事的一些帮助,发现某些对象具有多个 bean 的事实引导我们 that page
因此将以下行添加到 SecurityContext 使 AuthorizationContext 可用。
@Bean
public FilterRegistrationBean keycloakAuthenticationProcessingFilterRegistrationBean(
KeycloakAuthenticationProcessingFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
registrationBean.setEnabled(false);
return registrationBean;
}
@Bean
public FilterRegistrationBean keycloakPreAuthActionsFilterRegistrationBean(
KeycloakPreAuthActionsFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
registrationBean.setEnabled(false);
return registrationBean;
}
@Bean
public FilterRegistrationBean keycloakAuthenticatedActionsFilterBean(
KeycloakAuthenticatedActionsFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
registrationBean.setEnabled(false);
return registrationBean;
}
@Bean
public FilterRegistrationBean keycloakSecurityContextRequestFilterBean(
KeycloakSecurityContextRequestFilter filter) {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(filter);
registrationBean.setEnabled(false);
return registrationBean;
}
现在我希望与之合作的东西还没有奏效,但至少我在这里的问题得到了回答。
编辑:我走得更远,遇到了更多阻力。我直接在Keycloak Jira上提了个issue:KEYCLOAK-16515