我知道这有点老了,但我只需要解决这个问题,在较新的堆栈中找不到任何东西。
我们有多个环境共享相同的 CAS 服务(想想 dev、qa、uat 和本地开发环境);我们能够从多个 url 访问每个环境(通过反向代理通过客户端 Web 服务器直接访问后端服务器本身)。这意味着指定单个 url 充其量是困难的。也许有一种方法可以做到这一点,但能够使用动态ServiceProperties.getService()
. 我可能会添加某种服务器后缀检查,以确保 url 在某些时候不会被劫持。
这是我为使基本 CAS 流程正常工作而所做的工作,而不管用于访问安全资源的 URL 是什么...
- 覆盖
CasAuthenticationFilter
.
- 覆盖
CasAuthenticationProvider
.
setAuthenticateAllArtifacts(true)
上ServiceProperties
。
这是我的 spring 配置 bean 的长格式:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true)
public class CasSecurityConfiguration extends WebSecurityConfigurerAdapter {
只是通常的弹簧配置bean。
@Value("${cas.server.url:https://localhost:9443/cas}")
private String casServerUrl;
@Value("${cas.service.validation.uri:/webapi/j_spring_cas_security_check}")
private String casValidationUri;
@Value("${cas.provider.key:whatever_your_key}")
private String casProviderKey;
一些外部化的配置参数。
@Bean
public ServiceProperties serviceProperties() {
ServiceProperties serviceProperties = new ServiceProperties();
serviceProperties.setService(casValidationUri);
serviceProperties.setSendRenew(false);
serviceProperties.setAuthenticateAllArtifacts(true);
return serviceProperties;
}
上面的关键是setAuthenticateAllArtifacts(true)
调用。这将使服务票证验证器使用AuthenticationDetailsSource
实现而不是硬编码ServiceProperties.getService()
调用
@Bean
public Cas20ServiceTicketValidator cas20ServiceTicketValidator() {
return new Cas20ServiceTicketValidator(casServerUrl);
}
标准票验证器..
@Resource
private UserDetailsService userDetailsService;
@Bean
public AuthenticationUserDetailsService authenticationUserDetailsService() {
return new AuthenticationUserDetailsService() {
@Override
public UserDetails loadUserDetails(Authentication token) throws UsernameNotFoundException {
String username = (token.getPrincipal() == null) ? "NONE_PROVIDED" : token.getName();
return userDetailsService.loadUserByUsername(username);
}
};
}
现有 UserDetailsService 的标准挂钩
@Bean
public CasAuthenticationProvider casAuthenticationProvider() {
CasAuthenticationProvider casAuthenticationProvider = new CasAuthenticationProvider();
casAuthenticationProvider.setAuthenticationUserDetailsService(authenticationUserDetailsService());
casAuthenticationProvider.setServiceProperties(serviceProperties());
casAuthenticationProvider.setTicketValidator(cas20ServiceTicketValidator());
casAuthenticationProvider.setKey(casProviderKey);
return casAuthenticationProvider;
}
标准身份验证提供程序
@Bean
public CasAuthenticationFilter casAuthenticationFilter() throws Exception {
CasAuthenticationFilter casAuthenticationFilter = new CasAuthenticationFilter();
casAuthenticationFilter.setAuthenticationManager(authenticationManager());
casAuthenticationFilter.setServiceProperties(serviceProperties());
casAuthenticationFilter.setAuthenticationDetailsSource(dynamicServiceResolver());
return casAuthenticationFilter;
}
这里的关键是dynamicServiceResolver()
设置..
@Bean
AuthenticationDetailsSource<HttpServletRequest,
ServiceAuthenticationDetails> dynamicServiceResolver() {
return new AuthenticationDetailsSource<HttpServletRequest, ServiceAuthenticationDetails>() {
@Override
public ServiceAuthenticationDetails buildDetails(HttpServletRequest context) {
final String url = makeDynamicUrlFromRequest(serviceProperties());
return new ServiceAuthenticationDetails() {
@Override
public String getServiceUrl() {
return url;
}
};
}
};
}
makeDynamicUrlFromRequest()
从该方法动态创建服务 url 。该位在票证验证时使用。
@Bean
public CasAuthenticationEntryPoint casAuthenticationEntryPoint() {
CasAuthenticationEntryPoint casAuthenticationEntryPoint = new CasAuthenticationEntryPoint() {
@Override
protected String createServiceUrl(final HttpServletRequest request, final HttpServletResponse response) {
return CommonUtils.constructServiceUrl(null, response, makeDynamicUrlFromRequest(serviceProperties())
, null, serviceProperties().getArtifactParameter(), false);
}
};
casAuthenticationEntryPoint.setLoginUrl(casServerUrl + "/login");
casAuthenticationEntryPoint.setServiceProperties(serviceProperties());
return casAuthenticationEntryPoint;
}
当 CAS 想要重定向到登录屏幕时,这部分使用相同的动态 url 创建器。
private String makeDynamicUrlFromRequest(ServiceProperties serviceProperties){
return "https://howeverYouBuildYourOwnDynamicUrl.com";
}
这就是你的想法。我只传入了 ServiceProperties 来保存我们配置的服务的 URI。我们在背面使用 HATEAOS 并有一个类似的实现:
return UriComponentsBuilder.fromHttpUrl(
linkTo(methodOn(ExposedRestResource.class)
.aMethodOnThatResource(null)).withSelfRel().getHref())
.replacePath(serviceProperties.getService())
.build(false)
.toUriString();
编辑:这是我为有效服务器后缀列表所做的。
private List<String> validCasServerHostEndings;
@Value("${cas.valid.server.suffixes:company.com,localhost}")
private void setValidCasServerHostEndings(String endings){
validCasServerHostEndings = new ArrayList<>();
for (String ending : StringUtils.split(endings, ",")) {
if (StringUtils.isNotBlank(ending)){
validCasServerHostEndings.add(StringUtils.trim(ending));
}
}
}
private String makeDynamicUrlFromRequest(ServiceProperties serviceProperties){
UriComponents url = UriComponentsBuilder.fromHttpUrl(
linkTo(methodOn(ExposedRestResource.class)
.aMethodOnThatResource(null)).withSelfRel().getHref())
.replacePath(serviceProperties.getService())
.build(false);
boolean valid = false;
for (String validCasServerHostEnding : validCasServerHostEndings) {
if (url.getHost().endsWith(validCasServerHostEnding)){
valid = true;
break;
}
}
if (!valid){
throw new AccessDeniedException("The server is unable to authenticate the requested url.");
}
return url.toString();
}