一、项目整体架构分析

  首先数据库中menu_catalog和menu表是存放菜单目录的,导航栏的展示都是从这里面取,menu中有导航栏的路径,通过这些路径以及menu_id从MainController中查找跳转的共同页面(frame.jsp)

  根据点击menu_id的不同展示不同的页面,frame.jsp中包含的页面有top.jsp(里面主要引入一些公用的js,定义<html><body>的开始标签),bottom.jsp(里面是</body></html>结束标签)这两个定义在页面的最前面和最后面,中间包括header.jsp(里面是头部导航栏内容,有标题展示的逻辑),footer.jsp(这里面是底部展示的公共部分),main-sidebar.jsp(这个是左侧栏展示的内容,以及操作的逻辑),<iframe></iframe>最后是要展示的页面menuFrame,定义在iframe标签中,这里设置的最小高度是800px;如果页面需要还可以自定义高度。

  代码是:

    var timer;
    if (timer) {
        clearInterval(timer);
    }
    timer = setInterval(function () {
        var menuFrame = $(window.parent.document).find("#menuFrame");
        var height = $("#bigDiv").height() + 20;
        menuFrame.height(height);
    }, 500);//每0.5秒检查一次

  根据div的高度自动检测,然后根据div的高度改变menuFrame的高度,这样不同页面就可以根据内容需要设置成合适的高度。

 

  在MainController中设置menuId,根据menuId是否选中在header.jsp中设置标题的不同样式。

  点击导航栏除了首页,其他都是走menu/{id}的路径,都会跳转到frame页面,也就是左侧是导航栏,右侧是页面的样式,点击首页标签,会跳转到home页面。

  

 

      Window函数:

     (1)Window setInterval() 方法:可以重复执行

         setInterval() 方法可按照指定的周期(以毫秒计)来调用函数或计算表达式。
         setInterval() 方法会不停地调用函数,直到 clearInterval() 被调用或窗口被关闭。由 setInterval() 返回的 ID 值可用作 clearInterval() 方法的参数。

    (2)Window setTimeout() 方法:只执行一次

              setTimeout() 方法用于在指定的毫秒数后调用函数或计算表达式。
              使用 clearTimeout() 方法来阻止函数的执行。

 

二、与服务器交互获取数据

  个人理解:用户进行登录操作,向服务器发送http请求,创建一个到服务器指定端口的TCP连接,HTTP服务器则在那个端口监听客户端的请求, 服务器根据请求信息做出响应,响应完之后释放TCP连接。

  向服务器发送请求包括:用户在浏览器中输入地址,或者使用AJAX向服务器请求数据。

  HTTP协议是无状态的,无法保存用户的登录状态,也就是用户登录网站从一个页面跳转到另一个页面不会保存登录状态,为了实现保存状态功能,引入了COOKIE技术,HTTP+COOKIE这样就可以保存用户状态。

 

三、项目整合shiro过程分析

  项目中,用户成功登录之后就会 跳转到首页,默认配置是在shiro过滤器中设置,如果我们是直接访问登录页面的话,shiro就会根据我们配置的successUrl去重定向,如果我们没有配置successUrl的话,那么shiro重定向默认的/。

  用户输入用户名密码点击登录会被FormAuthenticationFilter拦截器拦截,同样,当点击登录的时候自定义Controller就会调用login方法。

 Subject currentUser = SecurityUtils.getSubject();
 currentUser.login(token);

  1、Controller中我们使用subject.login(token)来执行登陆操作
  2、DelegatingSubject.login中使用securityManager.login(this.token)
  3、DefaultSecrityManager(这里我注入的是这个secrityManager,也可以自定义).login中使用authenticate(token)
  4、AuthenticatingSecurityManager.authenticate中使用authenticator.authenticate(token)
  5、AbstractAuthenticator.authenticate中使用doAuthenticate(token)
  6、ModularRealmAuthenticator.doAuthenticate中通过getRealms来得到所有的Realm,然后使用(我假设这里只定义了一个realm)doSingleRealmAuthentication(realm, authenticationtoekn)
  7、ModularReamlmAuthenticator.doSingleRealmAuthentication中使用了Realm.getAuthenticationInfo(token)
  8、AUthenticatingRealm中使用doGetAuthenticatingInfor(token),这个方法其实就是我们自定的Realm中的方法,而后使用assertCredentialsMatch(token,info)
  9、自定义或者默认的CredentialMatcher的doCredentialsMatch方法对info中的Credential和token中的crediential进行比对

     总结:用户点击登录,调用login方法,对用户信息进行认证,认证的是自定义的realm,即前端输入的用户信息与数据库中查询到的用户信息进行对比,如果一致认证成功,用户认证成功之后获取该用户的权限,根据不同用户展示不同信息。

 

  shiro中配置与过滤器(表单过滤器):https://www.cnblogs.com/jtlgb/p/9577207.html

  authc对应shiro中的拦截器FormAuthenticationFilter,验证之后才能访问。

  项目中获取用户登录对象、获取session、设置session、删除session、用户注销都是在com.cattsoft.system.security下的SecurityManager类中设置的。

 

shiro登录过程验证:

  shiro登录之后逻辑跑通:https://blog.csdn.net/u011607686/article/details/80360744

     登录过程总结:由于shiro默认注册了FormAuthenticationFilter,所以配置中可以不需要为此方法定义bean,但有个前提,登录页面中的登录账号和密码,记住我的name必须和FormAuthenticationFilter默认的名称一致。登录页面提交后跳转到/login,进入登录方法,由于此路径权限设置为authc,shiro对该路径进行过滤,authc权限由FormAuthenticationFilter进行过滤,登录请求进入onAccessDenied方法

protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {  
        if (isLoginRequest(request, response)) {  //判断是否是登录请求  
            if (isLoginSubmission(request, response)) { // 是否是http post请求  
                if (log.isTraceEnabled()) {  
                    log.trace("Login submission detected.  Attempting to execute login.");  
                }  
                return executeLogin(request, response);  
            } else {  
                if (log.isTraceEnabled()) {  
                    log.trace("Login page view.");  
                }  
                //allow them to see the login page ;)  
                return true;  
            }  
        } else {  
            if (log.isTraceEnabled()) {  
                log.trace("Attempting to access a path which requires authentication.  Forwarding to the " +  
                        "Authentication url [" + getLoginUrl() + "]");  
            }  
            saveRequestAndRedirectToLogin(request, response);  
            return false;  
        }  
}  

  其中 executeLogin(request, response)方法的具体实现在继承的AuthenticatingFilter里,FormAuthenticationFilter过滤器继承AuthenticatingFilter过滤器,AuthenticatingFilter过滤器继承AuthenticationFilter过滤器。

protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {  
        AuthenticationToken token = createToken(request, response);   
        if (token == null) {  
            String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken " +"must be created in order to execute a login attempt.";  
            throw new IllegalStateException(msg);  
        }  
        try {  
            Subject subject = getSubject(request, response);  
            subject.login(token);  
            return onLoginSuccess(token, subject, request, response);  
        } catch (AuthenticationException e) {  
            return onLoginFailure(token, e, request, response);  
        }  
}  

  剖析:createToken(request, response); 具体实现在子类FormAuthenticationFilter中

protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {  
    String username = getUsername(request);  
    String password = getPassword(request);  
    return createToken(username, password, request, response);  
}  

  从上可以看出,具体的登录账号和密码从request中取出来,并创建了token对象,调用subject的login方法,login方法实现大致流程是用token去realm中取AuthenticationInfo对象,AuthenticationInfo对象存放的是正确的登录账号和密码,并和token中数据进行匹配,然后根据匹配情况返回相应的结果。

  

四、shiro中jsp标签

  principal标签:取值取的是用户登录的时候,在realm实现类中

return new SimpleAuthenticationInfo(user,user.getPswd(),getName());
在new SimpleAuthenticationInfo(第一个参数,....) 的第一个参数放的如果是一个username(用户名),那么就可以直接用。
<!--取到username-->
<shiro: principal/>

如果第一个参数放的是对象,比如放的是User对象。那么如果需要username字段。
<!--需要指定property-->
<shiro:principal property="username"/>

  参考:https://blog.csdn.net/baidu_37366055/article/details/88072135

 

五、shiro中鉴权和用户权限

  用户在登录的时候在SecurityRealm中会根据登录用户查询数据库中该用户所拥有的权限,主要根据operation、permission、operate_permission、menu、menu_operation五张表可以查询用户的菜单和目录权限,当不同用户登录时会展示不同的菜单内容,管理员登录之后可以为每个角色和用户进行授权操作,也是操作这几张表,授权之后用户展示信息会改变。

 

  授权:https://blog.csdn.net/www1056481167/article/details/89061629

  项目用户权限个人理解:用户登录的时候从shiro中获取该用户菜单和菜单目录,在鉴权的时候在doGetAuthorizationInfo方法中查询用户的权限信息并保存在用户信息中,什么时候进行鉴权是个问题。

  shiro认证和鉴权过程:https://www.cnblogs.com/zhukaixin/p/10722811.html

  shiro权限认证及授权的执行流程:https://www.cnblogs.com/JerryTomcat/p/11897554.html

  总结:用户登录之后跳转到登录成功页面,当页面中遇到shiro标签时,这时会执行授权方法,在本项目中,用户登录成功跳转首页,导航栏的显示中就有shiro标签,就会查询登录用户所有的权限。

 

  过滤器继承关系:https://blog.csdn.net/u014203449/article/details/80689268

 六、项目整合shiro新的理解

  配置文件中设置了menuFilterChainDefinition,其中属性有filterChainDefinitions,里面是访问路径,其中有/login = authc,也就是当点击登录按钮的时候被拦截器进行拦截,拦截器是自定义的SecurityAuthcFilter,并且继承FormAuthenticationFilter,也就是说登录的时候,所有提交的参数还是被表单拦截器拦截,然后根据这些参数进行用户认证,另外SecurityAuthcFilter中有调用SecurityManager.isSessionUserAdminRole();里面判断是否有管理员角色,调用这个方法之后就会触发鉴权方法,即用户的权限查询就是在这个时候执行的。

 

  用户登录认证和鉴权大体流程:

  用户点击登录,被SecurityAuthcFilter拦截器拦截,SecurityAuthcFilter继承FormAuthenticationFilter,FormAuthenticationFilter继承AuthenticatingFilter,AuthenticatingFilter会执行executeLogin方法,方法里有subject.login(token);方法,shiro当遇到这个方法时会触发用户认证方法,即SecurityRealm中的doGetAuthenticationInfo方法,方法查询数据库和参数token进行对比,经过一系列验证,最后用户成功登录,接着执行下面的方法onLoginSuccess,这时会跳转到SecurityAuthcFilter中的onLoginSuccess方法中,里面有SecurityManager.isSessionUserAdminRole();这段代码会触发鉴权方法,即SecurityRealm中的doGetAuthorizationInfo,通过查询数据库查获取到该用户所拥有的所有权限,然后接着执行下面的代码。