我正在尝试将基本应用程序从仅使用 Spring Security 更改为使用 CAS,以启用 SSO。但我在某个地方遇到了重定向循环,并且我无法找出问题所在。我还制作了另外两个模拟应用程序,在 CAS 上没有问题,因为它们正在工作。我使用 Java 配置代替 XML 配置,取自here https://objectpartners.com/2014/05/20/configuring-spring-security-cas-providers-with-java-config/。我已经尝试过该示例的 XML 配置,但仍然得到相同的结果。
我的猜测是authenticationManager有问题,无法从Spring Security检测到用户。该日志至少指示 AnonymousUser 并引发 AccessDeniedException。但它适用于其他两个具有类似配置的模拟应用程序(我什至尝试复制它,但错误仍然发生)。几天来我一直在尝试解决这个问题,但没有成功,因此非常感谢您的帮助。我正在使用 Tomcat 8、Spring 4.2 和 Ja-sig CAS 4.0.0,全部运行在 Windows 8 上。
我的网络安全配置:
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private DataSource dataSource;
@Autowired
@Resource(name="CASuserDetailsService")
private AuthenticationUserDetailsService userDetailsService;
@Bean
public ServiceProperties serviceProperties() {
ServiceProperties serviceProperties = new ServiceProperties();
serviceProperties.setService("https://localhost:8443/i9t-YM/j_spring_cas_security_check");
serviceProperties.setSendRenew(false);
return serviceProperties;
}
@Bean
public CasAuthenticationProvider casAuthenticationProvider() {
CasAuthenticationProvider casAuthenticationProvider = new CasAuthenticationProvider();
casAuthenticationProvider.setAuthenticationUserDetailsService(authenticationUserDetailsService());
casAuthenticationProvider.setServiceProperties(serviceProperties());
casAuthenticationProvider.setTicketValidator(cas20ServiceTicketValidator());
casAuthenticationProvider.setKey("some_id_for_this_cas_prov");
return casAuthenticationProvider;
}
@Bean
public AuthenticationUserDetailsService authenticationUserDetailsService() {
return userDetailsService;
}
@Bean
public Cas20ServiceTicketValidator cas20ServiceTicketValidator() {
return new Cas20ServiceTicketValidator("https://localhost:8443/cas");
}
@Bean
public CasAuthenticationFilter casAuthenticationFilter() throws Exception {
CasAuthenticationFilter casAuthenticationFilter = new CasAuthenticationFilter();
casAuthenticationFilter.setAuthenticationManager(authenticationManager());
casAuthenticationFilter.setFilterProcessesUrl("https://localhost:8443/i9t-YM/j_spring_cas_security_check");
return casAuthenticationFilter;
}
@Bean
public CasAuthenticationEntryPoint casAuthenticationEntryPoint() {
CasAuthenticationEntryPoint casAuthenticationEntryPoint = new CasAuthenticationEntryPoint();
casAuthenticationEntryPoint.setLoginUrl("https://localhost:8443/cas/login");
casAuthenticationEntryPoint.setServiceProperties(serviceProperties());
return casAuthenticationEntryPoint;
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(casAuthenticationProvider());
//Yes, joe is with a md5 poassword on the database, but i'm using it here as a mockup to see if it works. Also, if there's no ".password", it'll throw an ConstructorCantBeNull or something like that.
auth.inMemoryAuthentication().withUser("joe").password("joe").roles("USER");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilter(casAuthenticationFilter());
http.exceptionHandling().authenticationEntryPoint(casAuthenticationEntryPoint());
http.authorizeRequests().antMatchers("/**").access("hasRole('ROLE_USER')");
// http.authorizeRequests().antMatchers("/resource", "/secure/**").access("hasRole('ROLE_USER')");
// http.authorizeRequests().antMatchers("/resources/**").permitAll();
// http.httpBasic().and().authorizeRequests().antMatchers("/index.html", "/home.html", "/login.html", "/")
// .permitAll().anyRequest().authenticated()
// //.and().addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class)
// //.csrf().csrfTokenRepository(csrfTokenRepository())
// ;
}
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
}
TestCasAuthenticationUserDetailsService(请注意,我尝试通过多种方式使其工作......)
@Service("CASuserDetailsService")
public class TestCasAuthenticationUserDetailsService implements AuthenticationUserDetailsService, UserDetailsService {
@Autowired
private DataSource dataSource;
@Override
public UserDetails loadUserDetails(Authentication token) throws UsernameNotFoundException {
List<GrantedAuthority> authorities = new ArrayList<>();
System.out.println(token.getName());
System.out.println(token.toString());
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
return new User(token.getName(), token.getName(), authorities);
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
List<GrantedAuthority> authorities = new ArrayList<>();
System.out.println(username);
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
return new User(username, username, authorities);
}
}
我的 web.xml:
<servlet>
<servlet-name>springServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/spring-context.xml
<!-- /WEB-INF/spring-security.xml -->
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
<error-page>
<exception-type>java.lang.Exception</exception-type>
<location>/erro.html</location>
</error-page>
<error-page>
<error-code>404</error-code>
<location>/404.html</location>
</error-page>
<!-- CAS -->
<filter>
<filter-name>CAS-SSO-Filter</filter-name>
<filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CAS-SSO-Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
</listener>
<!-- Force SSL -->
<security-constraint>
<web-resource-collection>
<web-resource-name>i9t-YM</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
模拟应用程序中的 applicationContext-security.xml:
<security:http auto-config="true" entry-point-ref="casEntryPoint">
<security:intercept-url pattern="/*" access="ROLE_USER" />
<security:custom-filter position="CAS_FILTER"
ref="casFilter" />
</security:http>
<security:user-service id="userService">
<security:user name="joe" authorities="ROLE_USER" />
</security:user-service>
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider
ref="casAuthenticationProvider" />
</security:authentication-manager>
<bean id="serviceProperties" class="org.springframework.security.cas.ServiceProperties">
<property name="service"
value="https://localhost:8443/casTest/j_spring_cas_security_check" />
<property name="sendRenew" value="false" />
</bean>
<bean id="casFilter"
class="org.springframework.security.cas.web.CasAuthenticationFilter">
<property name="authenticationManager" ref="authenticationManager" />
</bean>
<bean id="casEntryPoint"
class="org.springframework.security.cas.web.CasAuthenticationEntryPoint">
<property name="loginUrl" value="https://localhost:8443/cas/login" />
<property name="serviceProperties" ref="serviceProperties" />
</bean>
<bean id="casAuthenticationProvider"
class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
<property name="authenticationUserDetailsService">
<bean
class=" org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
<constructor-arg ref="userService" />
</bean>
</property>
<property name="serviceProperties" ref="serviceProperties" />
<property name="ticketValidator">
<bean class="org.jasig.cas.client.validation.Cas20ServiceTicketValidator">
<constructor-arg index="0" value="https://localhost:8443/cas" />
<!-- <property name="proxyGrantingTicketStorage" ref="proxyGrantingTicketStorage" /> -->
<!-- <property name="proxyCallbackUrl" value="https://localhost:8443/cas/secure/receptor" /> -->
</bean>
</property>
<property name="key" value="some_id_for_this_cas_prov" />
</bean>
<bean id="proxyGrantingTicketStorage" class="org.jasig.cas.client.proxy.ProxyGrantingTicketStorageImpl" />
SSL 对他们来说也工作得很好。
存在循环时记录的错误:
2015-08-27 11:29:59,026 INFO [org.jasig.cas.CentralAuthenticationServiceImpl] - <Granted service ticket [ST-10-yA1U32bOGJ6Gg5HShohm-cas01.example.org] for service [https://localhost:8443/i9t-YM/j_spring_cas_security_check] for user [joe]>
2015-08-27 11:29:59,027 INFO [com.github.inspektr.audit.support.Slf4jLoggingAuditTrailManager] - <Audit trail record BEGIN
=============================================================
WHO: joe
WHAT: ST-10-yA1U32bOGJ6Gg5HShohm-cas01.example.org for https://localhost:8443/i9t-YM/j_spring_cas_security_check
ACTION: SERVICE_TICKET_CREATED
APPLICATION: CAS
WHEN: Thu Aug 27 11:29:59 BRT 2015
CLIENT IP ADDRESS: 0:0:0:0:0:0:0:1
SERVER IP ADDRESS: 0:0:0:0:0:0:0:1
=============================================================
>
/j_spring_cas_security_check?ticket=ST-10-yA1U32bOGJ6Gg5HShohm-cas01.example.org at position 1 of 12 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
/j_spring_cas_security_check?ticket=ST-10-yA1U32bOGJ6Gg5HShohm-cas01.example.org at position 2 of 12 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
HttpSession returned null object for SPRING_SECURITY_CONTEXT
No SecurityContext was available from the HttpSession: org.apache.catalina.session.StandardSessionFacade@1c993da0. A new one will be created.
/j_spring_cas_security_check?ticket=ST-10-yA1U32bOGJ6Gg5HShohm-cas01.example.org at position 3 of 12 in additional filter chain; firing Filter: 'HeaderWriterFilter'
/j_spring_cas_security_check?ticket=ST-10-yA1U32bOGJ6Gg5HShohm-cas01.example.org at position 4 of 12 in additional filter chain; firing Filter: 'CsrfFilter'
/j_spring_cas_security_check?ticket=ST-10-yA1U32bOGJ6Gg5HShohm-cas01.example.org at position 5 of 12 in additional filter chain; firing Filter: 'LogoutFilter'
Request 'GET /j_spring_cas_security_check' doesn't match 'POST /logout
/j_spring_cas_security_check?ticket=ST-10-yA1U32bOGJ6Gg5HShohm-cas01.example.org at position 6 of 12 in additional filter chain; firing Filter: 'CasAuthenticationFilter'
Checking match of request : '/j_spring_cas_security_check'; against 'https://localhost:8443/i9t-ym/j_spring_cas_security_check'
serviceTicketRequest = false
proxyReceptorConfigured = false
proxyReceptorRequest = false
proxyTicketRequest = false
requiresAuthentication = false
/j_spring_cas_security_check?ticket=ST-10-yA1U32bOGJ6Gg5HShohm-cas01.example.org at position 7 of 12 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
pathInfo: both null (property equals)
queryString: arg1=ticket=ST-9-DElbuW6RP24GocThfiBt-cas01.example.org; arg2=ticket=ST-10-yA1U32bOGJ6Gg5HShohm-cas01.example.org (property not equals)
saved request doesn't match
/j_spring_cas_security_check?ticket=ST-10-yA1U32bOGJ6Gg5HShohm-cas01.example.org at position 8 of 12 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
/j_spring_cas_security_check?ticket=ST-10-yA1U32bOGJ6Gg5HShohm-cas01.example.org at position 9 of 12 in additional filter chain; firing Filter: 'AnonymousAuthenticationFilter'
Populated SecurityContextHolder with anonymous token: 'org.springframework.security.authentication.AnonymousAuthenticationToken@905571d8: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@0: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 0CA64FA23DD44EECC261887599F2541B; Granted Authorities: ROLE_ANONYMOUS'
/j_spring_cas_security_check?ticket=ST-10-yA1U32bOGJ6Gg5HShohm-cas01.example.org at position 10 of 12 in additional filter chain; firing Filter: 'SessionManagementFilter'
/j_spring_cas_security_check?ticket=ST-10-yA1U32bOGJ6Gg5HShohm-cas01.example.org at position 11 of 12 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
/j_spring_cas_security_check?ticket=ST-10-yA1U32bOGJ6Gg5HShohm-cas01.example.org at position 12 of 12 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
Request '/j_spring_cas_security_check' matched by universal pattern '/**'
Secure object: FilterInvocation: URL: /j_spring_cas_security_check?ticket=ST-10-yA1U32bOGJ6Gg5HShohm-cas01.example.org; Attributes: [hasRole('ROLE_USER')]
Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken@905571d8: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@0: RemoteIpAddress: 0:0:0:0:0:0:0:1; SessionId: 0CA64FA23DD44EECC261887599F2541B; Granted Authorities: ROLE_ANONYMOUS
Voter: org.springframework.security.web.access.expression.WebExpressionVoter@2c4d2096, returned: -1
Access is denied (user is anonymous); redirecting to authentication entry point
org.springframework.security.access.AccessDeniedException: Access is denied
at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:83)
at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:232)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:123)
at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:90)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:114)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:122)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:111)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:169)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:48)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:205)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:120)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:96)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:64)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:91)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:53)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:176)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:219)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:614)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:142)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:617)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:518)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1091)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:668)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1527)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1484)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
Trying to match using Ant [pattern='/**', GET]
Request '/j_spring_cas_security_check' matched by universal pattern '/**'
Trying to match using NegatedRequestMatcher [requestMatcher=Ant [pattern='/**/favicon.ico']]
Checking match of request : '/j_spring_cas_security_check'; against '/**/favicon.ico'
matches = true
Trying to match using NegatedRequestMatcher [requestMatcher=MediaTypeRequestMatcher [contentNegotiationStrategy=org.springframework.web.accept.ContentNegotiationManager@37c9ddce, matchingMediaTypes=[application/json], useEquals=false, ignoredMediaTypes=[*/*]]]
httpRequestMediaTypes=[text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8]
Processing text/html
application/json .isCompatibleWith text/html = false
Processing application/xhtml+xml
application/json .isCompatibleWith application/xhtml+xml = false
Processing application/xml;q=0.9
application/json .isCompatibleWith application/xml;q=0.9 = false
Processing */*;q=0.8
Ignoring
Did not match any media types
matches = true
Trying to match using NegatedRequestMatcher [requestMatcher=RequestHeaderRequestMatcher [expectedHeaderName=X-Requested-With, expectedHeaderValue=XMLHttpRequest]]
matches = true
All requestMatchers returned true
DefaultSavedRequest added to Session: DefaultSavedRequest[https://localhost:8443/i9t-YM/j_spring_cas_security_check?ticket=ST-10-yA1U32bOGJ6Gg5HShohm-cas01.example.org]
Calling Authentication entry point.
SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
SecurityContextHolder now cleared, as request processing completed
我在这里尝试了其他问题的许多解决方案,但我不知道我是否只是做了一些我看不到的非常愚蠢的事情,或者我是否搞砸了配置。
有什么遗漏的可以告诉我,我会补充的。提前致谢!
编辑:它没有验证应用程序中的票证,但它应该可以工作。另一个应用程序正在按预期工作:
2015-08-28 08:44:39,049 INFO [org.jasig.cas.CentralAuthenticationServiceImpl] - <Granted service ticket [ST-44-QmOXrKwachmUcM16DqV4-cas01.example.org] for service [https://localhost:8443/casTest/j_spring_cas_security_check] for user [joe]>
2015-08-28 08:44:39,049 INFO [com.github.inspektr.audit.support.Slf4jLoggingAuditTrailManager] - <Audit trail record BEGIN
=============================================================
WHO: joe
WHAT: ST-44-QmOXrKwachmUcM16DqV4-cas01.example.org for https://localhost:8443/casTest/j_spring_cas_security_check
ACTION: SERVICE_TICKET_CREATED
APPLICATION: CAS
WHEN: Fri Aug 28 08:44:39 BRT 2015
CLIENT IP ADDRESS: 0:0:0:0:0:0:0:1
SERVER IP ADDRESS: 0:0:0:0:0:0:0:1
=============================================================
>
2015-08-28 08:44:39,063 INFO [com.github.inspektr.audit.support.Slf4jLoggingAuditTrailManager] - <Audit trail record BEGIN
=============================================================
WHO: audit:unknown
WHAT: ST-44-QmOXrKwachmUcM16DqV4-cas01.example.org
ACTION: SERVICE_TICKET_VALIDATED
APPLICATION: CAS
WHEN: Fri Aug 28 08:44:39 BRT 2015
CLIENT IP ADDRESS: 127.0.0.1
SERVER IP ADDRESS: 127.0.0.1
=============================================================
>
EDIT2:一个非常奇怪的补充:其他示例应用程序单独使用 servlet,并且与 spring 3.2 一起工作。现在,我尝试模仿将原始版本复制到此示例中,并尝试将其降级直到它起作用。问题是我无法让它工作任何 @Controller 注释或类似的...但是重定向消失了。如果我更新我的 Maven,从 4.2 升级到 3.2,循环就会消失。但如果我使用的是 4.2,除了版本之外什么都不改变,就会出现循环!