zoukankan      html  css  js  c++  java
  • 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='角色资源权限表中间表';
  • 相关阅读:
    linux 内核防火墙配置规则
    postfix 配置邮件服务器
    JDK 和 tomcat 安装 配置
    ifstat-网络接口监测工具
    一个比较通用的Makefile
    [转]vim常用命令
    [转]Valgrind简单用法
    [转]UDP/TCP穿越NAT的P2P通信方法研究(UDP/TCP打洞 Hole Punching)
    [转]Reed Solomon纠删码
    [转]分布式文件系统FastDFS架构剖析
  • 原文地址:https://www.cnblogs.com/leeego-123/p/10717744.html
Copyright © 2011-2022 走看看