一、原理分析

1.Shiro 的简介是:好用的 Java 安全框架,可执行身份验证、授权、密码和会话管理,它有三个核心组件:Subject, SecurityManager 和 Realm

Subject 在 Shiro 中指当前”用户”,是框架执行认证、授权功能的门户。

SecurityManager,安全管理器,所有与安全有关的操作都需要通过它,它是整个 Shiro 框架的功能核心。

  让你觉得 Shiro 的认证、授权、会话管理等这些功能的完成都是 SecurityManager 自己在干?实际上,基于责任分离的原则,SecurityManager  本身也并不完成具体的功能,它只负责需求调度,具体的功能完成都分配到具体的功能组件,比如登录认证就找登录认证组件(Authentication),授权找授权组件(Authorization),会话找会话组件(Session Manager),数据比对就找数据源组件(Realm)等等。是的,Shiro 中的 SecurityManager 就是保证应用程序启动的时候,能够创建出全局唯一的安全管理器实例,让该实例在幕后帮我们完成安全有关的认证、授权和会话管理等工作。

  安全管理器的创建是依赖于认证、授权,缓存、数据源等诸多组件的,你可以各自创建功能组件对象然后交给 SecurityManger ,配置的方式,选择很多,比如你可以通过 Spring XML 配置,也可以用 YAML 文件或者 Properties文件配置等,

Realm:领域对象,在 Shiro 和你的应用程序安全数据(比如登录的用户名、密码,用户的权限等)之间架起一座沟通的桥梁,安全管理器要验证用户身份,或者要获取用户对应的权限,是分别通过认证组件(Authentication)和授权组件(Authorization)来具体完成的,但是这两个组件要完成认证或授权的实际功能,又需要与安全有关的数据做支撑,这个时候,他们就要从 Realm 那里获取相应的用户数据进行比较以确定登录用户身份是否合法,或者从 Realm 那里得到用户相应的角色 / 权限以验证用户是否能进行某些操作操作。

二、初始化配置流程

1.准备工作,引入POM包

       <dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring-boot-web-starter</artifactId></dependency>

2.securityManager

使用DefaultWebSecurityManager,并且Realm数据源组件使用自定义的ClientRealm(从数据库中读取账号信息),SessionManager会话管理器使用自定义的MyDefaultWebSessionManager(将会话信息保存到REDIS)。

 @Bean(name = "securityManager")public DefaultWebSecurityManager securityManager(@Qualifier("sessionManager") MyDefaultWebSessionManager sessionManager,@Qualifier("clientRealm") ClientRealm clientRealm) {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();securityManager.setRealm(clientRealm);securityManager.setSessionManager(sessionManager);return securityManager;}

3.SessionManager会话管理器,从外部注入RedisSessionDAO会话存储器,SimpleCookie-COOKIE操作器(shiro自带)。

 @Bean(name = "sessionManager")public MyDefaultWebSessionManager sessionManager(@Qualifier("redisSessionDAO") RedisSessionDAO redisSessionDAO,@Qualifier("sessionIdCookie") SimpleCookie sessionIdCookie) {MyDefaultWebSessionManager myDefaultWebSessionManager = new MyDefaultWebSessionManager();myDefaultWebSessionManager.setSessionIdCookie(sessionIdCookie);myDefaultWebSessionManager.setDeleteInvalidSessions(false);myDefaultWebSessionManager.setSessionIdCookieEnabled(true);myDefaultWebSessionManager.setSessionValidationSchedulerEnabled(false);myDefaultWebSessionManager.setSessionDAO(redisSessionDAO);return myDefaultWebSessionManager;}

RedisSessionDAO 使用redis存储SESSION。

public class RedisSessionDAO extends CachingSessionDAO {private static Logger logger = LoggerFactory.getLogger(RedisSessionDAO.class);@Overrideprotected void doDelete(Session session) {if (session == null || session.getId() == null) {logger.error("session or session id is null");return;}AuthCacheFactory.PENSION_AUTH_SESSION.delete(session.getId().toString());}@Overrideprotected void doUpdate(Session session) {saveSession(session);}@Overrideprotected Serializable doCreate(Session session) {if (session == null) {logger.error("session is null");throw new UnknownSessionException("session is null");}Serializable sessionId = this.generateSessionId(session);this.assignSessionId(session, sessionId);saveSession(session);return sessionId;}@Overrideprotected Session doReadSession(Serializable sessionId) {if (sessionId == null) {logger.error("session id is null");return null;}return AuthCacheFactory.PENSION_AUTH_SESSION.getBlob(sessionId.toString(), Session.class);}private void saveSession(Session session) throws UnknownSessionException {if (session == null || session.getId() == null) {logger.error("session or session id is null");throw new UnknownSessionException("session or session id is null");}session.setTimeout(14400 * 1000);// 默认四个小时AuthCacheFactory.PENSION_AUTH_SESSION.setBlob(session.getId().toString(), session);}
}

4.MyDefaultWebSessionManager

DefaultWebSessionManager为默认的web应用Session管理器,主要是涉及到Session和Cookie

具备了SessionIdCookie、SessionIdCookie启用开关,涉及到的行为:添加、删除SessionId到Cookie、读取Cookie获得SessionId。

其实就是

// 给浏览器添加Cookie:response.addHeader("Set-Cookie", "JSESESSIONIDCOOKIE=UUID666777888; Path=/; HttpOnly");

MyDefaultWebSessionManager为对HEADER要自定义写入和读取方式,如根据不同的HOST写入不同的HEADER。

public class MyDefaultWebSessionManager extends DefaultWebSessionManager {private static final Logger log = LoggerFactory.getLogger(MyDefaultWebSessionManager.class);@Overrideprotected void onStart(Session session, SessionContext context) {if (!WebUtils.isHttp(context)) {log.debug("SessionContext argument is not HTTP compatible or does not have an HTTP request/response "+ "pair. No session ID cookie will be set.");return;}HttpServletRequest request = WebUtils.getHttpRequest(context);HttpServletResponse response = WebUtils.getHttpResponse(context);if (isSessionIdCookieEnabled()) {Serializable sessionId = session.getId();storeCookieSessionId(sessionId, request, response);} else {log.debug("Session ID cookie is disabled.  No cookie has been set for new session with id {}",session.getId());}request.removeAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE);request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_IS_NEW, Boolean.TRUE);}private void storeCookieSessionId(Serializable sessionId, HttpServletRequest request,HttpServletResponse response) {if (sessionId == null) {String msg = "sessionId cannot be null when persisting for subsequent requests.";throw new IllegalArgumentException(msg);}String host = MySimpleCookie.getHost(request);log.info("请求头host为:{}",host);request.getHeader("Host");String pre = "";if (host.startsWith("pre.")) {pre = "pre_";}Cookie template = getSessionIdCookie();synchronized (MyDefaultWebSessionManager.class) {// set prefixString cookieName = !"".equals(pre) && template.getName().startsWith(pre) ? template.getName(): pre + template.getName();template.setName(cookieName);}setSessionIdCookie(template);Cookie cookie = new MySimpleCookie(template);String idString = sessionId.toString();
//      String domain = "";
//      if (host.split(":")[0].equals("localhost")){
//          domain = "localhost";
//      }else {
//          String[] split = host.split(".");
//          domain=split[1]+"."+split[2];
//      }
//      cookie.setDomain(domain);cookie.setDomain(null);cookie.setValue(idString);cookie.saveTo(request, response);log.trace("Set session ID cookie for session with id {}", idString);}
}
5.MySimpleCookie cookie会话保存器。
public class MySimpleCookie extends SimpleCookie {private static final transient Logger log = LoggerFactory.getLogger(MySimpleCookie.class);public MySimpleCookie(String name) {super(name);}public MySimpleCookie(Cookie cookie) {setName(cookie.getName());setValue(cookie.getValue());setComment(cookie.getComment());setDomain(cookie.getDomain());setPath(cookie.getPath());setMaxAge(Math.max(DEFAULT_MAX_AGE, cookie.getMaxAge()));setVersion(Math.max(DEFAULT_VERSION, cookie.getVersion()));setSecure(cookie.isSecure());setHttpOnly(cookie.isHttpOnly());}@Overridepublic void saveTo(HttpServletRequest request, HttpServletResponse response) {String name = getName();String value = getValue();String comment = getComment();String domain = getDomain();Enumeration<String> e = request.getHeaderNames();while (e.hasMoreElements()) {String name1 = (String) e.nextElement();if ("host".equalsIgnoreCase(name1)) {String value1 = request.getHeader(name1);log.debug(name1 + ":" + value1);}}String host = getHost(request);log.debug("host:" + host);if (!StringUtils.isEmpty(host)) {if (host.indexOf("default.coding.clife.net") != -1) {domain = ".clife.cn";}else if (host.indexOf("lz-itest.coding.clife.net") != -1 || host.indexOf("lz-dev.coding.clife.net") != -1){domain = ".clife.net";}}log.debug("domain:" + domain);String path = ROOT_PATH;int maxAge = getMaxAge();int version = getVersion();boolean secure = isSecure();boolean httpOnly = isHttpOnly();addCookieHeader(response, name, value, comment, domain, path, maxAge, version, secure, httpOnly);}private void addCookieHeader(HttpServletResponse response, String name, String value, String comment, String domain,String path, int maxAge, int version, boolean secure, boolean httpOnly) {String headerValue = buildHeaderValue(name, value, comment, domain, path, maxAge, version, secure, httpOnly);response.addHeader(COOKIE_HEADER_NAME, headerValue);if (log.isDebugEnabled()) {log.debug("Added HttpServletResponse Cookie [{}]", headerValue);}}public static String getHost(HttpServletRequest request) {String host = request.getHeader("Host");if (host == null || host.equals("")) {host = request.getHeader("host");}return host;}}

6.权限过滤器配置,控制认证过滤器,和角色过滤器,三大过滤器配置,用来判断URL过滤,登录,和角色权限。

shiroFilterFactoryBean实现FactoryBean,说明它是ShiroFilter的工厂类。它是怎么初始化让Shiro能很好的工作的呢,该类的入口方法是createInstance(),该方法实现了几个功能 
6.1.创建了一个过滤器管理类FilterChainManager,该类主要管理shiro里的过滤器,里面有2个重要的属性 
6.1.1 filters:管理全部过滤器,包括默认的关于身份验证和权限验证的过滤器,这些过滤器分为两组,一组是认证过滤器,有anon,authcBasic,auchc,user,一组是授权过滤器,有perms,roles,ssl,rest,port。同时也包含在xml里filters配置的自定义过滤器。在其它地方使用时都是从过滤器管理类里filters里拿的。且过滤器是单例的,整个Shiro框架只维护每种类型过滤器的单例。 
6.1.2 filterChains:过滤链。它是我们重点关注的东西,是一个Map对象,其中key就是我们请求的url,value是一个NamedFilterList对象,里面存放的是与url对应的一系列过滤器。这后面会详细讲解。

6.2.ShiroFilterFactoryBean工程FILTER,为构建ShiroFilterFactoryBean.SpringShiroFilter

查看初始化FILTER过程。

ShiroFilterFactoryBean
protected AbstractShiroFilter createInstance() throws Exception {log.debug("Creating Shiro Filter instance.");SecurityManager securityManager = this.getSecurityManager();String msg;if (securityManager == null) {msg = "SecurityManager property must be set.";throw new BeanInitializationException(msg);} else if (!(securityManager instanceof WebSecurityManager)) {msg = "The security manager does not implement the WebSecurityManager interface.";throw new BeanInitializationException(msg);} else {FilterChainManager manager = this.createFilterChainManager();PathMatchingFilterChainResolver chainResolver = new PathMatchingFilterChainResolver();chainResolver.setFilterChainManager(manager);return new ShiroFilterFactoryBean.SpringShiroFilter((WebSecurityManager)securityManager, chainResolver);}}

6.3.将过滤器管理类设置到PathMatchingFilterChainResolver类里,该类负责路径和过滤器链的解析与匹配。根据url找到过滤器链。

我们以如下的xml配置为例讲解,如下:

<!-- Shiro的Web过滤器 --><bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"><property name="securityManager" ref="securityManager"/><property name="loginUrl" value="/login.jsp"/><property name="filters"><util:map><entry key="authc" value-ref="formAuthenticationFilter"/><entry key="logout" value-ref="logoutFilter" /></util:map></property><property name="filterChainDefinitionMap" ref="filterChainDefinitionMap" /></bean><bean id="filterChainDefinitionMap"class="com.haedrig.shiro.spring.ChainDefinitionSectionMetaSource"><!-- 默认的连接配置 --><property name="filterChainDefinitions"><value>/login.jsp = anon/login = authc/logout = logout/authenticated = authc/views/**=anon/** = authc, perms</value></property></bean>
 @Bean(name = "shiroFilterFactoryBean")public ShiroFilterFactoryBean shirFilter(@Qualifier("securityManager") SecurityManager securityManager) {ClientShiroFilterFactoryBean shiroFilterFactoryBean = new ClientShiroFilterFactoryBean();shiroFilterFactoryBean.setSecurityManager(securityManager);// 自定义拦截器Map<String, Filter> filters = new LinkedHashMap<>();filters.put("authRoles", getAuthRolesFilter());filters.put("authc", getClientAuthenticationFilter());shiroFilterFactoryBean.setFilters(filters);shiroFilterFactoryBean.setFilterChainDefinitionsStr(filterChainDefinitions);shiroFilterFactoryBean.setLoginUrl(domainName + frontLoginUrl);shiroFilterFactoryBean.setUnauthorizedUrl(domainName + frontLoginUrl);return shiroFilterFactoryBean;}public ClientAuthenticationFilter getClientAuthenticationFilter() {ClientAuthenticationFilter clientAuthenticationFilter = new ClientAuthenticationFilter();return clientAuthenticationFilter;}@Beanpublic AuthRolesFilter getAuthRolesFilter() {AuthRolesFilter authRolesFilter = new AuthRolesFilter();return authRolesFilter;}
protected FilterChainManager createFilterChainManager() {DefaultFilterChainManager manager = new DefaultFilterChainManager();//获得shiro默认的过滤器,同时将xml里配置的loginUrl,successUrl,unauthorizedUrl设置到不同过滤器里Map<String, Filter> defaultFilters = manager.getFilters();//apply global settings if necessary:for (Filter filter : defaultFilters.values()) {applyGlobalPropertiesIfNecessary(filter);}//将自定义过滤器添加到filters里让管理器管理,注意,filters是Map,所以key同名的会被覆盖。//Apply the acquired and/or configured filters:Map<String, Filter> filters = getFilters();if (!CollectionUtils.isEmpty(filters)) {for (Map.Entry<String, Filter> entry : filters.entrySet()) {String name = entry.getKey();Filter filter = entry.getValue();applyGlobalPropertiesIfNecessary(filter);if (filter instanceof Nameable) {((Nameable) filter).setName(name);}//'init' argument is false, since Spring-configured filters should be initialized//in Spring (i.e. 'init-method=blah') or implement InitializingBean:manager.addFilter(name, filter, false);}}//build up the chains://获得xml里filterChainDefinitions配置Map<String, String> chains = getFilterChainDefinitionMap();if (!CollectionUtils.isEmpty(chains)) {for (Map.Entry<String, String> entry : chains.entrySet()) {String url = entry.getKey();String chainDefinition = entry.getValue();//根据url创建url对应的过滤链**重点**重点**重点,每个配置过的url都对应一个过滤链manager.createChain(url, chainDefinition);}}return manager;
}

6.4 讲解: 
6.4.1.applyGlobalPropertiesIfNecessary(filter);方法是将配置的loginUrl,successUrl,unauthorizedUrl设置到不同过器里。其中loginUrl赋值到所以继承自AccessControlFilter的过滤器里,successUrl赋值到所以继承自AuthenticationFilter的过滤器里,unauthorizedUrl赋值到所以继承自AuthorizationFilter的过滤器里。 
当然其实过滤器可以自己设置loginUrl,successUrl,unauthorizedUrl,自定义赋值的也覆盖全局指定的。

6.4.2.读取filters配置的自定义过滤器,将它们纳入到过滤器管理器里。

6.4.3.最后读取filterChainDefinitions配置,根据配置设置每个url对应的过滤链,filterChains保存这些配置,它是一个Map集合,key就是url,value就是过滤器组成的NamedFilterList集合。其实当请求过来时,解析出请求路径,会从filterChains里找到url对应的过滤链,按过滤器的策略一个一个执行下去。 
同时会调用PathMatchingFilter的processPathConfig()方法做些赋值操作。下面会专门讲将从PathMatchingFilter开始工作的过程。

6.4.4.一步一步分析下来过滤器管理器里的过滤链filterChains如下:

{/login.jsp=org.apache.shiro.web.filter.mgt.SimpleNamedFilterList@6ed97422,/login=org.apache.shiro.web.filter.mgt.SimpleNamedFilterList@592dbd8,/logout=org.apache.shiro.web.filter.mgt.SimpleNamedFilterList@1141badb,/authenticated=org.apache.shiro.web.filter.mgt.SimpleNamedFilterList@2d1b876e,/views/**=org.apache.shiro.web.filter.mgt.SimpleNamedFilterList@31106774,/**=org.apache.shiro.web.filter.mgt.SimpleNamedFilterList@14cd7d10,/index.jsp=org.apache.shiro.web.filter.mgt.SimpleNamedFilterList@712384a,/admin.jsp=org.apache.shiro.web.filter.mgt.SimpleNamedFilterList@7643ef31
可以看到每个url对应一个SimpleNamedFilterList对象,SimpleNamedFilterList是个List子类对象,保存的是过滤器集。6.4.5.查看FILTER初始化过程。

三、运行流程

1.登陆认证控制器接口

 UsernamePasswordToken token = new UsernamePasswordToken(username, password);try {// 先销毁之前会话if (subject.isAuthenticated()) {log.debug("to log out first....");subject.getSession().stop(); // 主动调用退出}// 登录,即身份验证subject.login(token);SecurityUtils.getSubject().getSession().removeAttribute(username);saveUserToSession(username);return JsonResult.success(map);} catch (UnknownAccountException e) {ResultStatus status = new ResultStatus(GlobalResultStatus.USER_NOT_EXIST.getCode(), "用户名或者密码错误!");return JsonResult.fail(status);} catch (IncorrectCredentialsException e) {// 登录错误Integer errorCount = AccountCacheFactory.PENSION_LOGIN.get(username, Integer.class);return JsonResult.fail(new ResultStatus(errorCount, "用户名/密码错误"));} catch (LockedAccountException e) {log.error("user login error ", e);return JsonResult.fail(GlobalResultStatus.USER_LOCKED);} catch (DisabledAccountException e) {ResultStatus status = new ResultStatus(GlobalResultStatus.ACCOUNT_NOT_INVITE.getCode(), "用户名或者密码错误!");return JsonResult.fail(status);} catch (ExcessiveAttemptsException e) {ResultStatus status = new ResultStatus(GlobalResultStatus.USER_LOGIN_FAILED.getCode(), e.getMessage());return JsonResult.fail(status);} catch (AuthenticationException e) {log.error("user Login auth error", e);return JsonResult.fail(GlobalResultStatus.ERROR);} catch (Exception e) {log.error("user Login error", e);ResultStatus status = new ResultStatus(GlobalResultStatus.ERROR.getCode(),"账号异常,请联系管理员(shengli@szhittech.com)!");return JsonResult.fail(status);}

2.ClientRealm  认证账号数据源

public class ClientRealm extends AuthorizingRealm {@Reference(check = false)private AuthUserService authUserService;@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {log.info("....doGetAuthorizationInfo");return null;}/*** 登录校验*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {log.info("登录校验开始....doGetAuthenticationInfo");String username = (String) token.getPrincipal();AuthUser user = new AuthUser();user.setUserName(username);user.setStatus(UserContant.ONE);AuthUser userResult = authUserService.get(user);if (userResult == null) {throw new UnknownAccountException();}if (userResult.getStatus() == 0 || userResult.getStatus().equals(UserContant.TWO)) {throw new LockedAccountException("账号被冻结或失效");}SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(userResult.getUserName(), // 用户名userResult.getPassword(), // 密码ByteSource.Util.bytes(userResult.getCredentialsSalt()), // salt=username+saltgetName() // realm name);return authenticationInfo;}
}

注意此时只是根据客户端传入的登录凭证信息查找数据源的账号信息,还未进行校验。

3.密码校验

RetryLimitHashedCredentialsMatcher

前端传入的密码,先取密码的UTF8字节流进行MD5,然后BASE64传入。

数据库中存储的密码就是MD5值,所以对前端参数进行BASE64解码校验是否相等即可。

public class RetryLimitHashedCredentialsMatcher extends HashedCredentialsMatcher {@Overridepublic boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {String username = (String) token.getPrincipal();log.info("username:{}", username);UsernamePasswordToken upToken = (UsernamePasswordToken) token;String password = String.valueOf(upToken.getPassword());String passwordMd5 = new String(Base64.decodeBase64(password));String dbpassword = (String) info.getCredentials();Integer errorCount = AccountCacheFactory.PENSION_LOGIN.get(username, Integer.class);if (errorCount == null){errorCount = 0;}if (StringUtil.isEmpty(dbpassword)) {return false;}return dbpassword.equals(passwordMd5);}
}

在登录第一次创建session时,会往 客户端的cookie中写入key名称为pension的sessionId.

4.鉴权FILTER,自定义未登录返回结果。

ClientAuthenticationFilter检测是否登陆。未登陆返回401

@Slf4j
public class ClientAuthenticationFilter extends AuthenticationFilter {/*** 处理认证失败时调用(未登录时)*/@Overrideprotected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {HttpServletRequest httpRequest = (HttpServletRequest) request;HttpServletResponse httpServletResponse = (HttpServletResponse) response;String xRequestedwith = httpRequest.getHeader("X-Requested-With");log.info("xRequestedwith:{}", xRequestedwith);// ajax请求if ("XMLHttpRequest".equalsIgnoreCase(xRequestedwith)) {httpServletResponse.setCharacterEncoding("UTF-8");httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);PrintWriter out = httpServletResponse.getWriter();out.write("{\"code\":" + GlobalResultStatus.USER_LOGIN_SESSION_TIME_OUT.getCode() + ",\"msg\":\""+ GlobalResultStatus.USER_LOGIN_SESSION_TIME_OUT.getMsg() + "\",\"loginUrl\":\"" + getLoginUrl()+ "\"}");out.flush();out.close();} else {httpServletResponse.setCharacterEncoding("UTF-8");httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE);PrintWriter out = httpServletResponse.getWriter();out.write("{\"code\":" + GlobalResultStatus.USER_LOGIN_SESSION_TIME_OUT.getCode() + ",\"msg\":\""+ GlobalResultStatus.USER_LOGIN_SESSION_TIME_OUT.getMsg() + "\",\"loginUrl\":\"" + getLoginUrl()+ "\"}");out.flush();out.close();//redirectToLogin(request, response);}return false;}}

4.1.鉴权FILTER执行过程

4.2. 我们查看FILTER拦截正常请求的流程。

AbstractShiroFilter首先会从请求头信息中生成subject主题登录对象进行构造,然后再进行过滤器责任链执行。

我们知道DelegatingFilterProxy过滤器的代理类来实现拦截的,任何请求都会先经过shiro先过滤,直到成功才会执行javaweb本身的过滤器。 
一个请求过来时,先到达AbstractShiroFilter.executeChain()方法,去根据request解析出来的url找到对应的过滤链,然后执行过滤器链。 
executeChain()方法如下:

protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)throws IOException, ServletException {//得到过滤器链FilterChain chain = getExecutionChain(request, response, origChain);chain.doFilter(request, response);
进入getExecutionChain()方法:
protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) {FilterChain chain = origChain;//resolver即是前面说的PathMatchingFilterChainResolver对象。里面保存有过滤器管理器实例FilterChainResolver resolver = getFilterChainResolver();if (resolver == null) {log.debug("No FilterChainResolver configured.  Returning original FilterChain.");return origChain;}//进入PathMatchingFilterChainResolver对象里,根据解析出来的requestURI找到对应的过滤器链并返回FilterChain resolved = resolver.getChain(request, response, origChain);if (resolved != null) {log.trace("Resolved a configured FilterChain for the current request.");chain = resolved;} else {log.trace("No FilterChain configured for the current request.  Using the default.");}return chain;
从这里面可以看到/**是找到了三个内部filter.最终执行authc的第一个filter的onPreHandler如果isAccessAllowed返回TRUE,则直接返回TRUE,则可以执行FILTER过滤器链的下一个过滤器。如果isAccessAllowed返回FALSE,则请求onAccessDenied方法,如果它返回TRUE,则也会通过,如果返回FALSE,则直接终止过滤器链的请求,确认未登录。
AccessControlFilter
public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);}

protected Subject getSubject(ServletRequest request, ServletResponse response) {return SecurityUtils.getSubject();
}

正常登陆下, isAccessAllowed会去获取当前的subject是否存在,并且已认证成功,和当前认证的主体为正常的账号。

4.3.我们查看FILTER拦截携带非法TOKEN请求的流程。

由于是异常未登录的TOKEN,所以获取到的subject为未认证。

isAccessAllowed返回FALSE,会执行onAccessDenied方法。

onAccessDenied方法自定义向RESPONSE写入异常结果,并返回FALSE.

由于上面的方法最终返回FALSE,所以整个过滤器执行链不再向下执行,终止执行,返回结果。

5.AuthRolesFilter过滤器判断是否有相应接口URL的访问权限。

5.1 登陆成功后,向当前session存入一个sessionUser对象,里面包含了当前用户能访问的权限列表。

5.2 正常业务接口访问时,则从session中获取sessionUser对象,对比是否有权限,没有则返回FALSE,终止过滤器链访问。

5.3 在isAccessAllowed返回FALSE后,在执行onAccessDenied会自定义返回值。

如果 isAccessAllowed返回TRUE,则即使执行onAccessDenied,过滤器链也会向下执行。

6.我们补充一个,从HTTP请求头的COOKIE中获取sessionId以及生成 subject主体的流程。

6.1 这是在abstractShiroFilter拦截前,先生成subject的流程,首先从请求信息中构建session,然后再构建Principals鉴权信息,最后再构建subject.

6.2 我们查看构建session的过程,其实就是从HTTP请求信息中先构建DefaultSessionManager中生成的session.

6.3 我们可以看到,其实就是从HTTP的REQUEST的请求头中寻找cookie的值列表,并且找到

SimpleCookie的name属性对应的cookie值。
SimpleCookie
private static javax.servlet.http.Cookie getCookie(HttpServletRequest request, String cookieName) {javax.servlet.http.Cookie cookies[] = request.getCookies();if (cookies != null) {for (javax.servlet.http.Cookie cookie : cookies) {if (cookie.getName().equals(cookieName)) {return cookie;}}}return null;}

6.4 现在获取到sessionId,我们现在看构建session的过程。其实就是人sessionDao(RedisSessionDao)中获取sessionId为COOKIE的值session内容。

DefaultSessionManager   protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException {Serializable sessionId = getSessionId(sessionKey);if (sessionId == null) {log.debug("Unable to resolve session ID from SessionKey [{}].  Returning null to indicate a " +"session could not be found.", sessionKey);return null;}Session s = retrieveSessionFromDataSource(sessionId);if (s == null) {//session ID was provided, meaning one is expected to be found, but we couldn't find one:String msg = "Could not find session with ID [" + sessionId + "]";throw new UnknownSessionException(msg);}return s;}protected Session retrieveSessionFromDataSource(Serializable sessionId) throws UnknownSessionException {return sessionDAO.readSession(sessionId);}

6.5 最后从redisSessionDao中获取Session对象的所有BLOG数据。

我们查看SESSION中的数据内容

6.6 接下来我们开始来构建认证信息principals。resolvePrincipals

DefaultSubjectContextpublic PrincipalCollection resolvePrincipals() {PrincipalCollection principals = getPrincipals();if (isEmpty(principals)) {//check to see if they were just authenticated:AuthenticationInfo info = getAuthenticationInfo();if (info != null) {principals = info.getPrincipals();}}if (isEmpty(principals)) {Subject subject = getSubject();if (subject != null) {principals = subject.getPrincipals();}}if (isEmpty(principals)) {//try the session:Session session = resolveSession();if (session != null) {principals = (PrincipalCollection) session.getAttribute(PRINCIPALS_SESSION_KEY);}}return principals;}

我们可以看到,当前面没有解析过认证对象时,会从session中的PRINCIPALS_SESSION_KEY属性去取认证对象。

可以看到在session的属性中存储了当前登录的用户信息。

6.7 最后一步开始构建subject主题对象。

    DefaultWebSubjectFactory
public Subject createSubject(SubjectContext context) {//SHIRO-646//Check if the existing subject is NOT a WebSubject. If it isn't, then call super.createSubject instead.//Creating a WebSubject from a non-web Subject will cause the ServletRequest and ServletResponse to be null, which wil fail when creating a session.boolean isNotBasedOnWebSubject = context.getSubject() != null && !(context.getSubject() instanceof WebSubject);if (!(context instanceof WebSubjectContext) || isNotBasedOnWebSubject) {return super.createSubject(context);}WebSubjectContext wsc = (WebSubjectContext) context;SecurityManager securityManager = wsc.resolveSecurityManager();Session session = wsc.resolveSession();boolean sessionEnabled = wsc.isSessionCreationEnabled();PrincipalCollection principals = wsc.resolvePrincipals();boolean authenticated = wsc.resolveAuthenticated();String host = wsc.resolveHost();ServletRequest request = wsc.resolveServletRequest();ServletResponse response = wsc.resolveServletResponse();return new WebDelegatingSubject(principals, authenticated, host, session, sessionEnabled,request, response, securityManager);}

这里读到的 principals 跟上面的一样,从SESSION的属性中获取值。为当前登录用户。

6.8 验证是否认证成功

DefaultSubjectContextpublic boolean resolveAuthenticated() {Boolean authc = getTypedValue(AUTHENTICATED, Boolean.class);if (authc == null) {//see if there is an AuthenticationInfo object.  If so, the very presence of one indicates a successful//authentication attempt:AuthenticationInfo info = getAuthenticationInfo();authc = info != null;}if (!authc) {//fall back to a session check:Session session = resolveSession();if (session != null) {Boolean sessionAuthc = (Boolean) session.getAttribute(AUTHENTICATED_SESSION_KEY);authc = sessionAuthc != null && sessionAuthc;}}return authc;}

可以看到SESSION中 AUTHENTICATED_SESSION_KEY的值为TRUE,即为认证成功。

6.9 至此可以看subject所需的属性字段全部构建完成。

6.10 至此subject已创建完成,接下来就是进行filter责任链的执行,获取subject,判断是否已认证成功,和登录用户信息,来判断是否成功,还是返回未登录。

Shiro原理及源码分析相关推荐

  1. ConcurrentHashMap实现原理及源码分析

    ConcurrentHashMap是Java并发包中提供的一个线程安全且高效的HashMap实现(若对HashMap的实现原理还不甚了解,可参考我的另一篇文章HashMap实现原理及源码分析),Con ...

  2. concurrenthashmap_ConcurrentHashMap实现原理及源码分析

    ConcurrentHashMap是Java并发包中提供的一个线程安全且高效的HashMap实现(若对HashMap的实现原理还不甚了解,可参考我的另一篇文章HashMap实现原理及源码分析),Con ...

  3. SIFT原理与源码分析:DoG尺度空间构造

    <SIFT原理与源码分析>系列文章索引:http://blog.csdn.net/xiaowei_cqu/article/details/8069548 尺度空间理论 自然界中的物体随着观 ...

  4. 深入理解Spark 2.1 Core (十二):TimSort 的原理与源码分析

    在博文<深入理解Spark 2.1 Core (十):Shuffle Map 端的原理与源码分析 >中我们提到了: 使用Sort等对数据进行排序,其中用到了TimSort 这篇博文我们就来 ...

  5. 深入理解Spark 2.1 Core (十一):Shuffle Reduce 端的原理与源码分析

    我们曾经在<深入理解Spark 2.1 Core (一):RDD的原理与源码分析 >讲解过: 为了有效地实现容错,RDD提供了一种高度受限的共享内存,即RDD是只读的,并且只能通过其他RD ...

  6. 深入理解Spark 2.1 Core (十):Shuffle Map 端的原理与源码分析

    在上一篇<深入理解Spark 2.1 Core (九):迭代计算和Shuffle的原理与源码分析>提到经过迭代计算后, SortShuffleWriter.write中: // 根据排序方 ...

  7. 深入理解Spark 2.1 Core (八):Standalone模式容错及HA的原理与源码分析

    第五.第六.第七篇博文,我们讲解了Standalone模式集群是如何启动的,一个App起来了后,集群是如何分配资源,Worker启动Executor的,Task来是如何执行它,执行得到的结果如何处理, ...

  8. 深入理解Spark 2.1 Core (七):Standalone模式任务执行的原理与源码分析

    这篇博文,我们就来讲讲Executor启动后,是如何在Executor上执行Task的,以及其后续处理. 执行Task 我们在<深入理解Spark 2.1 Core (三):任务调度器的原理与源 ...

  9. 深入理解Spark 2.1 Core (六):Standalone模式运行的原理与源码分析

    我们讲到了如何启动Master和Worker,还讲到了如何回收资源.但是,我们没有将AppClient是如何启动的,其实它们的启动也涉及到了资源是如何调度的.这篇博文,我们就来讲一下AppClient ...

最新文章

  1. Java 如何优雅的实现时间控制
  2. 上海市经济信息化委关于支持新建互联网数据中心项目用能指标的通知
  3. 从营销手段到商业新基建,“以旧换新”还有多少价值等待挖掘?
  4. 云炬随笔20210930
  5. 数据结构与算法之反转单向链表和双向链表
  6. Python中查找包含它的列表元素的索引,index报错!!!
  7. vb实时错误6 溢出_java内存溢出系列(6): Out of swap space?
  8. myeclipse 怎么安装与激活
  9. python 哪些项目_哪些Python开源项目比较好
  10. mvc源码解读(10)-ParameterDescriptor方法Action方法的参数描述对象
  11. mysql rds 定时执行_RDS下执行SQL小脚本
  12. 2021全国计算机二级知识点,2021年度Dlypeq全国计算机等级考试二级公共基础知识点总结.doc...
  13. Python实现本地翻译API
  14. win10用账户登录计算机,图文详解让你的win10系统实现微软账户自动登录-系统操作与应用 -亦是美网络...
  15. 曾在国内外5家大厂做数据库工程师,这是他给出的5大数据库趋势预测
  16. 8.千峰教育os与窗口控制与内存修改与语言----自制随堂笔记
  17. css之calc,初探CSS3中的calc()功能
  18. Dubbo的基本使用
  19. 前后端交互之——AJAX提交
  20. Appium H5自动化测试

热门文章

  1. 排序算法之shell排序
  2. Apache HttpClient : Http Cookies
  3. html 设置readonly属性,HTML: input readonly 属性
  4. 模电——MOS管接下拉电阻
  5. PHP设计模式—外观模式
  6. 线程池中阻塞队列的作用?为什么是先添加列队而不是先 创建最大线程?
  7. 函数返回数组指针的几种方法
  8. oracle纸质许可证,收不到纸质证书或者有任何问题都可以请求oracle帮助
  9. JS 垃圾回收机制解析
  10. Pythonxy Spyder Errors