And*_*ite 8 java spring cas spring-security
使用Spring Security + CAS时,我会一直使用发送到CAS的回调URL(即服务属性)来触及一个小路障.我已经看了一堆的例子,如这和此,但它们都使用硬编码网址(甚至Spring的CAS文档).一个典型的剪辑看起来像这样......
<bean id="serviceProperties" class="org.springframework.security.ui.cas.ServiceProperties">
<property name="service" value="http://localhost:8080/click/j_spring_cas_security_check" />
</bean>
Run Code Online (Sandbox Code Playgroud)
首先,我不想硬编码服务器名称或端口,因为我希望这个WAR可以在任何地方部署,我不希望我的应用程序在编译时绑定到特定的DNS条目.其次,我不明白为什么Spring无法自动检测我的应用程序的上下文和请求的URL来自动构建URL.该声明的第一部分仍然有效,但As Raghuram通过此链接指出,出于安全原因,我们无法信任来自客户端的HTTP Host Header.
理想情况下,我希望服务URL完全符合用户的要求(只要请求有效,例如mycompany.com的子域),所以它是无缝的,或者至少我只想指定一些相对于我的路径应用程序上下文root并让Spring动态确定服务URL.像下面这样的东西......
<bean id="serviceProperties" class="org.springframework.security.ui.cas.ServiceProperties">
<property name="service" value="/my_cas_callback" />
</bean>
Run Code Online (Sandbox Code Playgroud)
要么...
<bean id="serviceProperties" class="org.springframework.security.ui.cas.ServiceProperties">
<property name="service" value="${container.and.app.derived.value.here}" />
</bean>
Run Code Online (Sandbox Code Playgroud)
这有可能或容易,还是我错过了明显的?
我知道这有点旧,但我只是必须解决这个问题,并且在新的堆栈中找不到任何东西.
我们有多个环境共享相同的CAS服务(想想dev,qa,uat和本地开发环境); 我们能够从多个URL(通过客户端Web服务器通过反向代理并直接到后端服务器本身)访问每个环境.这意味着指定单个URL充其量是困难的.也许有办法做到这一点,但能够使用动态ServiceProperties.getService().我可能会添加某种服务器后缀检查,以确保url在某些时候没有被劫持.
无论用于访问安全资源的URL如何,我都是为了使基本CAS流程正常工作而做的...
CasAuthenticationFilter.CasAuthenticationProvider.setAuthenticateAllArtifacts(true)在...上ServiceProperties.这是我的spring配置bean的长形式:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true)
public class CasSecurityConfiguration extends WebSecurityConfigurerAdapter {
Run Code Online (Sandbox Code Playgroud)
只是通常的spring配置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;
Run Code Online (Sandbox Code Playgroud)
一些外化配置参数.
@Bean
public ServiceProperties serviceProperties() {
ServiceProperties serviceProperties = new ServiceProperties();
serviceProperties.setService(casValidationUri);
serviceProperties.setSendRenew(false);
serviceProperties.setAuthenticateAllArtifacts(true);
return serviceProperties;
}
Run Code Online (Sandbox Code Playgroud)
上面的关键是setAuthenticateAllArtifacts(true)电话.这将使服务票证验证器使用AuthenticationDetailsSource实现而不是硬编码ServiceProperties.getService()调用
@Bean
public Cas20ServiceTicketValidator cas20ServiceTicketValidator() {
return new Cas20ServiceTicketValidator(casServerUrl);
}
Run Code Online (Sandbox Code Playgroud)
标准机票验证器..
@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);
}
};
}
Run Code Online (Sandbox Code Playgroud)
现有UserDetailsService的标准挂钩
@Bean
public CasAuthenticationProvider casAuthenticationProvider() {
CasAuthenticationProvider casAuthenticationProvider = new CasAuthenticationProvider();
casAuthenticationProvider.setAuthenticationUserDetailsService(authenticationUserDetailsService());
casAuthenticationProvider.setServiceProperties(serviceProperties());
casAuthenticationProvider.setTicketValidator(cas20ServiceTicketValidator());
casAuthenticationProvider.setKey(casProviderKey);
return casAuthenticationProvider;
}
Run Code Online (Sandbox Code Playgroud)
标准认证提供商
@Bean
public CasAuthenticationFilter casAuthenticationFilter() throws Exception {
CasAuthenticationFilter casAuthenticationFilter = new CasAuthenticationFilter();
casAuthenticationFilter.setAuthenticationManager(authenticationManager());
casAuthenticationFilter.setServiceProperties(serviceProperties());
casAuthenticationFilter.setAuthenticationDetailsSource(dynamicServiceResolver());
return casAuthenticationFilter;
}
Run Code Online (Sandbox Code Playgroud)
关键是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;
}
};
}
};
}
Run Code Online (Sandbox Code Playgroud)
从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;
}
Run Code Online (Sandbox Code Playgroud)
当CAS想要重定向到登录屏幕时,此部分使用相同的动态URL创建者.
private String makeDynamicUrlFromRequest(ServiceProperties serviceProperties){
return "https://howeverYouBuildYourOwnDynamicUrl.com";
}
Run Code Online (Sandbox Code Playgroud)
这就是你所做的一切.我只传入ServiceProperties来保存我们配置的服务的URI.我们在后端使用HATEAOS并具有如下实现:
return UriComponentsBuilder.fromHttpUrl(
linkTo(methodOn(ExposedRestResource.class)
.aMethodOnThatResource(null)).withSelfRel().getHref())
.replacePath(serviceProperties.getService())
.build(false)
.toUriString();
Run Code Online (Sandbox Code Playgroud)
编辑:这是我为有效的服务器后缀列表所做的.
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();
}
Run Code Online (Sandbox Code Playgroud)
在 Spring 2.6.5 spring 中,您可以扩展 org.springframework.security.ui.cas.ServiceProperties
在 spring 3 中,该方法是最终方法,您可以通过子类化 CasAuthenticationProvider 和 CasEntryPoint 来解决此问题,然后使用您自己的 ServiceProperties 版本并使用更动态的实现覆盖 getService() 方法。
您可以使用主机标头来计算所需的域,并通过验证仅使用您控制下的域/子域来使其更加安全。然后附加一些可配置的值。
当然,您将面临实施不安全的风险……所以要小心。
它最终可能看起来像:
<bean id="serviceProperties" class="my.ServiceProperties">
<property name="serviceRelativeUrl" value="/my_cas_callback" />
<property name="validDomainPattern" value="*.mydomain.com" />
</bean>
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
11647 次 |
| 最近记录: |