将 Spring 安全性全局方法安全性与 Keycloak 集成



我在使用Spring Security的Pre/Post授权注释和Servlet API与Keycloak集成时遇到问题。我调查了很多文章,教程和以下问题,但没有进一步的运气

  • 使用 keycloak 在 servlet 应用程序中获取用户角色
  • Spring Boot Keycloak - 如何获取分配给用户的角色列表?
  • 将弹簧安全注释与钥匙斗篷一起使用
  • Spring
  • 引导 + Spring 安全性 + 分层角色
  • 如何将基于方法的安全性添加到 Spring 引导项目?
  • 使用 Spring Security Java Config 配置 DefaultMethodSecurityExpressionHandler
  • SpringBoot + 基于方法的分层角色安全性:需要 ServletContext

我想要的只是删除ROLES_前缀,使用分层角色和一种舒适的方法来检索用户的角色。

截至目前,我能够在控制器中检索这样的分层角色,但无法使用注释:

@Controller
class HomeController {
@Autowired
AccessToken token
@GetMapping('/')
def home(Authentication auth, HttpServletRequest request) {
// Role 'admin' is defined in Keycloak for this application
assert token.getResourceAccess('my-app').roles == ['admin']
// All effective roles are mapped
assert auth.authorities.collect { it.authority }.containsAll(['admin', 'author', 'user'])
// (!) But this won't work:
assert request.isUserInRole('admin')
}
// (!) Leads to a 403: Forbidden
@GetMapping('/sec')
@PreAuthorize("hasRole('admin')") {
return "Hello World"
}
}

我猜@PreAuthorize注释不起作用,因为该 Servlet 方法不成功。

Keycloak和Spring中只定义了三个角色 - 管理员,作者,用户:

enum Role {
USER('user'),
AUTHOR('author'),
ADMIN('admin')
final String id
Role(String id) {
this.id = id
}
@Override
String toString() {
id
}
}

钥匙斗篷配置

从此网络安全中删除@EnableGlobalMethodSecurity注释后,会发现由No ServletContext set错误引起的Error creating bean with name 'resourceHandlerMapping'- 没有线索,这是从哪里来的!

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
/**
* Registers the KeycloakAuthenticationProvider with the authentication manager.
*/
@Autowired
void configureGlobal(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(keycloakAuthenticationProvider().tap { provider ->
// Assigns the Roles via Keycloaks role mapping
provider.grantedAuthoritiesMapper = userAuthoritiesMapper
})
}
@Bean
RoleHierarchyImpl getRoleHierarchy() {
new RoleHierarchyImpl().tap {
hierarchy = "$Role.ADMIN > $Role.AUTHOR > $Role.USER"
}
}
@Bean
GrantedAuthoritiesMapper getUserAuthoritiesMapper() {
new RoleHierarchyAuthoritiesMapper(roleHierarchy)
}
SecurityExpressionHandler<FilterInvocation> expressionHandler() {
// Removes the prefix
new DefaultWebSecurityExpressionHandler().tap {
roleHierarchy = roleHierarchy
defaultRolePrefix = null
}
}
// ...
@Bean
@Scope(scopeName = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
AccessToken accessToken() {
def request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest()
def authToken = (KeycloakAuthenticationToken) request.userPrincipal
def securityContext = (KeycloakSecurityContext) authToken.credentials
return securityContext.token
}
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http)
http
.authorizeRequests()
.expressionHandler(expressionHandler())
// ...
}
}

全局方法安全配置

我需要明确允许allow-bean-definition-overriding,因为否则我会收到一个bean with that name already defined错误,这表明我完全失去了对整个情况的控制,不知道发生了什么。

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
class GlobalMethodSecurityConfig extends GlobalMethodSecurityConfiguration {
@Autowired
RoleHierarchy roleHierarchy
@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
((DefaultMethodSecurityExpressionHandler)super.createExpressionHandler()).tap {
roleHierarchy = roleHierarchy
defaultRolePrefix = null
}
}
}

是否有任何可能很重要的进一步配置?非常感谢您的帮助!

正如 M. Deinum 指出的那样,必须用BeanPostProcessor删除多个位置的defaultRolePrefix,这在 (docs.spring.io) 禁用ROLE_前缀中进行了解释。

这种方法对我来说似乎不是很干净,所以我编写了一个自定义AuthoritiesMapper来实现从Keycloak映射分层角色,而无需将它们重命名为ROLE_Spring标准。首先,修改了Roles枚举以符合应用程序范围内的标准:

enum Role {
USER('ROLE_USER'),
AUTHOR('ROLE_AUTHOR'),
ADMIN('ROLE_ADMIN')
// ...
}

其次,我用前缀分层实现替换了RoleHierarchyAuthoritiesMapper

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
// ..
// Replaces the RoleHierarchyAuthoritiesMapper
@Bean
GrantedAuthoritiesMapper getUserAuthoritiesMapper() {
new PrefixingRoleHierarchyAuthoritiesMapper(roleHierarchy)
}
}
class PrefixingRoleHierarchyAuthoritiesMapper extends RoleHierarchyAuthoritiesMapper {
String prefix = 'ROLE_'
PrefixingRoleHierarchyAuthoritiesMapper(RoleHierarchy roleHierarchy) {
super(roleHierarchy)
}
@Override
Collection<? extends GrantedAuthority> mapAuthorities(Collection<? extends GrantedAuthority> authorities) {
def prefixedAuthorities = authorities.collect { GrantedAuthority originalAuthority ->
new GrantedAuthority() {
String authority = "${prefix}${originalAuthority.authority}".toUpperCase()
}
}
super.mapAuthorities(prefixedAuthorities)
}
}

最后,我摆脱了GlobalMethodSecurityConfig.

除了 (docs.spring.io) 禁用ROLE_前缀中提供的建议和 M. Deinum 提供的建议外,在使用 KeycloakWebSecurityConfigurerAdapter 时还需要进行一次修改。

在 configureGlobal 方法中,grantedAuthorityMapper bean 在 bean keycloakAuthenticationProvider 中设置。在 grantedAuthorityMapper 中,前缀可以设置为您想要的任何内容,其中默认值为"ROLE_"。

代码如下:

@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
SimpleAuthorityMapper grantedAuthoritiesMapper = new SimpleAuthorityMapper();
grantedAuthoritiesMapper.setPrefix("");
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(grantedAuthoritiesMapper);
auth.authenticationProvider(keycloakAuthenticationProvider);
}

这个解决方案对我有用。

最新更新