学习任务目标

  1. 用户必须要登陆之后才能访问定义链接,否则跳转到登录页面。

  2. 对链接进行权限控制,只有当当前登录用户有这个链接访问权限才可以访问,否则跳转到指定页面。

  3. 输入错误密码用户名或则用户被设置为静止登录,返回相应json串信息

    导入shiro依赖包到pom.xml

    <!-- shiro权限控制框架 -->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>1.3.2</version>
    </dependency>
    

    采用RBAC模式建立数据库

    /*表结构插入*/
    DROP TABLE IF EXISTS `u_permission`;
    
    CREATE TABLE `u_permission` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT,
      `url` varchar(256) DEFAULT NULL COMMENT 'url地址',
      `name` varchar(64) DEFAULT NULL COMMENT 'url描述',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8;
    
    /*Table structure for table `u_role` */
    
    DROP TABLE IF EXISTS `u_role`;
    
    CREATE TABLE `u_role` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT,
      `name` varchar(32) DEFAULT NULL COMMENT '角色名称',
      `type` varchar(10) DEFAULT NULL COMMENT '角色类型',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
    
    /*Table structure for table `u_role_permission` */
    
    DROP TABLE IF EXISTS `u_role_permission`;
    
    CREATE TABLE `u_role_permission` (
      `rid` bigint(20) DEFAULT NULL COMMENT '角色ID',
      `pid` bigint(20) DEFAULT NULL COMMENT '权限ID'
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    /*Table structure for table `u_user` */
    
    DROP TABLE IF EXISTS `u_user`;
    
    CREATE TABLE `u_user` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT,
      `nickname` varchar(20) DEFAULT NULL COMMENT '用户昵称',
      `email` varchar(128) DEFAULT NULL COMMENT '邮箱|登录帐号',
      `pswd` varchar(32) DEFAULT NULL COMMENT '密码',
      `create_time` datetime DEFAULT NULL COMMENT '创建时间',
      `last_login_time` datetime DEFAULT NULL COMMENT '最后登录时间',
      `status` bigint(1) DEFAULT '1' COMMENT '1:有效,0:禁止登录',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8;
    
    /*Table structure for table `u_user_role` */
    
    DROP TABLE IF EXISTS `u_user_role`;
    
    CREATE TABLE `u_user_role` (
      `uid` bigint(20) DEFAULT NULL COMMENT '用户ID',
      `rid` bigint(20) DEFAULT NULL COMMENT '角色ID'
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    

    Dao层代码的编写

    配置shiro

    ShiroConfig.java

    /**
     * @author 作者 z77z
     * @date 创建时间:2017年2月10日 下午1:16:38
     *
     */
    @Configuration
    public class ShiroConfig {
        /**
         * ShiroFilterFactoryBean 处理拦截资源文件问题。
         * 注意:单独一个ShiroFilterFactoryBean配置是或报错的,以为在
         * 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager
         *
         * Filter Chain定义说明 1、一个URL可以配置多个Filter,使用逗号分隔 2、当设置多个过滤器时,全部验证通过,才视为通过
         * 3、部分过滤器可指定参数,如perms,roles
         *
         */
        @Bean
        public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
    
            // 必须设置 SecurityManager
            shiroFilterFactoryBean.setSecurityManager(securityManager);
    
            // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
            shiroFilterFactoryBean.setLoginUrl("/login");
            // 登录成功后要跳转的链接
            shiroFilterFactoryBean.setSuccessUrl("/index");
            // 未授权界面;
            shiroFilterFactoryBean.setUnauthorizedUrl("/403");
    
            // 拦截器.
            Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
            // 配置不会被拦截的链接 顺序判断
            filterChainDefinitionMap.put("/static/**", "anon");
            filterChainDefinitionMap.put("/ajaxLogin", "anon");
    
            // 配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了
            filterChainDefinitionMap.put("/logout", "logout");
    
            filterChainDefinitionMap.put("/add", "perms[权限添加]");
    
            // <!-- 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
            // <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
            filterChainDefinitionMap.put("/**", "authc");
    
            shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
            System.out.println("Shiro拦截器工厂类注入成功");
            return shiroFilterFactoryBean;
        }
    
        @Bean
        public SecurityManager securityManager() {
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            // 设置realm.
            securityManager.setRealm(myShiroRealm());
            return securityManager;
        }
    
        /**
         * 身份认证realm; (这个需要自己写,账号密码校验;权限等)
         *
         * @return
         */
        @Bean
        public MyShiroRealm myShiroRealm() {
            MyShiroRealm myShiroRealm = new MyShiroRealm();
            return myShiroRealm;
        }
    }
    

    登录认证实现

    doGetAuthenticationInfo的重写

    /**
    * 认证信息.(身份验证) : Authentication 是用来验证用户身份
     *
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(
            AuthenticationToken authcToken) throws AuthenticationException {
        System.out.println("身份认证方法:MyShiroRealm.doGetAuthenticationInfo()");
    
        ShiroToken token = (ShiroToken) authcToken;
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("nickname", token.getUsername());
        map.put("pswd", token.getPswd());
        SysUser user = null;
        // 从数据库获取对应用户名密码的用户
        List<SysUser> userList = sysUserService.selectByMap(map);
        if(userList.size()!=0){
            user = userList.get(0);
        }
        if (null == user) {
            throw new AccountException("帐号或密码不正确!");
        }else if(user.getStatus()==0){
            /**
             * 如果用户的status为禁用。那么就抛出<code>DisabledAccountException</code>
             */
            throw new DisabledAccountException("帐号已经禁止登录!");
        }else{
            //更新登录时间 last login time
            user.setLastLoginTime(new Date());
            sysUserService.updateById(user);
        }
        return new SimpleAuthenticationInfo(user, user.getPswd(), getName());
    }
    

    链接权限的实现

    /**
    * 授权
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(
            PrincipalCollection principals) {
        System.out.println("权限认证方法:MyShiroRealm.doGetAuthenticationInfo()");
        SysUser token = (SysUser)SecurityUtils.getSubject().getPrincipal();
        String userId = token.getId();
        SimpleAuthorizationInfo info =  new SimpleAuthorizationInfo();
        //根据用户ID查询角色(role),放入到Authorization里。
        /*Map<String, Object> map = new HashMap<String, Object>();
        map.put("user_id", userId);
        List<SysRole> roleList = sysRoleService.selectByMap(map);
        Set<String> roleSet = new HashSet<String>();
        for(SysRole role : roleList){
            roleSet.add(role.getType());
        }*/
        //实际开发,当前登录用户的角色和权限信息是从数据库来获取的,我这里写死是为了方便测试
        Set<String> roleSet = new HashSet<String>();
        roleSet.add("100002");
        info.setRoles(roleSet);
        //根据用户ID查询权限(permission),放入到Authorization里。
        /*List<SysPermission> permissionList = sysPermissionService.selectByMap(map);
        Set<String> permissionSet = new HashSet<String>();
        for(SysPermission Permission : permissionList){
            permissionSet.add(Permission.getName());
        }*/
        Set<String> permissionSet = new HashSet<String>();
        permissionSet.add("权限添加");
        info.setStringPermissions(permissionSet);
           return info;
    }
    

    编写web层的代码

    登录页面:

    controller

    //跳转到登录表单页面
    @RequestMapping(value="login")
    public String login() {
        return "login";
    }
    
    /**
     * ajax登录请求
     * @param username
     * @param password
     * @return
     */
    @RequestMapping(value="ajaxLogin",method=RequestMethod.POST)
    @ResponseBody
    public Map<String,Object> submitLogin(String username, String password,Model model) {
        Map<String, Object> resultMap = new LinkedHashMap<String, Object>();
        try {
    
            ShiroToken token = new ShiroToken(username, password);
            SecurityUtils.getSubject().login(token);
            resultMap.put("status", 200);
            resultMap.put("message", "登录成功");
    
        } catch (Exception e) {
            resultMap.put("status", 500);
            resultMap.put("message", e.getMessage());
        }
        return resultMap;
    }
    

    jsp

    <%@ page language="java" contentType="text/html; charset=utf-8"
        pageEncoding="utf-8"%>
    <%
        String path = request.getContextPath();
        String basePath = request.getScheme() + "://"
                + request.getServerName() + ":" + request.getServerPort()
                + path;
    %>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <script type="text/javascript"
        src="<%=basePath%>/static/js/jquery-1.11.3.js"></script>
    <title>登录</title>
    </head>
    <body>
        错误信息:
        <h4 id="erro"></h4>
        <form>
            <p>
                账号:<input type="text" name="username" id="username" value="admin" />
            </p>
            <p>
                密码:<input type="text" name="password" id="password" value="123" />
            </p>
            <p>
                <input type="button" id="ajaxLogin" value="登录" />
            </p>
        </form>
    </body>
    <script>
        var username = $("#username").val();
        var password = $("#password").val();
        $("#ajaxLogin").click(function() {
            $.post("/ajaxLogin", {
                "username" : username,
                "password" : password
            }, function(result) {
                if (result.status == 200) {
                    location.href = "/index";
                } else {
                    $("#erro").html(result.message);
                }
            });
        });
    </script>
    </html>
    

    主页页面

    controller

    //跳转到主页
    @RequestMapping(value="index")
    public String index() {
        return "index";
    }
    
    /**
    * 退出
     * @return
     */
    @RequestMapping(value="logout",method =RequestMethod.GET)
    @ResponseBody
    public Map<String,Object> logout(){
        Map<String, Object> resultMap = new LinkedHashMap<String, Object>();
        try {
            //退出
            SecurityUtils.getSubject().logout();
        } catch (Exception e) {
            System.err.println(e.getMessage());
        }
        return resultMap;
    }
    

    jsp

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <%
        String path = request.getContextPath();
        String basePath = request.getScheme() + "://"
                + request.getServerName() + ":" + request.getServerPort()
                + path;
    %>
    <!DOCTYPE html>
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <script type="text/javascript"
        src="<%=basePath%>/static/js/jquery-1.11.3.js"></script>
    <title>Insert title here</title>
    </head>
    <body>
        helloJsp
        <input type="button" id="logout" value="退出登录" />
    </body>
    <script type="text/javascript">
        $("#logout").click(function(){
            location.href="/logout";
        });
    </script>
    </html>
    

    添加操作页面

    controller

    @RequestMapping(value="add")
    public String add() {
        return "add";
    }
    

    jsp

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <%
        String path = request.getContextPath();
        String basePath = request.getScheme() + "://"
                + request.getServerName() + ":" + request.getServerPort()
                + path;
    %>
    <!DOCTYPE html>
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <script type="text/javascript"
        src="<%=basePath%>/static/js/jquery-1.11.3.js"></script>
    <title>Insert title here</title>
    </head>
    <body>
    具有添加权限
    </body>
    </html>
    

    测试

    任务一


    任务二


    任务三

    总结

    当然shiro很强大,这仅仅是完成了登录认证和权限管理这两个功能,接下来我会继续学习和分享,说说接下来的学习路线吧:

  4. shiro+redis集成,避免每次访问有权限的链接都会去执行MyShiroRealm.doGetAuthenticationInfo()方法来查询当前用户的权限,因为实际情况中权限是不会经常变得,这样就可以使用redis进行权限的缓存。

  5. 实现shiro链接权限的动态加载,之前要添加一个链接的权限,要在shiro的配置文件中添加filterChainDefinitionMap.put("/add", "roles[100002],perms[权限添加]"),这样很不方便管理,一种方法是将链接的权限使用数据库进行加载,另一种是通过init配置文件的方式读取。

  6. Shiro 登录后跳转到最后一个访问的页面
     

  7. Shiro 自定义权限校验Filter定义,及功能实现。

  8. Shiro Ajax请求权限不满足,拦截后解决方案。这里有一个前提,我们知道Ajax不能做页面redirect和forward跳转,所以Ajax请求假如没登录,那么这个请求给用户的感觉就是没有任何反应,而用户又不知道用户已经退出了。

  9. Shiro JSP标签使用。

  10. Shiro 登录后跳转到最后一个访问的页面

  11. 在线显示,在线用户管理(踢出登录)。

  12. 登录注册密码加密传输。

  13. 集成验证码。

  14. 记住我的功能。关闭浏览器后还是登录状态。

  15. 还有没有想到的后面再说,欢迎大家提出一些建议。

10-07 17:21