• SpringBoot中关于Shiro权限管理的整合使用


     shiro是一个轻量级的安全框架,包含用户认证用户授权

    分析shiro的核心API:

    Subject:用户主体(把操作交给SecurityManager)

    SecurityManager:安全管理器(管理Reaml)

    Reaml:shiro连接数据的桥梁

    Shiro的配置类:
     创建ShiroFilterFactoryBean;

     创建DefaultWebSecurityManager;

      创建Reaml(继承AuthorizingReaml);

    Shiro内置过滤器,可以实现权限相关的拦截器:

     常用的过滤器:

    anno:无需认证(登陆)可以访问

    authc:必须认证才能访问

    user:如果使用rememberMe的功能可以直接访问

    perms:该资源必须得到资源权限可以访问

    role:该资源必须得到角色权限才能访问

    在整合Shiro的时候,我们先要确定一下我们的步骤:

    1.加入Shiro的依赖包,实现自己的Realm类(通过继承AuthorizingRealm类);

    2.实现Shiro的配置类

    3.实现前端的登录界面以及Controller类

    第一步:

    在pom.xml中加入依赖包

    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>1.4.0</version>
     </dependency>

    实现Realm类

    package ariky.shiro.realm;
     
    import java.util.HashSet;
    import java.util.Set;
     
    import javax.servlet.http.HttpServletRequest;
     
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.AuthenticationException;
    import org.apache.shiro.authc.AuthenticationInfo;
    import org.apache.shiro.authc.AuthenticationToken;
    import org.apache.shiro.authc.IncorrectCredentialsException;
    import org.apache.shiro.authc.LockedAccountException;
    import org.apache.shiro.authc.SimpleAuthenticationInfo;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.authz.AuthorizationInfo;
    import org.apache.shiro.authz.SimpleAuthorizationInfo;
    import org.apache.shiro.realm.AuthorizingRealm;
    import org.apache.shiro.subject.PrincipalCollection;
    import org.apache.shiro.util.ByteSource;
    import org.apache.shiro.web.subject.WebSubject;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
     
    /**
    * @ClassName:
    * @Description: Realm的配置
    * @author fuweilian
    * @date 2018-5-12 上午11:36:41
     */
    public class MyShiroRealm extends AuthorizingRealm {
        //slf4j记录日志,可以不使用
        private Logger logger = LoggerFactory.getLogger(MyShiroRealm.class);
     
        /**
         * 设置授权信息
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            logger.info("开始授权(doGetAuthorizationInfo)");
            SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
            HttpServletRequest request = (HttpServletRequest) ((WebSubject) SecurityUtils
                    .getSubject()).getServletRequest();//这个可以用来获取在登录的时候提交的其他额外的参数信息
            String username = (String) principals.getPrimaryPrincipal();//这里是写的demo,后面在实际项目中药通过这个登录的账号去获取用户的角色和权限,这里直接是写死的
            //受理权限
            //角色
            Set<String> roles = new HashSet<String>();
            roles.add("role1");
            authorizationInfo.setRoles(roles);
            //权限
            Set<String> permissions = new HashSet<String>();
            permissions.add("user:list");
            //permissions.add("user:add");
            authorizationInfo.setStringPermissions(permissions);
            return authorizationInfo;
        }
     
        /**
         * 设置认证信息
         */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(
                AuthenticationToken authenticationToken) throws AuthenticationException {
            logger.info("开始认证(doGetAuthenticationInfo)");
            //UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
            HttpServletRequest request = (HttpServletRequest) ((WebSubject) SecurityUtils
                    .getSubject()).getServletRequest();
            UsernamePasswordToken token = new UsernamePasswordToken (request.getParameter("userName"),request.getParameter("password"));
            //获取用户输入的账号
            String userName = (String)token.getPrincipal();
            //通过userName去数据库中匹配用户信息,通过查询用户的情况做下面的处理
            //这里暂时就直接写死,根据登录用户账号的情况做处理
            logger.info("账号:"+userName);
            if("passwordError".equals(userName)){//密码错误
                throw new IncorrectCredentialsException(); 
            }else if("lockAccount".equals(userName)){// 用户锁定
                throw new LockedAccountException(); 
            }else{
                SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                        userName, //用户名
                        "123456", //密码,写死
                        ByteSource.Util.bytes(userName+"salt"),//salt=username+salt
                        getName()  //realm name
                );
                return authenticationInfo;
            }
        }
        
    }

    第二步 实现Shiro的配置类:

    package ariky.shiro.configuration;
     
    import java.util.LinkedHashMap;
    import java.util.Map;
     
    import org.apache.shiro.mgt.SecurityManager;
    import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
    import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
    import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
     
    import ariky.shiro.realm.MyShiroRealm;
     
    /**
    * @ClassName: ShiroConfiguration 
    * @Description: shiro的配置类 
    * @author fuweilian
    * @date 2018-5-12 上午11:05:09
     */
    @Configuration
    public class ShiroConfiguration {
        private static Logger logger = LoggerFactory.getLogger(ShiroConfiguration.class);
        @Bean(name = "shiroFilter")
        public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager){
            logger.info("进入shiroFilter......");
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
            shiroFilterFactoryBean.setSecurityManager(securityManager);
            //设置不需要拦截的路径
            Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
            //按顺序依次判断
            filterChainDefinitionMap.put("/static/**", "anon");
            //配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
            filterChainDefinitionMap.put("/logout", "logout");
            //<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
            /************************************初始化所有的权限信息开始******************************************/
            //这里,如果以后再项目中使用的话,直接从数据库中查询
            filterChainDefinitionMap.put("/user/list", "authc,perms[user:list]");
            //filterChainDefinitionMap.put("/user/add", "authc,perms[user:add]");
            /***************************************初始化所有的权限信息开始结束*********************************************/
            filterChainDefinitionMap.put("/**", "authc");
            // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
            shiroFilterFactoryBean.setLoginUrl("/login");
            // 登录成功后要跳转的链接
            shiroFilterFactoryBean.setSuccessUrl("/index");
            //未授权界面
            shiroFilterFactoryBean.setUnauthorizedUrl("/error/403");
            shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
            return shiroFilterFactoryBean;
        }
        
        @Bean
        public MyShiroRealm myShiroRealm(){
             MyShiroRealm myShiroRealm = new MyShiroRealm();
             //后面这里可以设置缓存的机制
             return myShiroRealm;
        } 
        
        @Bean
        public SecurityManager securityManager(){
            DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
            securityManager.setRealm(myShiroRealm());
            return securityManager;
        }
        
        
        @Bean
        public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
            AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
            authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
            return authorizationAttributeSourceAdvisor;
        }
        
    }

    第三步:实现Controoler类,这里写俩个类,一个是登录信息的LoginController处理类,一个是测试权限用的UserController

    1.LoginController.java

    package ariky.controller;
     
    import java.util.Map;
     
    import javax.servlet.http.HttpServletRequest;
     
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.IncorrectCredentialsException;
    import org.apache.shiro.authc.LockedAccountException;
    import org.apache.shiro.authc.UnknownAccountException;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
     
    /**
    * @ClassName: LoginController 
    * @Description: 登录控制的controller
    * @author fuweilian
    * @date 2018-5-12 下午01:15:46
     */
    @RequestMapping
    @Controller
    public class LoginController {
        private Logger logger = LoggerFactory.getLogger(LoginController.class);
        
        @RequestMapping(value="/login",method=RequestMethod.GET)
        public String getLogin(){
            logger.info("进入login页面");
            return "login";
        }
        
        @RequestMapping(value="/login",method=RequestMethod.POST)
        public String doLogin(HttpServletRequest req,Map<String, Object> model){
            logger.info("进入登录处理");
            String exceptionClassName = (String) req.getAttribute("shiroLoginFailure");
            logger.info("exceptionClassName:"+exceptionClassName);
            String error = null;
            if (UnknownAccountException.class.getName().equals(exceptionClassName)) {
                error = "用户名/密码错误";
            } else if (IncorrectCredentialsException.class.getName().equals(exceptionClassName)) {
                error = "用户名/密码错误";
            }else if(LockedAccountException.class.getName().equals(exceptionClassName)){ 
                error = "用户已锁定或已删除";
            }else if (exceptionClassName != null) {
                error = "其他错误:" + exceptionClassName;
            }
            if(SecurityUtils.getSubject().isAuthenticated()){//没有错误,但是已经登录了,就直接跳转到welcom页面
                model.put("name", req.getParameter("userName"));
                return "index";
            }else{//有错误的
                model.put("error", error);
                return "login";
            }
        }
        @RequestMapping("/index")
        public String index(){
            return "index";
        }
    }

    2.UserController.java

    package ariky.controller;
     
    import java.util.ArrayList;
    import java.util.List;
     
    import org.apache.shiro.authz.annotation.RequiresPermissions;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.ResponseBody;
     
    /**
    * @ClassName: UserController 
    * @Description: 用户处理Controller
    * @author fuweilian
    * @date 2018-5-12 下午03:11:06
     */
    @Controller
    @RequestMapping("/user")
    public class UserController {
        Logger logger = LoggerFactory.getLogger(UserController.class);
        @RequiresPermissions("user:list")//这个是配置是否有该权限的,如果是按上面的写法,这个是有权限的
        @RequestMapping(value="/list",method=RequestMethod.GET)
        public String getList(){
            logger.info("进入用户列表");
            return "user/list";
        }
        @RequiresPermissions(value={"user:add"})//这个是没有权限的
        @RequestMapping(value="/add",method=RequestMethod.GET)
        public String getAdd(){
            logger.info("进入新增用户界面");
            return "user/add";
        }
        
    }

    前端界面:有5个界面 (login.jsp,index.jsp,list.jsp,add.jsp,403.jsp)

    目录结构为:

    1.login.jsp

    <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
    <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
    <html>
    <head>
    <title>Login</title>
    </head>
     
    <body>
        <h1>登录页面----${error}</h1>
        <form:form action="${pageContext.request.contextPath }/login"
             method="post">
            用户名:<input type="text" name="userName">
            <br />
            密码:<input type="passwordParam" name="password"/>
            <input type="submit" value="提交"/>
        </form:form>
    </body>
    </html>

    2.index.jsp

    <%@ page language="java" pageEncoding="UTF-8"%>
     <%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
    <html>
    <head>
    <title>第一个例子</title>
    <script src="${pageContext.request.contextPath }/webjars/jquery/2.1.4/jquery.js"></script>
    <script src="${pageContext.request.contextPath }/webjarslocator/jquery/jquery.js"></script>
    </head>
     
    <body>
        <h1>${name}:你好,欢迎访问该网页</h1>
            <shiro:hasPermission name="user:list"><!-- 这个a标签是可以看见的 -->
                <a href="${pageContext.request.contextPath }/user/list" target="_blank">跳转到用户列表(有权限)</a>
            </shiro:hasPermission>
        <br/>
            <shiro:hasPermission name="user:add"><!-- 这个a标签是看不见的 -->
                <a href="${pageContext.request.contextPath }/user/add" target="_blank">跳转到新增用户列表(无权限)</a>
            </shiro:hasPermission>
    </body>
    </html>

    3.list.jsp和add.jsp以及403.jsp都差不多一样,这里就写一个,这里只是demo所用,在实际项目中,要以实际项目为准

    <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
    <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
    <html>
    <head>
    <title>userList</title>
    </head>
     
    <body>
        <h1>用户列表信息</h1>
    </body>
    </html>

    上面就是全部代码了,如果启动成功,进入login登录界面就可以测试一下shiro的权限认证了。上面的代码都是写死的,如果想要实现动态的权限管理和用户的权限管理的话,还要做一些其他处理,用户的动态权限这个只要在自己的ShiroRealm类里面授权的时候做一下查询数据库,动态的授权和角色就行。关于动态的权限管理的话,下面的方式可以实现,在修改完权限数据后,更新一下shiro里面的配置就行,具体看下面的代码,这里是demo,不是实际项目,在实际项目中最好不要把逻辑写在Controller里面

    package ariky.shiro.controller;
     
    import java.util.LinkedHashMap;
    import java.util.Map;
     
    import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
    import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager;
    import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
    import org.apache.shiro.web.servlet.AbstractShiroFilter;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
     
    /**
    * @ClassName: PermssionController 
    * @Description: 权限操作的controller 
    * @author fuweilian
    * @date 2018-5-12 下午04:59:15
     */
    @Controller
    @RequestMapping("permssion")
    public class PermssionController {
        
         @Autowired
         ShiroFilterFactoryBean shiroFilterFactoryBean;
         
         /**
         * @Title: updatePermssion 
         * @author: fuweilian
         * @Description: 这里暂时直接写在controller里面,,不按规则写了,,到时候在项目中使用的时候,才写
         * @return  参数说明 
         * @return Object    返回类型 
         * @throws
          */
        @RequestMapping("/updatePermssion")
        @ResponseBody
        public Object updatePermssion(){
            synchronized (shiroFilterFactoryBean){
                AbstractShiroFilter shiroFilter = null;
                try {
                    shiroFilter = (AbstractShiroFilter) shiroFilterFactoryBean
                            .getObject();
                    PathMatchingFilterChainResolver filterChainResolver = (PathMatchingFilterChainResolver) shiroFilter
                        .getFilterChainResolver();
                    DefaultFilterChainManager manager = (DefaultFilterChainManager) filterChainResolver
                        .getFilterChainManager();
                    // 清空老的权限控制
                    manager.getFilterChains().clear();
                    shiroFilterFactoryBean.getFilterChainDefinitionMap().clear();
                    //后面这个可以直接从数据库里面获取
                    Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
                    //按顺序依次判断
                    filterChainDefinitionMap.put("/static/**", "anon");
                    //配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
                    filterChainDefinitionMap.put("/logout", "logout");
                    //<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
                    /************************************初始化所有的权限信息开始******************************************/
                    //这里,如果以后再项目中使用的话,直接从数据库中查询
                    filterChainDefinitionMap.put("/user/list", "authc,perms[user:list]");
                    filterChainDefinitionMap.put("/user/add", "authc,perms[user:add]");
                    /***************************************初始化所有的权限信息开始结束*********************************************/
                    filterChainDefinitionMap.put("/**", "authc");
                    //
                    shiroFilterFactoryBean.setLoginUrl("/login");
                    // 登录成功后要跳转的链接
                    shiroFilterFactoryBean.setSuccessUrl("/index");
                    //未授权界面
                    shiroFilterFactoryBean.setUnauthorizedUrl("/error/403");
                    shiroFilterFactoryBean
                        .setFilterChainDefinitionMap(filterChainDefinitionMap);
                    // 重新构建生成
                    Map<String, String> chains = shiroFilterFactoryBean
                            .getFilterChainDefinitionMap();
                    for (Map.Entry<String, String> entry : chains.entrySet()) {
                        String url = entry.getKey();
                        String chainDefinition = entry.getValue().trim()
                                .replace(" ", "");
                        manager.createChain(url, chainDefinition);
                    }
                    return "更新权限成功";  
                } catch (Exception e) {
                    throw new RuntimeException(
                            "更新shiro权限出现错误!");
                }
            }
        }
     
    }

    下面是mysql库的表结构

    /*
    Navicat MySQL Data Transfer
    Source Server         : arikyDB
    Source Server Version : 50721
    Source Host           : 47.106.95.168:3306
    Source Database       : ariky
    Target Server Type    : MYSQL
    Target Server Version : 50721
    File Encoding         : 65001
    Date: 2018-05-14 16:05:51
    */
     
    SET FOREIGN_KEY_CHECKS=0;
     
    -- ----------------------------
    -- Table structure for common_permssion
    -- ----------------------------
    DROP TABLE IF EXISTS `common_permssion`;
    CREATE TABLE `common_permssion` (
      `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
      `NAME` varchar(255) DEFAULT NULL COMMENT '权限名称',
      `TYPE` varchar(255) DEFAULT NULL COMMENT '类型按钮(button)或者菜单(menu) ',
      `PARENT_ID` int(11) DEFAULT NULL COMMENT '上级ID',
      `PARENT_IDS` varchar(255) DEFAULT NULL COMMENT '上级PIDs',
      `URL` varchar(255) DEFAULT NULL COMMENT '访问路径',
      `ICONCLS` varchar(255) DEFAULT NULL COMMENT '图标(可以不要)',
      `PERMISSION` varchar(255) DEFAULT NULL COMMENT '权限(如user:list)',
      `ORDER_NUM` int(11) DEFAULT NULL COMMENT '排序',
      `REMARK` varchar(255) DEFAULT NULL COMMENT '备注',
      PRIMARY KEY (`ID`)
    ) ENGINE=InnoDB AUTO_INCREMENT=41 DEFAULT CHARSET=utf8 COMMENT='该表用来存储资源权限信息';
     
    -- ----------------------------
    -- Table structure for common_role
    -- ----------------------------
    DROP TABLE IF EXISTS `common_role`;
    CREATE TABLE `common_role` (
      `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
      `LABEL_ID` varchar(255) DEFAULT NULL COMMENT '标签Id',
      `NAME` varchar(255) DEFAULT NULL COMMENT '角色名称',
      `ROLE` varchar(255) DEFAULT NULL,
      `DESCRIPTION` varchar(255) DEFAULT NULL,
      `IS_SHOW` int(11) DEFAULT '1' COMMENT '判断该角色是否在使用(1:使用,2:禁用)',
      `IS_HANDLER` int(2) DEFAULT NULL COMMENT '判断是什么角色(1:后台角色,2:商家管理员角色,3:商家添加用户角色,4:游客角色)',
      PRIMARY KEY (`ID`)
    ) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8 COMMENT='角色表';
     
    -- ----------------------------
    -- Table structure for common_role_permssion
    -- ----------------------------
    DROP TABLE IF EXISTS `common_role_permssion`;
    CREATE TABLE `common_role_permssion` (
      `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键Id',
      `ROLE_ID` int(11) DEFAULT NULL COMMENT '角色Id',
      `RESOURCE_ID` int(11) DEFAULT NULL COMMENT '资源(权限)Id',
      PRIMARY KEY (`ID`)
    ) ENGINE=InnoDB AUTO_INCREMENT=493 DEFAULT CHARSET=utf8 COMMENT='角色资源权限表中间表';
  • 相关阅读:
    [ Algorithm ] N次方算法 N Square 动态规划解决
    [ Algorithm ] LCS 算法 动态规划解决
    sql server全文索引使用中的小坑
    关于join时显示no join predicate的那点事
    使用scvmm 2012的动态优化管理群集资源
    附加数据库后无法创建发布,error 2812 解决
    浅谈Virtual Machine Manager(SCVMM 2012) cluster 过载状态检测算法
    windows 2012 r2下安装sharepoint 2013错误解决
    sql server 2012 数据引擎任务调度算法解析(下)
    sql server 2012 数据引擎任务调度算法解析(上)
  • 原文地址:https://www.cnblogs.com/leeego-123/p/10717744.html
Copyright © 2020-2023  润新知