1. Springboot如何跨域?
最简单的方法是:
定义一个配置CorsConfig类即可(是不是简单且无耦合到令人发指)
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
//import org.springframework.web.servlet.config.annotation.CorsRegistry;
//import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @ClassName: CorsConfig
* @Description: TODO(解决shiro过滤器在跨域处理之前失效)
* 也可以使用过滤器的方法
*
* @author issuser
* @date 2021-02-03 03:48:13
*/
@Configuration
public class CorsConfig {
// implements WebMvcConfigurer{
//
// public void addCorsMapping(CorsRegistry registry) {
// registry.addMapping("/**")
// .allowedOrigins(new String[] { "*" })
// //设置是否允许跨域传cookie
// .allowCredentials(true)
// .allowedMethods(new String[] { "GET", "POST", "PUT", "DELETE", "OPTIONS" })
// //设置缓存时间,减少重复响应
// .maxAge(3600L).allowedHeaders("*").allowCredentials(true);
// }
private CorsConfiguration buildConfig() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
//允许任何域名访问
corsConfiguration.addAllowedOrigin("*");
//允许任何header访问
corsConfiguration.addAllowedHeader("*");
//允许任何方法访问
corsConfiguration.addAllowedMethod("*");
corsConfiguration.setMaxAge(3600L); // 预检请求的有效期,单位为秒。
corsConfiguration.setAllowCredentials(true);// 是否支持安全证书(必需参数)
return corsConfiguration;
}
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", buildConfig());
return new CorsFilter(source);
}
}
不要用WebMvcConfigurerAdapter继承的方法了,因为已经过时了,Springboot2.0已经不推荐此方法
此处有一个比较坑的地方就是:
corsConfiguration.setMaxAge(3600L); // 预检请求的有效期,单位为秒。
corsConfiguration.setAllowCredentials(true);// 是否支持安全证书(必需参数)
这两句务必要加上,不然无论前端怎么做,也无论后台你对shiro的过滤器怎么重写,response请求返回状态都是302。
网络上大家的代码都是copy来copy去,往往没有这2句,所以这个坑真是眼泪汪汪。
我注释掉的代码就是继承的webMvcConfigurerAdapter,换成新的代码后就解决了跨域的问题。
2、自定义ShiroFilter
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMethod;
import com.dh.common.util.HttpContextUtils;
import com.google.gson.Gson;
/**
* @ClassName: OAuth2Filter
* @Description: TODO(shiro的拦截器:需要认证的api被调用前执行的拦截器也叫过滤器)
* @author issuser
* @date 2021-01-29 04:39:43
*/
public class OAuth2Filter extends AuthenticatingFilter{
private final Logger log = LoggerFactory.getLogger(OAuth2Filter.class);
/**
* @Title: createToken
* @Description: TODO(描述)
* @param request
* @param response
* @return
* @throws Exception
* @author issuser
* @date 2021-01-29 04:43:38
*/
@Override
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response)
throws Exception {
String token = getRequestToken((HttpServletRequest)request);
if(StringUtils.isBlank(token)) {
return null;
}
return new OAuth2Token(token);
}
/**
* @Title: isAccessAllowed
* @Description: TODO(解决OPTIONS请求跨域问题,如果是OPTIONS请求则放行)
* @param request
* @param response
* @param mappedValue
* @return
* @author issuser
* @date 2021-02-03 02:42:39
*/
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue){
if (((HttpServletRequest)request).getMethod().equals(RequestMethod.OPTIONS.name())) {
System.out.println("CROS发送的OPTIONS请求,不带数据,放行!");
return true;
}
return false;
}
/**
* @Title: onAccessDenied
* @Description: TODO(鉴权失败)
* @param request
* @param response
* @return
* @throws Exception
* @author issuser
* @date 2021-01-29 04:43:38
*/
@Override
protected boolean onAccessDenied(ServletRequest request, ServletResponse response)
throws Exception {
String token = getRequestToken((HttpServletRequest)request);
//判断token是否为空
if(StringUtils.isBlank(token)) {
HttpServletResponse httpResponse = (HttpServletResponse) response;
//是否允许浏览器携带用户身份信息(cookie)
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
// 允许哪些Origin发起跨域请求
httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin());
Map<String, Object> map = new HashMap<>();
map.put("401", "invalid(无效的) token");
//令牌无效
String json = new Gson().toJson(map);
httpResponse.getWriter().print(json);
return false;
}
return executeLogin(request, response);
}
/**
* @Title: getRequestToken
* @Description: TODO(获取token)
* @param request
* @return String
* @throws
* @author issuser
* @date 2021-01-29 04:56:08
*/
private String getRequestToken(HttpServletRequest request) {
String token = request.getHeader("ddkj_token");
log.info("获取请求头中的Authorization属性!");
if(StringUtils.isBlank(token)) {
token = request.getParameter("ddkj_token");
}
return token;
}
/**
* @Title: onLoginFailure
* @Description: TODO(登录失败)
* @param token
* @param e
* @param request
* @param response
* @return
* @author issuser
* @date 2021-02-03 02:45:15
*/
protected boolean onLoginFailure(AuthenticationToken token,
AuthenticationException e, ServletRequest request, ServletResponse response){
HttpServletResponse httpResponse = (HttpServletResponse)response;
httpResponse.setContentType("application/json;charset=utf-8");
httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
httpResponse.setHeader("Access-Control-Allow-Origin", HttpContextUtils.getOrigin());
try{
Throwable throwable = e.getCause() == null ? e : e.getCause();
Map<String, Object> map = new HashMap<>();
map.put("401", throwable.getMessage());
String json = new Gson().toJson(map);
httpResponse.getWriter().print(json);
}catch (IOException localIOException) {
}
return false;
}
}
3、将shiro注入到spring容器中
/**
* @ClassName: ShiroConfig
* @Description: TODO(集成shiro到spring容器中)
* @author issuser
* @date 2021-01-27 06:01:41
*/
@Configuration
public class ShiroConfig {
/**
* @Title: sessionManagerr管理着Session的创建、操作以及清除等
* @Description: TODO(shiro-会话管理器 sessionManager)
*
* Shiro提供的Session和Servlet中的Session其实是一样的作用,
* 只是Shiro中的Session不需要再依赖于WEB容器存在。
* Shiro中提供了基于内存和基于缓存的两种方式来存储Session
*
* @return SessionManager
* @throws
* @author issuser
* @date 2021-01-28 09:55:13
*/
@Bean({"sessionManager"})
public SessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
//manager.setCacheManager(cacheManager);// 加入缓存管理器
// manager.setSessionFactory(shiroSessionFactory());//设置sessionFactory
// manager.setSessionDAO(shiroSessionDao());// 设置SessionDao
// manager.setDeleteInvalidSessions(true);// 删除过期的session
// manager.setGlobalSessionTimeout(shiroSessionDao().getExpireTime());// 设置全局session超时时间
sessionManager.setSessionValidationSchedulerEnabled(true); // 是否定时检查session
sessionManager.setSessionIdCookieEnabled(true);
return sessionManager;
}
/**
* @Title: securityManager
* @Description: TODO(权限管理,配置主要是Realm的管理认证)
* @param sessionManager
* @return SecurityManager
* @throws
* @author issuser
* @date 2021-01-28 05:56:42
*/
@Bean({"securityManager"})
public SecurityManager securityManager(OAuth2Realm oAuth2Realm, SessionManager sessionManager) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(oAuth2Realm);
securityManager.setSessionManager(sessionManager);
return securityManager;
}
/**
* @Title: shiroFilter
* @Description: TODO(Filter工厂,设置对应的过滤条件和跳转条件)
* @param securityManager
* @return ShiroFilterFactoryBean
* @throws
* @author issuser
* @date 2021-02-03 02:50:05
*/
@Bean({"shiroFilter"})
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
shiroFilter.setSecurityManager(securityManager);
Map<String, Filter> filters = new HashMap<>();
filters.put("oauth2", new OAuth2Filter());
shiroFilter.setFilters(filters);
//登录
// shiroFilter.setLoginUrl("/login");
//首页
// shiroFilter.setSuccessUrl("/index");
//错误页面,认证不通过跳转
// shiroFilter.setUnauthorizedUrl("/error");
Map<String, String> filterMap = new LinkedHashMap<>();
// <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
//默认的过滤器还有:anno、authc
//配置不会被拦截的连接 顺序判断
filterMap.put("/webjars/**", "anon");
filterMap.put("/druid/**", "anon");
filterMap.put("/app/**", "anon");
filterMap.put("/sys/login", "anon");
filterMap.put("/swagger/**", "anon");
filterMap.put("/v2/api-docs", "anon");
filterMap.put("/swagger-ui.html", "anon");
filterMap.put("/swagger-resources/**", "anon");
filterMap.put("/captcha.jpg", "anon");
filterMap.put("/ddkjApi/**", "anon");
filterMap.put("/chain/**", "anon");
//主要这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截 剩余的都需要认证
filterMap.put("/**", "oauth2");
shiroFilter.setFilterChainDefinitionMap(filterMap);
return shiroFilter;
}
@Bean({"lifecycleBeanPostProcessor"})
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
return new LifecycleBeanPostProcessor();
}
/**
* @Title: defaultAdvisorAutoProxyCreator
* @Description: TODO(利用注解配置权限)
*
* 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,
* 并在必要时进行安全逻辑验证
* 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能
*
* @return DefaultAdvisorAutoProxyCreator
* @throws
* @author issuser
* @date 2021-02-03 03:29:22
*/
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();
proxyCreator.setProxyTargetClass(true);
return proxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}