spring boot 2.0 集成 shiro 和 pac4j cas单点登录-Ajax请求携带cookie跨域
一、CAS服务端
# CAS官方配置说明 https://apereo.github.io/cas/5.3.x/installation/Configuration-Properties.html
cas:
httpWebRequest:
header:
# 允许使用iframe嵌套CAS登录页面
xframe: false
cors:
# 跨域cookie
allowCredentials: true
allowHeaders:
- '*'
allowMethods:
- '*'
allowOrigins:
- '*'
enabled: true
exposedHeaders:
- 'Set-Cookie'
maxAge: 3600
二、CAS客户端
spring boot 2.0 集成 shiro 和 pac4j cas单点登录
1.新建CorsFilter类
package com.ruoyi.framework.shiro.web.filter;
import org.springframework.web.cors.CorsUtils;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 跨域配置
*/
public class CorsFilter implements Filter {
/**
* 增加跨域设置
*/
@Override
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse rep = (HttpServletResponse) response;
if (CorsUtils.isCorsRequest(req)) {
String origin = req.getHeader("Origin");
// Access-Control-Allow-Origin的值不要写*号,因为对于带有cookie的跨域请求,浏览器不支持这种宽松的策略
rep.setHeader("Access-Control-Allow-Origin", origin);
// 允许的访问方法
rep.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE, PATCH");
// Access-Control-Max-Age 用于 CORS 相关配置的缓存
rep.setHeader("Access-Control-Max-Age", "3600");
rep.setHeader("Access-Control-Allow-Headers", "token, Accept, Origin, X-Requested-With, Content-Type, Last-Modified");
// 如果操作cookie,必须加上这句话
rep.setHeader("Access-Control-Allow-Credentials", "true");
}
filterChain.doFilter(request, response);
}
}
2.修改ShiroConfig
@Bean
public FilterRegistrationBean corsFilter() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new CorsFilter());
filterRegistrationBean.setEnabled(true);
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.setOrder(1);
return filterRegistrationBean;
}
@Bean
public FilterRegistrationBean filterRegistrationBean() {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new DelegatingFilterProxy("shiroFilter"));
filterRegistrationBean.addInitParameter("targetFilterLifecycle", "true");
filterRegistrationBean.setEnabled(true);
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.setOrder(10);
return filterRegistrationBean;
}
/**
* Shiro过滤器配置
*/
@Bean
public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager, Config config)
{
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// Shiro的核心安全接口,这个属性是必须的
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, Filter> filters = new LinkedHashMap<String, Filter>();
// cas 资源认证拦截器
SecurityFilter securityFilter = new SecurityFilter();
securityFilter.setConfig(config);
securityFilter.setClients(casProperties.getCasClientName());
// cas 认证后回调拦截器
CallbackFilter callbackFilter = new CallbackFilter();
callbackFilter.setDefaultUrl(casProperties.getCasClientUrl());
callbackFilter.setConfig(config);
// 注销 拦截器
LogoutFilter logoutFilter = new LogoutFilter();
logoutFilter.setConfig(config);
logoutFilter.setCentralLogout(true);
logoutFilter.setLocalLogout(true);
logoutFilter.setDefaultUrl(casProperties.getCasClientUrl() + "/callback?client_name=" + casProperties.getCasClientName());
filters.put("onlineSession", onlineSessionFilter());
filters.put("syncOnlineSession", syncOnlineSessionFilter());
filters.put("logout",logoutFilter);
filters.put("callbackFilter", callbackFilter);
filters.put("securityFilter", securityFilter);
shiroFilterFactoryBean.setFilters(filters);
// Shiro连接约束配置,即过滤链的定义
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// 对静态资源设置匿名访问
filterChainDefinitionMap.put("/favicon.ico**", "anon");
filterChainDefinitionMap.put("/ruoyi.png**", "anon");
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/docs/**", "anon");
filterChainDefinitionMap.put("/fonts/**", "anon");
filterChainDefinitionMap.put("/img/**", "anon");
filterChainDefinitionMap.put("/images/**", "anon");
filterChainDefinitionMap.put("/ajax/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/ruoyi/**", "anon");
filterChainDefinitionMap.put("/captcha/captchaImage**", "anon");
filterChainDefinitionMap.put("/webjars/**", "anon");
filterChainDefinitionMap.put("/callback", "callbackFilter");
filterChainDefinitionMap.put("/logout", "logout");
// 所有请求需要认证
filterChainDefinitionMap.put("/**", "securityFilter,onlineSession,syncOnlineSession");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
说明:
注意的是项目加载了哪些filter,它们的顺序是怎样的?这样有利于排查问题。
在CorsFilter#doFilter方法内打断点,watch 变量FilterChain filterChain的值,注意其顺序。
三、ajax调用示例
$.ajax({
type:"post",
async: false,
url:"http://127.0.0.1:80/system/userInfo",
data: {},
// 携带cookie
xhrFields: {
withCredentials: true
},
crossDomain:true,
dataType: 'json',
success:function(data, textStatus, jqXHR) {
console.log(data);
},
error: function(xhr, textStatus, e) {
console.log(textStatus);
}
});
注意:新版本的Chrome浏览器,默认禁用跨域携带cookie,可以手动设置浏览器chrome://flags/ 搜索SameSite设置项并禁用(不建议)。可以使用nginx代理各系统,解决同源问题。
参考:
解决在CAS中的跨域请求问题
Refused to display ‘url’ in a frame because it set ‘X-Frame-Options’ to ‘deny’