这段时间公司要开发几个生产业务系统的管理模块,用Keycloak的LDAP做用户体系
使用OAuth2及jwt方式来完成用户认证,不过认证部分非常简单,只需要获取jwt就可以了,详见:
当完成登录后携带相关的jwt访问接口时你会发现,任何接口security都会返回403异常,这是因为用户虽然完成了认证,但是没有相关接口的权限,即便你使用http.authorizeRequests().anyRequest().hasAnyRole("xxx");
指明了只需要某角色就可以访问某接口,还是没有用,这是因为Keycloak官方的默认Provider不具备使用应用角色上下文的功能。
安装配置Keycloak和引入SpringSecurity这里不再赘述,只放出关键代码
默认的Security Keycloak:
@KeycloakConfiguration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Import({KeycloakSpringBootConfigResolver.class})
public class KeycloakAdapterConfig extends KeycloakWebSecurityConfigurerAdapter {
...
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
...
}
这一部分注册的是Keycloak官方的默认Provider,关键源码:
#org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider.java
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
KeycloakAuthenticationToken token = (KeycloakAuthenticationToken) authentication;
List<GrantedAuthority> grantedAuthorities = new ArrayList<GrantedAuthority>();
for (String role : token.getAccount().getRoles()) {
grantedAuthorities.add(new KeycloakRole(role));
}
return new KeycloakAuthenticationToken(token.getAccount(), token.isInteractive(), mapAuthorities(grantedAuthorities));
}
阅读源码和断点可以发现,token.getAccount().getRoles()
最终会使用登录后的jwt中的realm_access
作为用户上下文的角色列表,如果在业务管理模块中需要细分resource应用的角色的话,这种方式是不完善的。
所以我们需要自定义一个Provider,来完成Keycloak用户认证后的上下文的应用角色注入:
import org.keycloak.adapters.springboot.KeycloakSpringBootProperties;
import org.keycloak.adapters.springsecurity.account.KeycloakRole;
import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;
import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class CustomKeycloakAuthenticationProvider extends KeycloakAuthenticationProvider {
private GrantedAuthoritiesMapper grantedAuthoritiesMapper;
KeycloakSpringBootProperties keycloakSpringBootProperties;
public void setGrantedAuthoritiesMapper(GrantedAuthoritiesMapper grantedAuthoritiesMapper) {
this.grantedAuthoritiesMapper = grantedAuthoritiesMapper;
}
public void setKeycloakSpringBootProperties(KeycloakSpringBootProperties keycloakSpringBootProperties){
this.keycloakSpringBootProperties=keycloakSpringBootProperties;
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
KeycloakAuthenticationToken token = (KeycloakAuthenticationToken) authentication;
List<GrantedAuthority> grantedAuthorities = new ArrayList<GrantedAuthority>();
for (String role : token.getAccount().getRoles()) {
grantedAuthorities.add(new KeycloakRole(role));
}
//在下面注入指定应用的角色列表,当然也可以把上面的realm角色部分注释掉
for (String role : token.getAccount().getKeycloakSecurityContext().getToken()
.getResourceAccess(keycloakSpringBootProperties.getResource()).getRoles()) {
grantedAuthorities.add(new KeycloakRole(role));
}
return new KeycloakAuthenticationToken(token.getAccount(), token.isInteractive(), mapAuthorities(grantedAuthorities));
}
private Collection<? extends GrantedAuthority> mapAuthorities(Collection<? extends GrantedAuthority> authorities) {
return grantedAuthoritiesMapper != null
? grantedAuthoritiesMapper.mapAuthorities(authorities)
: authorities;
}
@Override
public boolean supports(Class<?> aClass) {
return KeycloakAuthenticationToken.class.isAssignableFrom(aClass);
}
}
上面这段代码就是我们的自定义Provider了,其中新加了一个keycloakSpringBootProperties
属性,是为了使用在配置文件中配置的keycloak.resource
作为我们的需要注入角色列表的应用,这一块可以根据需要酌情修改
然后修改KeycloakAdapterConfig
:
@KeycloakConfiguration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Import({KeycloakSpringBootConfigResolver.class})
public class KeycloakAdapterConfig extends KeycloakWebSecurityConfigurerAdapter {
...
@Resource
KeycloakSpringBootProperties keycloakSpringBootProperties;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
CustomKeycloakAuthenticationProvider customKeycloakAuthenticationProvider=new CustomKeycloakAuthenticationProvider();
customKeycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
customKeycloakAuthenticationProvider.setKeycloakSpringBootProperties(keycloakSpringBootProperties);
auth.authenticationProvider(customKeycloakAuthenticationProvider);
}
...
}
即可
这个时候当使用HttpSecurity
或者@PreAuthorize("hasRole('xxx')")
配置需要指定角色的接口时,就不会再出现403异常~