博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
spring集成shiro登陆流程(上)
阅读量:6210 次
发布时间:2019-06-21

本文共 17397 字,大约阅读时间需要 57 分钟。

上一篇已经分析了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中获取sessionprotected 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 @Overrideprotected 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方法最后会调用到这儿来 返回nullprotected 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、返回falseprotected 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  而不是上一次失败的urlprotected 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的配置

 

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

小结:

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

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

 

转载于:https://www.cnblogs.com/qiaozhuangshi/p/10777507.html

你可能感兴趣的文章
我的友情链接
查看>>
[iOS Animation]-CALayer 性能优化实例
查看>>
CentOS多网卡重命名配置
查看>>
变态青蛙跳
查看>>
Git常用命令总结
查看>>
定时任务
查看>>
MyBatis之输入与输出(resultType、resultMap)映射
查看>>
剥开比原看代码09:通过dashboard创建密钥时,前端的数据是如何传到后端的?
查看>>
51、YUM安装配置LAMP、phpMyAdmin实战
查看>>
Yeslab现任明教教主ISE课程前七部分免费发布
查看>>
linux下恢复误删文件
查看>>
Universal-Image-Loader,android-Volley,Picasso、Fresco和Glide开源组件加载网络图片的优缺点比较...
查看>>
RAID的肤浅认识
查看>>
poxtfix+dovecot+saslauthd+courier-authlib +mysql + extmail 完整虚拟邮箱系统部署
查看>>
Erlang并发机制 –进程调度
查看>>
XEN--转载自鸟哥的linux私房菜
查看>>
我的第一程序语言python
查看>>
DHCP服务开启了,为什么老是网络冲突
查看>>
跳出多重循环 JS
查看>>
MySql 自动更新时间为当前时间
查看>>