shiro有几种默认的拦截器,authc,anno,roles,user等 authc就是FormAuthenticationFilter的实例
ShiroFilterFactoryBean的配置:
private Map<String, Filter> filters; <取名,拦截器地址>,可以自定义拦截器放在这
private Map<String, String> filterChainDefinitionMap; <url,拦截器名>哪些路径会被此拦截器拦截到
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 必须设置 SecurityManager shiroFilterFactoryBean.setSecurityManager(securityManager); shiroFilterFactoryBean.setLoginUrl("/login"); shiroFilterFactoryBean.setSuccessUrl("/index"); shiroFilterFactoryBean.setUnauthorizedUrl("/403"); // private Map<String, Filter> filters; shiro有一些默认的拦截器 比如auth,它就是FormAuthenticationFilter表单拦截器 <取名,拦截器地址>,可以自定义拦截器放在这 //private Map<String, String> filterChainDefinitionMap; <url,拦截器名>哪些路径会被此拦截器拦截到 // 拦截器. Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); // 配置不会被拦截的链接 顺序判断 filterChainDefinitionMap.put("/static/**", "anon"); filterChainDefinitionMap.put("/ajaxLogin", "anon"); filterChainDefinitionMap.put("/focus/userlogin", "anon"); filterChainDefinitionMap.put("/swagger-ui.html#", "anon"); // 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 // <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问;user:remember me的可以访问--> filterChainDefinitionMap.put("/fine", "user"); filterChainDefinitionMap.put("/focus/**", "authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); System.out.println("Shiro拦截器工厂类注入成功"); return shiroFilterFactoryBean; }
一。请求被authc拦截,如果状态未登录,就会被跳到登录页面,登录成功后,会继续原请求页面,除非原请求就是successurl,才去successurl
PathMatchingFilter是开涛讲过得,匹配某url 拦截进行处理的拦截器,里面有匹配url方法,preHandle,onPreHandle等方法。
AccessControlFilter重写了onPreHandle
pathsMatch:该方法用于path与请求路径进行匹配的方法;如果匹配返回true;
onPreHandle:在preHandle中,当pathsMatch匹配一个路径后,会调用opPreHandler方法并将路径绑定参数配置传给mappedValue;然后可以在这个方法中进行一些验证(如角色授权),如果验证失败可以返回false中断流程;默认返回true;也就是说子类可以只实现onPreHandle即可,无须实现preHandle。如果没有path与请求路径匹配,默认是通过的(即preHandle返回true)
public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue); }
isAccessAllowed:请求是否被允许访问,此方法在AccessControlFilter是抽象方法,被AuthenticatingFilter重写
@Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { return super.isAccessAllowed(request, response, mappedValue) || (!isLoginRequest(request, response) && isPermissive(mappedValue)); }
分别点进去看,调用父类AuthenticationFilter的方法判断 当前用户是否已认证过
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { Subject subject = getSubject(request, response); return subject.isAuthenticated(); }
并且判断请求url是不是配置的loginurl
protected boolean isLoginRequest(ServletRequest request, ServletResponse response) { return pathsMatch(getLoginUrl(), request); }
回头再看 onPrehandle,如果是一个没认证过的请求,isAccessAlowed肯定是false,执行onAccessDefined方法,开涛也说过,此方法是请求未通过认证时执行的方法,按逻辑推理,请求未认证就跳转到loginurl在这里实现
这个方法在authc里,onAccessDenied(用到这点:子类继承父类,重写了A方法,父类有个B方法,子类对象调用B方法,执行的是子类的A方法)
如果请求是登录请求,发起登录,如果不是就保存原请求,并重定向到登录url ,saveRequestAndRedirectToLogin
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; } }
看看saveRequestAndRedirectToLogin
protected void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException { saveRequest(request); redirectToLogin(request, response); }
saveRequest
protected void saveRequest(ServletRequest request) { WebUtils.saveRequest(request); }
webUtils.saveRequest:其实是放到了session里,key是SAVED_REQUEST_KEY
public static void saveRequest(ServletRequest request) { Subject subject = SecurityUtils.getSubject(); Session session = subject.getSession(); HttpServletRequest httpRequest = toHttp(request); SavedRequest savedRequest = new SavedRequest(httpRequest); session.setAttribute(SAVED_REQUEST_KEY, savedRequest); }
那登录成功后怎么跳转到原请求页面的?肯定是从session取出原请求,还是看authc的onAcessDefined方法
如果请求是loginurl并允许,就发起登录
if (isLoginRequest(request, response)) { if (isLoginSubmission(request, response)) { if (log.isTraceEnabled()) { log.trace("Login submission detected. Attempting to execute login."); } return executeLogin(request, response); }
executeLogin:同样是 subject.login(token)
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception { AuthenticationToken token = createToken(request, response); if (token == null) { String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken " + "must be created in order to execute a login attempt."; throw new IllegalStateException(msg); } try { Subject subject = getSubject(request, response); subject.login(token); return onLoginSuccess(token, subject, request, response); } catch (AuthenticationException e) { return onLoginFailure(token, e, request, response); } }
onLoginSuccess:authc重写此方法
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception { issueSuccessRedirect(request, response); //we handled the success redirect directly, prevent the chain from continuing: return false; }
protected void issueSuccessRedirect(ServletRequest request, ServletResponse response) throws Exception { WebUtils.redirectToSavedRequest(request, response, getSuccessUrl()); }找到session的的原请求,发起请求
public static void redirectToSavedRequest(ServletRequest request, ServletResponse response, String fallbackUrl) throws IOException { String successUrl = null; boolean contextRelative = true; SavedRequest savedRequest = WebUtils.getAndClearSavedRequest(request); if (savedRequest != null && savedRequest.getMethod().equalsIgnoreCase(AccessControlFilter.GET_METHOD)) { successUrl = savedRequest.getRequestUrl(); contextRelative = false; } if (successUrl == null) { successUrl = fallbackUrl; } if (successUrl == null) { throw new IllegalStateException("Success URL not available via saved request or via the " + "successUrlFallback method parameter. One of these must be non-null for " + "issueSuccessRedirect() to work."); } WebUtils.issueRedirect(request, response, successUrl, null, contextRelative); }
从session取出了那个key 并清空
public static SavedRequest getAndClearSavedRequest(ServletRequest request) { SavedRequest savedRequest = getSavedRequest(request); if (savedRequest != null) { Subject subject = SecurityUtils.getSubject(); Session session = subject.getSession(); session.removeAttribute(SAVED_REQUEST_KEY); } return savedRequest; }
到这就大致明白,authc 完成 认证通过后 转发到原请求页面的流程
二。SecurityUtils.getSubject
前面的AuthenticationFilter或其他过滤器也有不少是直接通过这个方法得到subject,然后通过subject.isAuthenticated 包括check权限或角色,
就会想到是不是subject也保存到session里,登录成功一次后,以后的请求是如何判断 用户已认证通过呢?但不是这样
通过线程副本,这个后面再看,确实直接放在seesion不安全,删除cookie岂不是认证失败了
public static Subject getSubject() { Subject subject = ThreadContext.getSubject(); if (subject == null) { subject = (new Subject.Builder()).buildSubject(); ThreadContext.bind(subject); } return subject; }