凉衫薄

人生哪能多如意,万事只求半称心。
Shiro
问题 在前后端未分离的架构中,一般由shiro认证后,直接返回登陆成功的视图。而在前后端分离的架构中,页面的跳转由前端路由控制,后端仅仅返回json数据给前端。要在前后端分离架构中使用shiro就必须将返回视图的逻辑改为返回json。 Shiro过滤器的拦截过程 我们先来看一下Shiro过滤器的继承关系,如下图所示(图片偷来的): 我们需要关注的是AccessControlFilter这个过滤器,AccessControlFilter中有三个比较常用的方法: protected abstract boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception; protected abstract boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception; public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception; 1.isAccessAllowed方法表示该URL是否允许访问,mappedValue参数是我们配置的URL拦截规则,返回true表示允许访问,false表示不允许访问。 2.onAccessDenied方法表示当请求被拒绝时,是否需要继续处理,返回true表示继续处理,返回false表示不再执行拦截器链,直接返回。 3.onPrehandle方法是AdviceFilter方法中定义的,他会调用isAccessAllowed和onAccessDenied来判断是否进行处理,源码如下: public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue); } 而我们在前后端未分离架构中常用的是AccessControlFilter的子类FormAuthenticationFilter,他的onAccessDenied是这样写的: protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { if (isLoginRequest(request, response)) { if (isLoginSubmission(request, response)) { if (log.isTraceEnabled()) { log.trace("Login submission detected. Attempting to execute login."); } return executeLogin(request, response); } else { if (log.isTraceEnabled()) { log.trace("Login page view."); } //allow them to see the login page ;) return true; } } else { if (log.isTraceEnabled()) { log.trace("Attempting to access a path which requires authentication. Forwarding to the " +"Authentication url [" + getLoginUrl() + "]"); } saveRequestAndRedirectToLogin(request, response); return false; } } 大致讲解一下,当我们登陆时提交的表单URL被FormAuthenticationFilter拦截后,会执行onAccessDenied方法,首先该方法会判断该请求是否是访问登陆URL的请求,如果否直接跳转到登陆页面,如果是再判断该请求是请求登陆的页面还是提交的是登陆的表单信息,如果是则返回登陆的页面,否则执行executeLogin方法,该方法具体怎么实现我们不用去管,有兴趣的同学可以看源码,我们直需要知道,登陆成功后会执行onLoginSuccess方法,登陆失败后会执行onLoginFailure。onLoginFailure默认处理是返回登陆页面,onLoginSuccess默认实现是返回登陆成功的页面,也就是跳转到登陆页面之前被拦截的那个页面(有点绕,慢慢品味)。 解决方法 好了,了解了Shiro过滤器原理之后,所有问题都迎刃而解了,前后端分离架构中,无非是把原先Shiro返回的视图改为返回Json。我们写一个AjaxFormAuthenticadtionFilter类继承FormAuthenticationFilter类,修改onAccessDenied、onLoginSuccess、onloginFailure方法,源码如下(这里就不返回json了,直接返回一个字符串代替): public class AjaxFormAuthenticationFilter extends FormAuthenticationFilter { private static final Logger log = LoggerFactory.getLogger(AjaxFormAuthenticationFilter.class); @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { if (isLoginRequest(request, response)) { if (isLoginSubmission(request, response)) { if (log.isTraceEnabled()) { log.trace("Login submission detected. Attempting to execute login."); } return executeLogin(request, response); } else { if (log.isTraceEnabled()) { log.trace("Login page view."); } //allow them to see the login page ;) return true; } } else { if (log.isTraceEnabled()) { log.trace("Attempting to access a path which requires authentication. Forwarding to the " + "Authentication url [" + getLoginUrl() + "]"); } response.setContentType("application/json"); response.setCharacterEncoding("UTF-8"); PrintWriter out = response.getWriter(); out.println("尚未登陆,拒绝访问"); out.flush(); out.close(); return false; } } @Override protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) { response.setContentType("application/json"); response.setCharacterEncoding("UTF-8"); PrintWriter out = null; try { out = response.getWriter(); } catch (IOException e) { e.printStackTrace(); } out.println("登陆成功"); out.flush(); out.close(); return true; } @Override protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) { response.setContentType("application/json"); response.setCharacterEncoding("UTF-8"); PrintWriter out = null; try { out = response.getWriter(); } catch (IOException e1) { e1.printStackTrace(); } out.println("登陆失败"); out.flush(); out.close(); return false; } } 在这里前后端分离架构中springboot整合shiro算是完成了。