我有一个Spring Boot 2.2应用程序,用于使用组织的CAS实例对用户进行身份验证。当用户请求需要授权(例如角色)时,spring sec cas集成(过滤器)通过将其转发以登录到CAS服务器来开始进行身份验证,然后将其重定向到其原始请求。很好。
现在,我们需要组织外部的用户才能登录(没有CAS帐户)。
我的当前配置有两个问题:
1)它会将所有未经身份验证的用户转发到CAS服务。相反,我想将它们转发到/ login的应用内登录表单(使用POST方法login);这提供了使用CAS登录的选项。 See redacted screenshot of sign in form
2)当用户使用/ login表单并单击“使用CAS登录”时,我希望他们通过CAS进行身份验证,然后让他们继续其原始请求。
两个AuthenticationProvider
都是分段工作的。 dao身份验证提供程序只有在访问/login
时才能访问。否则会发生上述问题1。我不知道如何在他们单击按钮时配置#2(/login/cas
上的GET?但是当GET被视为新请求却又被放弃时,如何将它们返回到原始请求?)
一种总结预期行为的方案:未经身份验证的用户向受保护区域发出Web请求。用户被转发到/ login
a)他们使用电子邮件和密码(已通过daoAutheticationProvider验证)登录。或者
b)他们单击使用CAS按钮登录(已通过casAuthenticationProvider验证)。
通过身份验证后,它们将转发回其原始请求。
这里是代码,全部在java配置中,我们在方法上使用@PreAuthorize,而不在HttpSecurity配置上使用antmatchers,但如果解决方案需要如何工作,愿意返回到antmatchers
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true,jsr250Enabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
public void configureGlobal(AuthenticationmanagerBuilder auth) {
auth.authenticationProvider(daoAuthenticationProvider());
auth.authenticationProvider(casAuthenticationProvider());
}
private DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider ap = new DaoAuthenticationProvider();
ap.setUserDetailsService(daoUserDetailsService); //implements UserDetailsService
return ap;
}
private CasAuthenticationProvider casAuthenticationProvider() {
CasAuthenticationProvider ap = new CasAuthenticationProvider();
ap.setauthenticationUserDetailsService(casCustomUserDetailsService); //extends AbstractCasAssertionUserDetailsService
ap.setServiceProperties(serviceProperties());
ap.setTicketValidator(new Cas30ServiceTicketValidator(casServiceUrl));
ap.setKey("obviouslyredacted");
return ap;
}
private CasAuthenticationFilter casAuthenticationFilter() throws Exception {
CasAuthenticationFilter filter = new CasAuthenticationFilter();
filter.setfilterProcessesUrl("/j_spring_cas_security_check");
filter.setauthenticationmanager(authenticationmanager());
return filter;
}
private LogoutFilter requestSingleLogoutFilter() {
LogoutFilter filter = new LogoutFilter(casServiceUrl + "/logout",new SecurityContextLogoutHandler());
filter.setfilterProcessesUrl("/j_spring_cas_security_logout");
return filter;
}
private SingleSignOutFilter singleSignOutFilter() {
SingleSignOutFilter filter = new SingleSignOutFilter();
filter.setCasServerUrlPrefix(casServiceUrl);
filter.setIgnoreInitConfiguration(true);
return filter;
}
private CasAuthenticationEntryPoint casAuthenticationEntryPoint() {
CasAuthenticationEntryPoint ep = new CasAuthenticationEntryPoint();
ep.setLoginUrl(casServiceUrl + "/login");
ep.setServiceProperties(serviceProperties());
return ep;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
XFrameOptionsHeaderWriter xframeoptions = new XFrameOptionsHeaderWriter(SAMEORIGIN);
XXssprotectionHeaderWriter xxssprotection = new XXssprotectionHeaderWriter();
xxssprotection.setEnabled(true);
http
.sessionmanagement()
.sessionCreationPolicy(ALWAYS)
.and().csrf().disable()
.authorizeRequests()
.antMatchers("/login").permitAll()
.antMatchers("/css/**").permitAll()
.antMatchers("/favicon_192x192.png").permitAll()
.anyRequest().authenticated()
.and().formLogin()
.loginPage("/login")
.permitAll()
.and().logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/dologout"))
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.permitAll()
.and().exceptionHandling()
.accessDeniedPage(NAV_URL_ERROR + "/403")
.and().headers().addHeaderWriter(xframeoptions)
.and().headers().addHeaderWriter(xxssprotection);
//Only enable CAS in Production or SPI
if (activeProfile.equals(ENV_PRODUCTION) || activeProfile.equals(ENV_SPI))
http
.addFilter(casAuthenticationFilter())
.addFilterBefore(requestSingleLogoutFilter(),LogoutFilter.class)
.addFilterBefore(singleSignOutFilter(),CasAuthenticationFilter.class)
.logout()
.logoutUrl("/dologout")
.logoutSuccessUrl(applicationBaseUrl + "/j_spring_cas_security_logout")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.permitAll()
.and()
.exceptionHandling()
.authenticationEntryPoint(casAuthenticationEntryPoint())
.and()
.csrf().disable()
.headers().frameOptions().disable();
}
}
也感觉我的http config方法可以清理(此项目已从启动1.x天迁移过来)
更新: 有了Marco的评论,我得以找到解决方案。
我创建了一个扩展CasAuthenticationPoint的自定义类,该自定义类在所有重写的方法上调用super(...),只是公开了最终的和受保护的方法进行操作(我仍然需要CAS来创建和编码服务并进行重定向网址)。
private CommonAuthenticationEntryPoint commonAuthenticationEntryPoint() {
CustomCasAuthenticationEntryPoint ep = new CustomCasAuthenticationEntryPoint();
ep.setLoginUrl(casServiceUrl + "/login");
ep.setServiceProperties(serviceProperties());
return new CommonAuthenticationEntryPoint(ep);
}
然后是AutheticationEntryPoint:
public class CommonAuthenticationEntryPoint implements AuthenticationEntryPoint,InitializingBean {
private final CustomCasAuthenticationEntryPoint customCasAuthenticationEntryPoint;
public CommonAuthenticationEntryPoint(CustomCasAuthenticationEntryPoint customCasAuthenticationEntryPoint) {
this.customCasAuthenticationEntryPoint = customCasAuthenticationEntryPoint;
}
@Override
public void afterPropertiesSet() {
customCasAuthenticationEntryPoint.afterPropertiesSet();
}
@Override
public void commence(final HttpServletRequest request,final HttpServletResponse response,final AuthenticationException authenticationException) throws IOException {
if (request.getParameter("cas") == null)
response.sendRedirect("login");
else {
final String urlEncodedService = customCasAuthenticationEntryPoint.createServiceUrl(request,response);
final String redirectUrl = customCasAuthenticationEntryPoint.createRedirectUrl(urlEncodedService);
customCasAuthenticationEntryPoint.preCommence(request,response);
response.sendRedirect(redirectUrl);
}
}
}
百里香叶中的按钮可以做到这一点:
<a class="btn btn-lg btn-default btn-block" th:href="@{${session.get('SPRING_SECURITY_SAVED_REQUEST').redirectUrl}(cas=true)}">
<img th:src="@{/favicon_192x192.png}" aria-hidden="true" alt="" id="org-sso"/> Organization CAS
</a>