先讲一下此次项目的基本架构 使用springcloudalibaba 做的分布式项目shiro实现单点登录,前后端分离,前端使用vue。
使用shiro结合redis存储session信息登录之后返回token字符串,前端请求携带在header里以Authorization携带到后台。
1跨域问题
在gateway进行统一配置
@Configuration public class CorsConfig { @Bean public CorsWebFilter corsWebFilter() { CorsConfiguration cfg = new CorsConfiguration(); cfg.setAllowCredentials(true); cfg.addAllowedOrigin("*"); cfg.addAllowedMethod("*"); cfg.addAllowedHeader("*"); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", cfg); return new CorsWebFilter(source); } }
另外在yml配置
routes: - id: com-lepu-his-adapter uri: lb://com-lepu-his-adapter predicates: - Path=/his/** filters: - StripPrefix=1 - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin
主要是最后DedupeResponseHeader 意思是去除响应头中重复的值。
2 前后端分离之后需要放过OPTIONS请求
在gateway中配置
@Component public class LoginFilter implements GlobalFilter, Ordered { @Override public int getOrder() { return -1; } @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); ServerHttpResponse response = exchange.getResponse(); if (CorsUtils.isCorsRequest(request)) { HttpHeaders requestHeaders = request.getHeaders(); HttpMethod requestMethod = requestHeaders.getAccessControlRequestMethod(); HttpHeaders headers = response.getHeaders(); //放过OPTIONS请求 if (request.getMethod() == HttpMethod.OPTIONS) { response.setStatusCode(HttpStatus.OK); return Mono.empty(); } } boolean containsKey = exchange.getRequest().getHeaders().containsKey("Authorization"); if(!containsKey){ response.setStatusCode(HttpStatus.UNAUTHORIZED); DataBuffer wrap = response.bufferFactory().wrap("token22 can not be null".getBytes()); return response.writeWith(Mono.just(wrap)); } return chain.filter(exchange); } }
一方面是配置放过OPTIONS请求,另外一方面在gateway只做了认证请求头不能为空的限制。
3 前后端分离之后shiro等未登录、未授权等问题不能通过配置重定向页面来解决。
3.1未登录问题 继承FormAuthenticationFilter
@Slf4j public class ShiroFormAuthenticationFilter extends FormAuthenticationFilter { @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { if (this.isLoginRequest(request, response)) { if (this.isLoginSubmission(request, response)) { if (log.isTraceEnabled()) { log.trace("Login submission detected. Attempting to execute login."); } return this.executeLogin(request, response); } else { if (log.isTraceEnabled()) { log.trace("Login page view."); } return true; } } else { HttpServletRequest req = (HttpServletRequest)request; HttpServletResponse resp = (HttpServletResponse)response; if (req.getMethod().equals(RequestMethod.OPTIONS.name())) { resp.setStatus(HttpStatus.OK.value()); return true; } else { if (log.isTraceEnabled()) { log.trace("Attempting to access a path which requires authentication. Forwarding to the Authentication url [{}]" ,this.getLoginUrl()); } /** * 在这里实现自己想返回的信息,其他地方和源码一样就可以了 */ resp.setHeader("Access-Control-Allow-Credentials", "true"); resp.setContentType("application/json; charset=utf-8"); resp.setCharacterEncoding("UTF-8"); PrintWriter out = resp.getWriter(); out.println(JSON.toJSONString(ResultBean.reponse(Constant.ERROR_401))); out.flush(); out.close(); return false; } } } }
3.2未授权问题
@Slf4j @ControllerAdvice public class BaseExceptionHandler { @ExceptionHandler(value = Exception.class) @ResponseBody public ResultBean error(HttpServletRequest request, HttpServletResponse response, Exception e) { log.error("系统错误", e); return ResultBean.reponse(Constant.ERROR_500); } @ExceptionHandler(value = AuthorizationException.class) @ResponseBody public ResultBean error(HttpServletRequest request, HttpServletResponse response, AuthorizationException e) { return ResultBean.reponse(Constant.ERROR_402); } }
在公共异常里面配置未授权异常的捕获类