zoukankan      html  css  js  c++  java
  • spring集成shiro登陆流程(上)

    上一篇已经分析了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

  • 相关阅读:
    循环结构
    位运算符
    Switch 选择结构
    if结构和逻辑运算符
    变量和运算符
    [luogu1090 SCOI2003] 字符串折叠(区间DP+hash)
    [luogu2329 SCOI2005] 栅栏(二分+搜索)
    [luogu 4886] 快递员
    [luogu4290 HAOI2008]玩具取名(DP)
    [luogu2624 HNOI2008]明明的烦恼 (prufer+高精)
  • 原文地址:https://www.cnblogs.com/qiaozhuangshi/p/10777507.html
Copyright © 2011-2022 走看看