SpringBoot2.x整合Shiro出现Cors跨域问题

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;
	}

 

参考文章:https://my.oschina.net/u/4397001/blog/3421625

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值