我需要在带有远程授权服务器的spring boot oauth 2.0资源服务器中实现动态弹簧安全匹配器(URL,方法,角色,权限等)验证。
是否有关于如何操作的提示,将这些信息提取到数据库?
到目前为止,我已经找到了一个关于如何继续进行的小指示,创建了一个扩展 DefaultFilterInvocationSecurityMetadataSource 并覆盖方法 getAttributes(Object object)的过滤器,但我没有&#39 ;不知道这是否是最好的方法。 https://github.com/sohamghosh/spring-security-dynamic-roles
答案 0 :(得分:2)
我使用我在问题中发布的链接所指出的想法来完成我的解决方案。如果有人能给我提示,我会很感激。
首先,我只想在我的资源服务器上与用户关联权限,因为我的授权服务器没有一切,现在不需要我的应用程序业务规则,角色和权限。此外,我想将这些 Authorities 存储在数据库中,以便我可以动态管理它。所以我创建了一个自定义的 accessTokenConverter 来链接到我的 RemoteTokenServices (指向我的Oauth 2.0 授权服务器),它覆盖了方法 extractAuthentication DefaultAccessTokenConverter 添加这些新的权限。请参阅下面的完整课程配置。
/**
* Class responsible for configuring Resource Server's security filters and connectivity with Heimdall (Authorization Server)
* @author mariane.vieira
*
*/
@Configuration
@EnableResourceServer
public class OAuth2ResourceConfig extends ResourceServerConfigurerAdapter {
@Value("${auth.server.resourceId}")
private String resourceId;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private DynamicAuthorityRepository authorityRepository;
@Autowired
private UrlInterceptorRepository urlInterceptorRepository;
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(resourceId).stateless(false);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http
// Since we want the protected resources to be accessible in the UI as well we need
// session creation to be allowed (it's disabled by default in 2.0.6)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.addFilter(filterSecurityInterceptor())
//permitting all because security paths verifications are going to be dynamic
//because of this filter above
.authorizeRequests().antMatchers("/**").permitAll();
}
/**
* Dynamic {@link AccessTokenConverter}, normaly extracts authentication just like
* {@link DefaultAccessTokenConverter} but fetches others authorities localy stored
* with {@link DynamicAuthorityRepository} by username.
*
* @author mariane.vieira
*
*/
public class DynamicAccessTokenConverter extends DefaultAccessTokenConverter {
@Override
public OAuth2Authentication extractAuthentication(Map<String, ?> map) {
OAuth2Authentication authentication = super.extractAuthentication(map);
List<DynamicAuthority> dynamicAuthorities = authorityRepository.findByUsername(String.valueOf(authentication.getPrincipal()));
List<GrantedAuthority> authorities = authentication.getAuthorities().stream().collect(Collectors.toList());
authorities.addAll(dynamicAuthorities.stream().map(auth -> new SimpleGrantedAuthority(auth.getAuthority()))
.collect(Collectors.toList()));
OAuth2Request request = new OAuth2Request(authentication.getOAuth2Request().getRequestParameters(),
authentication.getOAuth2Request().getClientId(), authorities, true,
authentication.getOAuth2Request().getScope(), authentication.getOAuth2Request().getResourceIds(),
null, null, null);
return new OAuth2Authentication(request,
new UsernamePasswordAuthenticationToken(authentication.getPrincipal(), "N/A", authorities));
}
}
/**
* Instantiates Bean accessTokenConverter as an instance of {@link DynamicAccessTokenConverter}
* @return {@link DynamicAccessTokenConverter}
*/
@Bean
public AccessTokenConverter accessTokenConverter() {
return new DynamicAccessTokenConverter();
}
/**
* Instantiates Bean remoteTokenServices with informations of Heimdall (Authorization Server) instance and
* client credentials. Besides, sets our custom accessTokenConverter to fetch authorities dynamically.
* @param checkTokenUrl Url to Heimdall's (Authorization Server) check token endpoint
* @param clientId Client Id registered in Heimdall (Authorization Server)
* @param clientSecret Client Secret registered in Heimdall (Authorization Server)
* @return {@link RemoteTokenServices} bean
*/
@Bean
public RemoteTokenServices remoteTokenServices(final @Value("${auth.server.url}") String checkTokenUrl,
final @Value("${auth.server.clientId}") String clientId,
final @Value("${auth.server.clientsecret}") String clientSecret) {
final RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
remoteTokenServices.setCheckTokenEndpointUrl(checkTokenUrl);
remoteTokenServices.setClientId(clientId);
remoteTokenServices.setClientSecret(clientSecret);
remoteTokenServices.setAccessTokenConverter(accessTokenConverter());
return remoteTokenServices;
}
/**
* Instantiates Bean remoteTokenServices filterSecurityInterceptor, instance of {@link DynamicFilterInvocationSecurityMetadataSource}
* that intercepts every request to verify security rules. These rules are stored in database and can be formed and verified
* dynamically.
* @return {@link FilterSecurityInterceptor} Bean, instance of {@link DynamicFilterInvocationSecurityMetadataSource}
*/
public FilterSecurityInterceptor filterSecurityInterceptor(){
DynamicFilterInvocationSecurityMetadataSource dynamicFilter = new DynamicFilterInvocationSecurityMetadataSource(
new LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>>());
dynamicFilter.setUrlInterceptorRepository(urlInterceptorRepository);
FilterSecurityInterceptor filter = new FilterSecurityInterceptor();
filter.setAuthenticationManager(authenticationManager);
filter.setAccessDecisionManager(accessDecisionManager());
filter.setSecurityMetadataSource(dynamicFilter);
return filter;
}
/**
* Instantiates Bean accessDecisionManager, instance of {@link UnanimousBased} with {@link ScopeVoter}, {@link RoleVoter}
* and {@link AuthenticatedVoter}.
* @return {@link AccessDecisionManager} bean, instance of {@link UnanimousBased}
*/
@Bean
public AccessDecisionManager accessDecisionManager(){
return new UnanimousBased(Arrays.asList(new ScopeVoter(), new RoleVoter(), new AuthenticatedVoter()));
}
}
此外,如上所述,我还想动态验证安全路径访问并将其存储在数据库中。我添加了一个 FilterSecurityInterceptor ,其中包含了我个性化的安全元数据源。它由上面的配置类实例化。下面的类是安全元数据源,适用于在数据库中搜索路径及其 ConfigAttributes 。
public class DynamicFilterInvocationSecurityMetadataSource extends DefaultFilterInvocationSecurityMetadataSource {
private UrlInterceptorRepository urlInterceptorRepository;
public DynamicFilterInvocationSecurityMetadataSource(LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap) {
super(requestMap);
}
public UrlInterceptorRepository getUrlInterceptorRepository() {
return urlInterceptorRepository;
}
public void setUrlInterceptorRepository(UrlInterceptorRepository urlInterceptorRepository) {
this.urlInterceptorRepository = urlInterceptorRepository;
}
/**
* {@link ConfigAttribute} with specific attribute (access rule).
* Possible values to getAttribute's return:
* - IS_AUTHENTICATED_ANONYMOUSLY - No token in the request
* - IS_AUTHENTICATED_REMEMBERED
* - IS_AUTHENTICATED_FULLY - With a valid token
* - SCOPE_<scope> - Token with a specific scope
* - ROLE_<role> - Token's user with specific role
* @author mariane.vieira
*
*/
public class DynamicConfigAttribute implements ConfigAttribute {
private static final long serialVersionUID = 1201502296417220314L;
private String attribute;
public DynamicConfigAttribute(String attribute) {
this.attribute = attribute;
}
@Override
public String getAttribute() {
/* Possible values to getAttribute's return:
* IS_AUTHENTICATED_ANONYMOUSLY, IS_AUTHENTICATED_REMEMBERED
* IS_AUTHENTICATED_FULLY, SCOPE_<scope>, ROLE_<role>
*/
return this.attribute;
}
@Override
public String toString() {
return this.attribute;
}
}
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
FilterInvocation fi = (FilterInvocation) object;
String url = fi.getRequestUrl();
String httpMethod = fi.getRequest().getMethod();
if (url != null) {
//Searches for interceptors whose patterns matches the URL
List<UrlInterceptor> interceptors = this.urlInterceptorRepository.findByUrl(url);
Collection<ConfigAttribute> configAttributes = interceptors.stream()
//If the httpMethod is null is because it is valid for all methods
.filter(in -> in.getHttpMethod() == null || in.getHttpMethod().equals(httpMethod))
.map(in -> new DynamicConfigAttribute(in.getAccess()))
.collect(Collectors.toList());
return configAttributes;
}
return null;
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> clazz) {
return FilterInvocation.class.isAssignableFrom(clazz);
}
}