У меня проблемы с использованием аннотаций до / после авторизации Spring Security и Servlet API с интеграцией Keycloak. Я исследовал множество статей, учебных пособий и следующих вопросов без дальнейшей удачи :
Все, что мне нужно, это удалить префикс 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
не работает, потому что этот метод сервлета не успешен.
В Keycloak и Spring определены только три роли - администратор, автор, пользователь:
enum Role {
USER('user'),
AUTHOR('author'),
ADMIN('admin')
final String id
Role(String id) {
this.id = id
}
@Override
String toString() {
id
}
}
Конфигурация Keycloak
При удалении аннотации @EnableGlobalMethodSecurity
из этой веб-безопасности выявляется Error creating bean with name 'resourceHandlerMapping'
, вызванная ошибкой No ServletContext set
- без понятия, откуда она взялась!
@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
}
}
}
Какие еще конфигурации могут быть важны? Большое спасибо за вашу помощь!