上一篇已经分析了shiro的入口filter是SpringShiroFilter, 那么它的doFilter在哪儿呢?

我们看到它的直接父类AbstractShrioFilter继承了OncePerRequestFilter类,该类是shiro内置的大部分filter的父类(抽像公共部分),在该类中定义了doFilter方法

OncePerRequestFilte

public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
    //当前过滤器的名字
String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();
     //如果该过滤器执行过,那么将不执行同一个名字的过滤器 直接执行过滤链中的下一个过滤器
if ( request.getAttribute(alreadyFilteredAttributeName) != null ) {
filterChain.doFilter(request, response);
} else if (!isEnabled(request, response) || shouldNotFilter(request) ) {
        //如果当前过滤器设置了enabled属性为false,则不执行,直接执行过滤链中的下一个过滤器
filterChain.doFilter(request, response);
} else {
//标志当前过滤器已经执行过
request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE);
try {
          //1、 核心方法
doFilterInternal(request, response, filterChain);
} finally {
//过滤链执行完毕后,清空request中的过滤链执行记录
request.removeAttribute(alreadyFilteredAttributeName);
}
}
}

由于我们是通过SpringShiroFilter拦截进来的那么会调用AbstractShrioFilter中的doFilterInternal

AbstractShrioFilter

protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain)
throws ServletException, IOException { //封装容器的request和response为shiro自己的 其中在request中标识了当前不为servlet容器的session (在创建session时会用到servlet容器调用getSession()时 )
final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain);
final ServletResponse response = prepareServletResponse(request, servletResponse, chain);
//2、 创建subject(可以看出每次请求都会创建一个Subject对象)
final Subject subject = createSubject(request, response); //执行过滤链
subject.execute(new Callable() {
public Object call() throws Exception {
updateSessionLastAccessTime(request, response); //修改session的最后活动时间
executeChain(request, response, chain); //执行过滤链
return null;
}
});
}
//创建subject对象
protected WebSubject createSubject(ServletRequest request, ServletResponse response) {
return new WebSubject.Builder(getSecurityManager(), request, response).buildWebSubject();
}
//7、执行过滤链
protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain)
throws IOException, ServletException {
//获取当前请求对应的过滤链
FilterChain chain = getExecutionChain(request, response, origChain);
chain.doFilter(request, response);
}

WebSubject

//3、
public Builder(SecurityManager securityManager, ServletRequest request, ServletResponse response) {
//每次都创建subject上下文(subjectContext), 并设置securityManager对象
super(securityManager);
//将request和response添加到subject上下文(subjectContext), 即上面创建的对象
setRequest(request);
setResponse(response);
}
//4、创建
public WebSubject buildWebSubject() {
Subject subject = super.buildSubject();
return (WebSubject) subject;
}

Subject


//5、调用DefaultSecurityManager的createSubject方法
public Subject buildSubject() {
  return this.securityManager.createSubject(this.subjectContext);
}

DefaultSecurityManager

// 6
public Subject createSubject(SubjectContext subjectContext) {
//web的subjectContext时,会重新创建一个新的,其他的(ini等),只是copy
SubjectContext context = copy(subjectContext); //验证是否subject上下文中有securityMangary对象,如果没有创建一个
context = ensureSecurityManager(context); //将session放入subjectContext 该session会从cookie或者rul上带的JSESSIONID(默认) 注意:第一次访问项目来到这儿没有session
context = resolveSession(context); //校验用户登陆信息, 并放入context, 如果subject,session,和授权认证AuthenticationInfo中都没有,将会从rememberMeManager(cookie)中获取
   //注意:第一次访问项目来到这儿没有这些信息
context = resolvePrincipals(context); //创建一个WebDelegatingSubject对象
Subject subject = doCreateSubject(context); //保存当前认证的用户信息(一般是用户名)在session里,并标记当前用户已经被认证过 为了下次remember使用
save(subject); return subject;
}
// 从context中获取session
protected SubjectContext resolveSession(SubjectContext context) {
Session session = resolveContextSession(context);
if (session != null) {
context.setSession(session);
}
return context;
}
protected Session resolveContextSession(SubjectContext context) throws InvalidSessionException {
//调用的下面子类DefaultWebSecurityManager的方法
SessionKey key = getSessionKey(context);
if (key != null) {
//调用 SessionsSecurityManager#getSession
return getSession(key);
}
return null;
}
//登陆
public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info;
try {
info = authenticate(token);
} catch (AuthenticationException ae) {
onFailedLogin(token, ae, subject);
}
Subject loggedIn = createSubject(token, info, subject);
//登陆成功后 根据配置的"记住我" 保存认证信息
onSuccessfulLogin(token, info, loggedIn);
return loggedIn;
} protected void onSuccessfulLogin(AuthenticationToken token, AuthenticationInfo info, Subject subject) {
rememberMeSuccessfulLogin(token, info, subject);
}
protected void rememberMeSuccessfulLogin(AuthenticationToken token, AuthenticationInfo info, Subject subject) {
//获取 rememberMeManager管理器
RememberMeManager rmm = getRememberMeManager();
rmm.onSuccessfulLogin(subject, token, info);
}

 DefaultWebSecurityManager

//创建sessionKey
@Override
protected SessionKey getSessionKey(SubjectContext context) {
//从context中获取sessonId和request,response
if (WebUtils.isWeb(context)) {
Serializable sessionId = context.getSessionId();
ServletRequest request = WebUtils.getRequest(context);
ServletResponse response = WebUtils.getResponse(context);
return new WebSessionKey(sessionId, request, response);
} else {
...
}
}
// 第一次调用时 getSession方法最后会调用到这儿来 返回null
protected Session retrieveSession(SessionKey sessionKey) throws UnknownSessionException {
Serializable sessionId = getSessionId(sessionKey);
if (sessionId == null) {
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;
}

现在开始执行请求路径对应的过滤器

由于过滤链中的过滤器也是OncePerRequestFilte的子类,继续走OncePerRequestFilte#doFilter方法 然后会调用第一步doFilterInternal方法

  我们自定义的方法一般也是继承了AdviceFilter过滤器

AdviceFilter

public void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)
throws ServletException, IOException { Exception exception = null;
try {
       //8、执行前置方法
boolean continueChain = preHandle(request, response);
       if (continueChain) {
executeChain(request, response, chain);
} postHandle(request, response);
if (log.isTraceEnabled()) {
log.trace("Successfully invoked postHandle method");
} } catch (Exception e) {
exception = e;
} finally {
cleanup(request, response, exception);
}
}

AccessControlFilter

public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
//9、是登陆的rul或者已经认证过 否则重定向到登陆页面
return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
}

UserFilter

(这里以user过滤器为例,如果没有认证过,直接重定向到登陆url)

该过滤器重写了这两个方法

//10、判断是否认证过
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
  //判断当前请求的路径是否为当前过滤器配置的登陆路径(登陆不需要任何权限,返回true)
if (isLoginRequest(request, response)) {
return true;
} else {
    //判断是否已经认证过
Subject subject = getSubject(request, response);
return subject.getPrincipal() != null;
}
}
//11、返回false
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
  //重定向到登陆rul
saveRequestAndRedirectToLogin(request, response);
return false;
}
//12、
protected void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
//调用webUtils的方法,将当前请求失败的的信息保存起来(便于下次认证成功后直接重定向到该路径)
saveRequest(request);
//重定向的时候会生成session(shrio的)
redirectToLogin(request, response);
}

WebUtils

//保存
public static void saveRequest(ServletRequest request) {
Subject subject = SecurityUtils.getSubject();
//13、这里会创建一个 StoppingAwareProxiedSession AbstractNativeSessionManager#start是创建simpleSession并调用session监听器
  //会将sessinID存在cookie中和sessionDao中(默认时ehcache缓存,可以自己实现redis等)(在DefaultSessionManager#create(Session session)方法中)
Session session = subject.getSession();
HttpServletRequest httpRequest = toHttp(request);
//将当前目标路径的请求信息保存起来
SavedRequest savedRequest = new SavedRequest(httpRequest);
//存到session中,跳到登陆页面后登陆成功后会重定向到此次失败的路径
session.setAttribute(SAVED_REQUEST_KEY, savedRequest);
}
//重定向到savedRequet保存的路径 如果是直接访问的登陆url,则直接重定向到当前过滤器配置的登陆成功url
//成功后的重定向可是不生成session的
public static void redirectToSavedRequest(ServletRequest request, ServletResponse response, String fallbackUrl)
throws IOException {
String successUrl = null;
boolean contextRelative = true;
//从session中获取,并清空上一次失败保存的信息
SavedRequest savedRequest = WebUtils.getAndClearSavedRequest(request);
//上一次请求失败的保存的对象 而且是get请求(这里一般是直接浏览器输入的url) 如果是post请求过来的(一般是表单),直接返回目标路径
if (savedRequest != null && savedRequest.getMethod().equalsIgnoreCase(AccessControlFilter.GET_METHOD)) {
successUrl = savedRequest.getRequestUrl();
contextRelative = false;
}
  //第一次请求时,successUrl为null, 登陆成功后,有值(上一次失败的url)
if (successUrl == null) {
successUrl = fallbackUrl;
}
  //15、发出重定向
WebUtils.issueRedirect(request, response, successUrl, null, contextRelative);
} public static void issueRedirect(ServletRequest request, ServletResponse response, String url, Map queryParams, boolean contextRelative) throws IOException {
issueRedirect(request, response, url, queryParams, contextRelative, true);
}
// 会把sessionID写在rul上, 下面的内容就不带大家看了
public static void issueRedirect(ServletRequest request, ServletResponse response, String url, Map queryParams, boolean contextRelative, boolean http10Compatible) throws IOException {
RedirectView view = new RedirectView(url, contextRelative, http10Compatible);
view.renderMergedOutputModel(queryParams, toHttp(request), toHttp(response));
}

SavedRequest

// SavedRequest的构造方法
public SavedRequest(HttpServletRequest request) {
  //当前请求的方式(get|post...)
this.method = request.getMethod();
  //当前请求的参数
this.queryString = request.getQueryString();
  //当前请求失败的路径
this.requestURI = request.getRequestURI();
}

RedirectView (拼接重定向的参数请求头、url加sessionID,url编码等操作都由这儿进入)

public final void renderMergedOutputModel(
Map model, HttpServletRequest request, HttpServletResponse response) throws IOException { // Prepare name URL.
StringBuilder targetUrl = new StringBuilder();
if (this.contextRelative && getUrl().startsWith("/")) {
targetUrl.append(request.getContextPath());
}
targetUrl.append(getUrl());
     //拼接请求参数
appendQueryProperties(targetUrl, model, this.encodingScheme);
sendRedirect(request, response, targetUrl.toString(), this.http10Compatible);
}

AbstractNativeSessionManager

//14、创建session时会调用
public Session start(SessionContext context) {
//创建simpleSession
Session session = createSession(context);
//重置session时间
applyGlobalSessionTimeout(session);
  //会将sessionID存到cookie
onStart(session, context);
//调用session的Listner
notifyStart(session);
//Don't expose the EIS-tier Session object to the client-tier:
return createExposedSession(session, context);
}

那么此时一个没有授权的请求就执行完毕,现在就来到了我们的登陆界面

  登陆使用authc过滤器

FormAuthenticationFilter

和上面的过程一样,会判断是否认证,如果没有会执行onAccessDenied方法

// 这里需要该过滤器的 登陆url 和登陆所在的界面的url一样
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
//条件: 配置的该过滤器的登陆路径和请求路径相同
if (isLoginRequest(request, response)) {
//1、HttpServletRequest 2、post请求
if (isLoginSubmission(request, response)) {
return executeLogin(request, response);
} else {
//登陆页面的url 请求方式为get
return true;
}
} else {
//如果一个请求路径配置的authc过滤器,然后没有登陆直接调用,会走到这里
//重定向到登陆页面 会创建一个StoppingAwareProxiedSession类型的session 并把sessionId放在登陆页面的url上
saveRequestAndRedirectToLogin(request, response);
return false;
}
}
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
//调用 new UsernamePasswordToken(username, password, rememberMe, host);
AuthenticationToken token = createToken(request, response);
try {
Subject subject = getSubject(request, response);
subject.login(token);
return onLoginSuccess(token, subject, request, response);
} catch (AuthenticationException e) {
return onLoginFailure(token, e, request, response);
}
}
//登陆成功后 重定向到上一次重定向过来的路径或者当前过滤器的登陆路径
//可以重写该方法登陆后直接跳到当前过滤器配置的url 而不是上一次失败的url
protected boolean onLoginSuccess(AuthenticationToken token, Subject subject,
ServletRequest request, ServletResponse response) throws Exception {
//调用父类AuthenticationFilter的issueSuccessRedirect方法
issueSuccessRedirect(request, response);
//重定向后,阻止过滤连调用
return false;
}

AuthenticationFilter

protected void issueSuccessRedirect(ServletRequest request, ServletResponse response) throws Exception {
//当前过滤器配置的登陆url
WebUtils.redirectToSavedRequest(request, response, getSuccessUrl());
}

DelegatingSubject

public void login(AuthenticationToken token) throws AuthenticationException {
clearRunAsIdentitiesInternal();
//委托给securiManager登陆
Subject subject = securityManager.login(this, token);
PrincipalCollection principals;
String host = null;
if (subject instanceof DelegatingSubject) {
DelegatingSubject delegating = (DelegatingSubject) subject;
//认证信息
principals = delegating.principals;
host = delegating.host;
} else {
principals = subject.getPrincipals();
}
this.principals = principals;
//标记已经登陆过
this.authenticated = true;
if (token instanceof HostAuthenticationToken) {
host = ((HostAuthenticationToken) token).getHost();
}
if (host != null) {
this.host = host;
}
//获取登陆时的session
Session session = subject.getSession(false);
if (session != null) {
//执行new StoppingAwareProxiedSession(session, this); 登陆后的session封装成StoppingAwareProxiedSession代理对象
this.session = decorate(session);
} else {
this.session = null;
}
}

AbstractRememberMeManager  处理 remeberme

public void onSuccessfulLogin(Subject subject, AuthenticationToken token, AuthenticationInfo info) {
//清空之前的认证信息
forgetIdentity(subject); //如果是rememberMe类型的token
if (isRememberMe(token)) {
//记录
rememberIdentity(subject, token, info);
}
}
public void rememberIdentity(Subject subject, AuthenticationToken token, AuthenticationInfo authcInfo) {
//从认证后的信息中获取
PrincipalCollection principals = getIdentityToRemember(subject, authcInfo);
rememberIdentity(subject, principals);
}
// 加密处理
protected void rememberIdentity(Subject subject, PrincipalCollection accountPrincipals) {
byte[] bytes = convertPrincipalsToBytes(accountPrincipals);
rememberSerializedIdentity(subject, bytes);
}
//使用CipherService类进行处理
protected byte[] convertPrincipalsToBytes(PrincipalCollection principals) {
byte[] bytes = serialize(principals);
if (getCipherService() != null) {
bytes = encrypt(bytes);
}
return bytes;
}
// 返回加密后的认证信息
protected byte[] encrypt(byte[] serialized) {
byte[] value = serialized;
CipherService cipherService = getCipherService();
if (cipherService != null) {
// getEncryptionCipherKey() 获取的是rememberMe cookie加密和解密的密钥
ByteSource byteSource = cipherService.encrypt(serialized, getEncryptionCipherKey());
value = byteSource.getBytes();
}
return value;
}
//加密
protected byte[] encrypt(byte[] serialized) {
byte[] value = serialized;
CipherService cipherService = getCipherService();
if (cipherService != null) {
ByteSource byteSource = cipherService.encrypt(serialized, getEncryptionCipherKey());
value = byteSource.getBytes();
}
return value;
}

CookieRememberMeManager

protected void rememberSerializedIdentity(Subject subject, byte[] serialized) {
HttpServletRequest request = WebUtils.getHttpRequest(subject);
HttpServletResponse response = WebUtils.getHttpResponse(subject); //base 64 encode it and store as a cookie:
String base64 = Base64.encodeToString(serialized);
//rememberMe的cookie模板 key为自定义的名字 我这儿是rememberMe
Cookie template = getCookie();
Cookie cookie = new SimpleCookie(template);
cookie.setValue(base64);
cookie.saveTo(request, response);
}

下面是remember的配置

<bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
<constructor-arg value="rememberMe"/>
<property name="httpOnly" value="true"/>
<property name="maxAge" value="2592000"/><!-- 30天 -->
</bean> <!-- rememberMe管理器 -->
<bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
<!-- rememberMe cookie加密和解密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)-->
<property name="cipherKey"
value="#{T(org.apache.shiro.codec.Base64).decode('4AvVhmFLUs0KTA3Kprsdag==')}"/>
<property name="cookie" ref="rememberMeCookie"/>
</bean>

由于篇幅原因,本节详细介绍的是登陆需要验证的请求跳转到登陆界面的源码解析

小结:

  1、当第一次请求失败后,会重定向到当前过滤器的登陆界面,并创建一个session,将sessinID存在cookie,重定向的url,还会存放在sessionDao中(默认是ehcache, 可自定义)

  2、当请求的路径为不用认证(anon等自定义preHandle返回true的路径),也会由servlet容器调用shiroRequest的getSession方法创建一个session,保存位置同1

spring集成shiro登陆流程(上)的更多相关文章

  1. spring集成shiro登陆流程(下)

    首先声明入门看的张开涛大神的<跟我学shiro> 示例:https://github.com/zhangkaitao/shiro-example 博客:http://jinnianshil ...

  2. Spring集成shiro做登陆认证

    一.背景 其实很早的时候,就在项目中有使用到shiro做登陆认证,直到今天才又想起来这茬,自己抽空搭了一个spring+springmvc+mybatis和shiro进行集成的种子项目,当然里面还有很 ...

  3. spring集成shiro报错解决(no bean named 'shiroFilter' is defined)

    引言: 本人在使用spring集成shiro是总是报“no bean named 'shiroFilter' is defined”,网上的所有方式挨个试了一遍,又检查了一遍, 还是没有解决,最后,抱 ...

  4. shiro实战系列(十五)之Spring集成Shiro

    Shiro 的 JavaBean 兼容性使得它非常适合通过 Spring XML 或其他基于 Spring 的配置机制.Shiro 应用程序需要一个具 有单例 SecurityManager 实例的应 ...

  5. Shiro(二):Spring-boot如何集成Shiro(上)

    这篇文章主要介绍了spring-boot是如何集成shiro的authentication流程的. 从shiro-spring-boot-web-starter说起 shiro-spring-boot ...

  6. spring 集成shiro 之 自定义过滤器

    在web.xml中加入 <!-- 过期时间配置 --> <session-config><session-timeout>3</session-timeout ...

  7. Shiro学习总结(10)——Spring集成Shiro

    1.引入Shiro的Maven依赖 [html] view plain copy <!-- Spring 整合Shiro需要的依赖 --> <dependency> <g ...

  8. Spring集成Shiro使用小结

    shiro的认证流程 Application Code:应用程序代码,由开发人员负责开发的 Subject:框架提供的接口,代表当前用户对象 SecurityManager:框架提供的接口,代表安全管 ...

  9. Spring集成shiro+nginx 实现访问记录

    最近公司的网站需要添加用户访问记录功能,由于使用了nginx请求转发直接通过HttpServletRequest无法获取用户真实Ip 关于nginx获取真实IP的资料  https://blog.cs ...

随机推荐

  1. SpringBoot操作数据库 2017.12.14

    http://blog.csdn.net/forezp/article/details/61472783

  2. ORA-12737: Instant Client Light: unsupported server character set CHS16GBK

    当使用Navicat Premiun 英文版连接oracl时可能会报ORA-12737: Instant Client Light: unsupported server character set ...

  3. Python 三级菜单 增强版

    需要实现的功能是:三级菜单1.从文本内读出选项2.查询每一级的选项,并能对选项进行增/删/改功能3.每一级可以退出程序或者返回上一层 2018-5-14 更新内容 思路 实现过程中的BUG及解决方案: ...

  4. 学习笔记1--响应式网页+Bootstrap起步+全局CSS样式

    一.学习之前要了解一些背景知识: 在2g时代,3g时代,4g时代,早期的网页浏览设备,功能机,智能机.(本人最喜欢的透明肌,和古典黑莓机) 1.什么是响应式网页? Responsive Web Pag ...

  5. JAVA经典算法40题(原题+分析)之原题

    JAVA经典算法40题(上) [程序1]   题目:古典问题:有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第四个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少? [程 ...

  6. guid.go

    ] = byte(g)     hex.Encode(h[:], b[:])     return h }

  7. BZOJ_2820_YY的GCD_莫比乌斯反演

    BZOJ_2820_YY的GCD_莫比乌斯反演 题意&分析: 首先f[i]非积性,但可以通过μ处理,所以我们考虑线筛 f[i*p]=μ[i*p/p']; 1.当i为质数时f[i]=1; 2.当 ...

  8. BZOJ_2440_[中山市选2011]完全平方数_容斥原理+线性筛

    BZOJ_2440_[中山市选2011]完全平方数_容斥原理 题意: 求第k个不是完全平方数倍数的数 分析: 二分答案,转化成1~x中不是完全平方数倍数的数的个数 答案=所有数-1个质数的平方的倍数+ ...

  9. Java开源生鲜电商平台-性能优化以及服务器优化的设计与架构(源码可下载)

    Java开源生鲜电商平台-性能优化以及服务器优化的设计与架构(源码可下载) 说明:Java开源生鲜电商平台-性能优化以及服务器优化的设计与架构,我采用以下三种维度来讲解 1.  代码层面. 2.  数 ...

  10. 构建apache web 服务器

    一.Apache服务器工作模式 1.Prefork模式:Prefork MPM使用多个子进程,每个子进程只有一个线程,每个进程在某个确定的时间只能维护一个连接 2.Worker模式: Worker M ...