shiro 运行流程
单词 | 词性 | 解释 |
---|---|---|
authentication | n. | 认证 |
authentic | adj. | 真实的 |
authorization | n. | 授权 |
authorise | vt. | 授权 |
authentication 你是谁
authorization 你能做什么
认证流程(可以在controller中进行,也可以在filter中进行):
JdbcRealm.doGetAuthenticationInfo(AuthenticationToken) line: 46 | 由抽象类最终调用最外层用户自定义方法(比较入参token 和返回info, 身份和凭证都匹配则通过,需要用到credentialsMatcher) |
JdbcRealm(AuthenticatingRealm).getAuthenticationInfo(AuthenticationToken) line: 571 | |
ModularRealmAuthenticator.doSingleRealmAuthentication(Realm, AuthenticationToken) line: 180 | |
ModularRealmAuthenticator.doAuthenticate(AuthenticationToken) line: 267 | |
ModularRealmAuthenticator(AbstractAuthenticator).authenticate(AuthenticationToken) line: 198 | |
DefaultWebSecurityManager(AuthenticatingSecurityManager).authenticate(AuthenticationToken) line: 106 | 调用父类login |
DefaultWebSecurityManager(DefaultSecurityManager).login(Subject, AuthenticationToken) line: 274 | 调用父类 login |
WebDelegatingSubject(DelegatingSubject).login(AuthenticationToken) line: 260 | 调用 securityManager 的 login 方法 (已配置到xml中) |
SystemController.logining(String, String, String) line: 66 | spring 调度到该controller , 从这开始,调用subject.login 传入认证token |
ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 193 |
这里循环所有filter (此刻是 shiroFilter 配置在spring bean configuration ) |
1 入口是 ShiroFilter
2 穿针引线是靠 ThreadLocal
3 先走filter ,比如:authc , perms
4 再走controller ,进行login(token)
5 login 过程中需要用到 securityManager 、 realm
实例:AdviceFilter
public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { ... boolean continueChain = preHandle(request, response); ... if (continueChain) { executeChain(request, response, chain); } ... postHandle(request, response); ... }
chain中是自己配置的过滤器
主要是在preHandle 中完成的操作,返回true 则进行下一项过滤
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception { ... for (String path : this.appliedPaths.keySet()) { // If the path does match, then pass on to the subclass implementation for specific checks //(first match 'wins'): if (pathsMatch(path, request)) { ...return isFilterChainContinued(request, response, path, config); } } //no path matched, allow the request to go through: return true; }
循环配置的所有路径,找到与当前请求路径匹配的路径,
private boolean isFilterChainContinued(ServletRequest request, ServletResponse response, String path, Object pathConfig) throws Exception { if (isEnabled(request, response, path, pathConfig)) { //isEnabled check added in 1.2 ... //The filter is enabled for this specific request, so delegate to subclass implementations //so they can decide if the request should continue through the chain or not: return onPreHandle(request, response, pathConfig); } ... //This filter is disabled for this specific request, //return 'true' immediately to indicate that the filter will not process the request //and let the request/response to continue through the filter chain: return true; }
当前请求是
((HttpServletRequest)request).getRequestURL() (java.lang.StringBuffer) http://localhost:8888/sdfs ((HttpServletRequest)request).getRequestURI() (java.lang.String) /sdfs
当前请求是/sdfs ,匹配到了 /**
如果开启了过滤器,则触发 onPreHandle ,命名方式类似onclick
public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue); }
触发后,第一步判断就是 isAccessAllowed , 如果被拒绝,则会触发 onAccessDenied (这里是逻辑或运算)
@Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { return super.isAccessAllowed(request, response, mappedValue) || (!isLoginRequest(request, response) && isPermissive(mappedValue)); }
这里super调用是要满足所有父类的逻辑
MyFilter -> AuthenticatingFilter -> AuthenticationFilter
cn.zno.xxx.MyFilter org.apache.shiro.web.filter.authc.AuthenticatingFilter org.apache.shiro.web.filter.authc.AuthenticationFilter
也就是说要依次执行下面的逻辑
1)判断是否已认证通过 Subject subject = getSubject(request, response); subject.isAuthenticated(); 2)判断是否不是登录路径 && 被允许 (!isLoginRequest(request, response) && isPermissive(mappedValue)); 3) 判断自定义逻辑
有一项被拒绝,则会触发 onAccessDenied
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { if (isLoginRequest(request, response)) { if (isLoginSubmission(request, response)) { return executeLogin(request, response); } else { //allow them to see the login page ;) return true; } } else { saveRequestAndRedirectToLogin(request, response); return false; } }
目前只有一个onAccessDenied 默认实现
首先判断当前请求地址是否是登录路径/login.html
如果是登录路径且是post提交表单,则执行认证逻辑
如果是登录路径但不是post提交表单,则穿过过滤器,到达controller,展示登录表单
如果不是登录路径则保存当前请求,然后重定向到登录表单页面 (这里不再 executeChain)
TIPS:
isAccessAllowed 和 onAccessDenied 的返回值是boolean ,返回false 代表被拦截(可以做重定向),返回true代表放行(可能到达controller)
方法内能干的3件事:
1)放行到达controller
2)登录认证
3)拦截后重定向
// 认证过滤器中都能干什么事情: // 1) 判断当前请求是否是loginUrl (isLoginRequest // 2)如果当前请求时loginUrl,判断是否是post 登录表单 (isLoginSubmission // 3) 执行登录认证 (executeLogin // 4) 重定向到登录页面 (redirectToLogin // 5) 重定向到百度页面( WebUtils.issueRedirect(req, res, "http://baidu.com", null, false);
TIPS:
什么是 mappedValue
mappedValue 是 配置的过滤器的 optional_config ,是spring数组,是在ShiroFilter 初始化时解析好的。
通过 String[] DefaultFilterChainManager.splitChainDefinition(String chainDefinition) 解析
回忆一下shiro过滤器语法
filter1[optional_config1], filter2[optional_config2], ..., filterN[optional_configN]
转成name + config
而 config 通过 shiro 自己的 StringUtils 的 split 方法转为数组,如果空就是null
而 config 转成的String[] 就是 mappedValues
protected boolean isPermissive(Object mappedValue) { if(mappedValue != null) { String[] values = (String[]) mappedValue; return Arrays.binarySearch(values, PERMISSIVE) >= 0; } return false; }
这个方法就是要看config中是否包含 "permissive"