精华内容
下载资源
问答
  • shiro动态URL权限控制

    2018-04-14 10:43:21
    在Spring中整合shiro,使用shiro动态URL权限控制学习教程
  • Shiro权限控制

    2019-03-13 22:48:57
    2、基于Apache Shiro实现登录认证和权限控制,重点shiro权限控制流程、自定义Realm对象控制系统认证和授权 4、动态系统菜单显示功能 5、对认证和授权数据进行缓存优化; 权限控制的两种方式: 一、粗粒度URL级别...

    概述:

    1、权限控制的两种方式:粗粒度基于URL级别权限控制、细粒度基于方法级别权限控制

    2、基于Apache Shiro实现登录认证和权限控制,重点shiro权限控制流程、自定义Realm对象控制系统认证和授权

    4、动态系统菜单显示功能

    5、对认证和授权数据进行缓存优化;


    权限控制的两种方式:

    一、粗粒度URL级别权限控制:

    可以基于Filter实现:在数据库中存放用户、权限、访问URL对应关系,当前用户访问一个URL地址,查询数据库判断用户当前具有权限,是否包含这个URL,如果包含允许访问,如果不包含则权限不足

    二、细粒度方法级别权限控制

    可以代理、自定义注解实现,访问目标对象方法,在方法上添加权限注解信息,对目标对象创建代理对象,访问真实对象先访问代理对象,在代理对象查询数据库判断是否具有注解上描述需要权限,具有权限允许访问,不具有权限,拦截访问,提示权限不足。


    权限相关数据表分析和创建(能实现)

    1、准备工作

    1)用户(User):系统登录用户

    2)权限(Permission):描述权限信息(粗粒度权限控制,在权限表描述访问资源URL信息)

    3)角色(Role):方便用户进行授权,角色就是权限的集合

    用户 *---* 角色 *---* 权限(5张表)

    4)Menu菜单,为了方便进行动态菜单管理,为不同用户定制不同系统菜单

    不同用户系统菜单,可以根据用户角色进行管理 角色*---*菜单(2张表)


    ApacheShiro简介和项目导入  官网:http://shiro.apache.org/

    Apache Shiro(更轻量,使用更简单,并不完全依赖spring,可以独立使用)

    Apache Shiro体系结构:

    1. Authentication认证:用户登录,身份识别  who are you?
    2. Authorization 授权:用户具有哪些权限、角色 what can you do?
    3. Cryptography安全数据加密
    4. Session Management 会话管理
    5. Web Integration  web系统集成
    6. Integrations 集成其他应用,spring、缓存框架

    ApacheShiro运行流程和权限控制方式分析:

    shiro主要有三大功能模块:

    1)Subject:    主体,一般指用户 ,就是shiro管理的用户(获取登录用户信息)

    2)SecurityManager安全管理器,就是shiro权限控制核心对象,在编程时,只需要操作Subject方法,底层调用SecurityManager方法,无需直接编程操作SecurityManager   管理所有Subject,可以配合内部安全组件。(类似于SpringMVC中的DispatcherServlet)

    3)Realm  :用于进行权限信息的验证,一般需要自己实现。

    2、Shiro执行流程:

    应用程序--->Subject--->SecurityManager--->Realm--->安全数据

    3、Shiro进行权限控制的四种主要方式:

    1)在程序中通过Subject编程方式进行权限控制

    2)配置Filter实现URL级别粗粒度权限控制,url级别

    3)配置代理,基于注解实现细粒度权限控制

    4)在页面中使用shiro自定义标签实现,页面显示权限控制


    SSM整合Shiro

    配置ShiroFilter实现URL级别权限控制:

    1. 配置web.xml
    <!-- shiro的Filter  -->
    	<filter> 
    		<!-- 去spring配置文件中寻找同名bean  -->
    		<filter-name>shiroFilter</filter-name>
    		<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    	</filter>
    	<filter-mapping>
    		<filter-name>shiroFilter</filter-name>
    		<url-pattern>/*</url-pattern>
    	</filter-mapping>

    2、配置applicationContext-shiro.xml

    applicationContext.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
    	xmlns:context="http://www.springframework.org/schema/context"
    	xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx"
    	xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:task="http://www.springframework.org/schema/task"
    	xsi:schemaLocation="
    		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
    		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
    		http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
    		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
    		http://www.springframework.org/schema/data/jpa 
    		http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
     
    	<!-- 扫描 @Server @Controller @Repository -->
    	<context:component-scan base-package="cn.itcast"/>
    
    	<!-- 加载properties文件 -->
    	<context:property-placeholder location="classpath:config.properties" />
    	
    	<!-- 引入外部数据文件 -->
    	<import resource="applicationContext-dataSource.xml"/>
    	
    	<!-- 引入WebService配置 -->
    	<import resource="applicationContext-webService.xml"/>
    	
    	<!-- 引入quartz配置 -->
    	<import resource="applicationContext-quartz.xml"/>
    	
    	<!-- 引入 mq配置  -->
    	<import resource="applicationContext-mq.xml"/>
    	
    	<!-- 引入 elasticsearch 配置  -->
    	<import resource="applicationContext-elasticsearch.xml"/>
    	
    	<!-- 引入 shiro权限控制配置 -->
    	<import resource="applicationContext-shiro.xml"/>
    	
    	<!-- 引入cache 配置 -->
    	<import resource="applicationContext-cache.xml"/>
    </beans>

    applicationContext-shiro.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
    	xmlns:context="http://www.springframework.org/schema/context"
    	xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx"
    	xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:task="http://www.springframework.org/schema/task"
    	xsi:schemaLocation="
    		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
    		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
    		http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
    		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
    		http://www.springframework.org/schema/data/jpa 
    		http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
    	
    	<!-- 配置Shiro核心Filter  --> 
    	<bean id="shiroFilter" 
    		class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    		<!-- 安全管理器 -->
    		<property name="securityManager" ref="securityManager" />
    		<!-- 未认证,跳转到哪个页面  -->
    		<property name="loginUrl" value="/login.html" />
    		<!-- 登录页面页面 -->
    		<property name="successUrl" value="/index.html" />
    		<!-- 认证后,没有权限跳转页面 -->
    		<property name="unauthorizedUrl" value="/unauthorized.html" />
    		<!-- shiro URL控制过滤器规则  -->
    		<property name="filterChainDefinitions">
    			<value>
    				/login.html* = anon
    				/user_login.action* = anon 
    				/validatecode.jsp* = anon
    				/css/** = anon
    				/js/** = anon
    				/images/** = anon
    				/services/** = anon 
    				/pages/base/courier.html* = perms[courier:list]
    				/pages/base/area.html* = roles[base]
    				/** = authc
    			</value>
    		</property>
    	</bean>
    	
    	<!-- 安全管理器  -->
    	<bean id="securityManager" 
    		class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    		<property name="realm" ref="bosRealm" />
    		<property name="cacheManager" ref="shiroCacheManager" />
    	</bean>
    	
    	<!-- 配置Realm -->
    	<bean id="bosRealm" class="cn.itcast.bos.realm.BosRealm">
    		<!-- 缓存区的名字 就是 ehcache.xml 自定义 cache的name -->
    		<property name="authorizationCacheName" value="bos" />
    	</bean>
    
    	//生命周期处理器
    	<bean id="lifecycleBeanPostProcessor"
    		class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
    		
    	<!-- 开启shiro注解模式  -->
    	<bean
    		class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
    		depends-on="lifecycleBeanPostProcessor" >
    		<property name="proxyTargetClass" value="true" />
    	</bean>
    		
    	<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
    		 <property name="securityManager" ref="securityManager"/>
    	</bean>
    	
    </beans>

    /login.html后面加上*是因为会有如下情况:

    http://localhost:9001/bos_management/login.html;jsessionid=68A360BFE7413C7CEAE81E5069F1EE81

    /**=authc放在最后

    ehcache.xml(额外)

    <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
    
        <diskStore path="java.io.tmpdir"/>
    
        <defaultCache
                maxElementsInMemory="10000"
                eternal="false"
                timeToIdleSeconds="120"
                timeToLiveSeconds="120"
                maxElementsOnDisk="10000000"
                diskExpiryThreadIntervalSeconds="120"
                memoryStoreEvictionPolicy="LRU">
            <persistence strategy="localTempSwap"/>
        </defaultCache>
        
        <cache name="bos"
                maxElementsInMemory="10000"
                eternal="false"
                timeToIdleSeconds="120"
                timeToLiveSeconds="120"
                maxElementsOnDisk="10000000"
                diskExpiryThreadIntervalSeconds="120"
                memoryStoreEvictionPolicy="LRU">
            <persistence strategy="localTempSwap"/>
        </cache>
        
        <cache name="standard"
                maxElementsInMemory="10000"
                eternal="false"
                timeToIdleSeconds="120"
                timeToLiveSeconds="120"
                maxElementsOnDisk="10000000"
                diskExpiryThreadIntervalSeconds="120"
                memoryStoreEvictionPolicy="LRU">
            <persistence strategy="localTempSwap"/>
        </cache>
        
    </ehcache>
    

    Shiro过滤器配置参考:

    anon    未认证可以访问

    authc   认证后可以访问

    perms  需要特定权限才能访问

    roles    需要特定角色才能访问

    user     需要特定用户才能访问

    port     需要特定端口才能访问(不常用)

    rest     根据指定HTTP请求才能访问(不常用)


    自定义Realm实现用户登录功能:(登录成功保存用户信息)

    1.提供login登录方法:

    public String login() {
    		// 用户名和密码 都保存在model中
    		// 基于shiro实现登录
    		Subject subject = SecurityUtils.getSubject();
    
    		// 用户名和密码信息
    		AuthenticationToken token = new UsernamePasswordToken(
    				model.getUsername(), model.getPassword());
    		try {
    			subject.login(token);
    			// 登录成功
    			// 将用户信息 保存到 Session
    			return SUCCESS;//跳转成功页面
    		} catch (AuthenticationException e) {
    			// 登录失败
    			e.printStackTrace();
    			return LOGIN;//重定向到登录页面
    		}
    	}
    
    	
    	public String logout() {
    		// 基于shiro完成退出
    		Subject subject = SecurityUtils.getSubject();
    		subject.logout();
    
    		return SUCCESS;
    	}
    

    Shiro执行流程:应用程序--->Subject--->SecurityManager--->Realm--->安全数据

     

    2. 自定义Realm对象,实现认证方法(实际开发中,只需要继承AuthorizingRealm)

    package cn.itcast.bos.realm;
    
    import java.util.List;
    
    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.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.subject.Subject;
    import org.springframework.beans.factory.annotation.Autowired;
    
    import cn.itcast.bos.domain.system.Permission;
    import cn.itcast.bos.domain.system.Role;
    import cn.itcast.bos.domain.system.User;
    import cn.itcast.bos.service.system.PermissionService;
    import cn.itcast.bos.service.system.RoleService;
    import cn.itcast.bos.service.system.UserService;
    
    // 自定义Realm ,实现安全数据 连接
    // @Service("bosRealm")
    public class BosRealm extends AuthorizingRealm {
    
    	@Autowired
    	private UserService userService;
    
    	@Autowired
    	private RoleService roleService;
    
    	@Autowired
    	private PermissionService permissionService;
    
    	@Override
    	// 授权...
    	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) {
    		System.out.println("shiro 授权管理...");
    		SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
    		// 根据当前登录用户 查询对应角色和权限
    		Subject subject = SecurityUtils.getSubject();
    		User user = (User) subject.getPrincipal();
    		// 调用业务层,查询角色
    		List<Role> roles = roleService.findByUser(user);
    		for (Role role : roles) {
    			authorizationInfo.addRole(role.getKeyword());
    		}
    		// 调用业务层,查询权限
    		List<Permission> permissions = permissionService.findByUser(user);
    		for (Permission permission : permissions) {
    			authorizationInfo.addStringPermission(permission.getKeyword());
    		}
    
    		return authorizationInfo;
    	}
    
    	@Override
    	// 认证...
    	protected AuthenticationInfo doGetAuthenticationInfo(
    			AuthenticationToken token) throws AuthenticationException {
    		System.out.println("shiro 认证管理... ");
    
    		// 转换token
    		UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
    
    		// 根据用户名 查询 用户信息
    		User user = userService.findByUsername(usernamePasswordToken
    				.getUsername());
    		if (user == null) {
    			// 用户名不存在
    			// 参数一: 期望登录后,保存在Subject中信息
    			// 参数二: 如果返回为null 说明用户不存在,报用户名
    			// 参数三 :realm名称
    			return null;
    		} else {
    			// 用户名存在
    			// 当返回用户密码时,securityManager安全管理器,自动比较返回密码和用户输入密码是否一致
    			// 如果密码一致 登录成功, 如果密码不一致 报密码错误异常
    			return new SimpleAuthenticationInfo(user, user.getPassword(),
    					getName());
    		}
    
    	}
    
    }
    

    3.在shiroFilter配置中将user_login.action放行

    6、注意问题

    1)当用户名不存在时,抛出异常

          2)当密码错误时,抛出异常

          


    对页面访问添加授权控制(重要)

    3.修改applicationContext-shiro.xml配置shiroFilter权限过滤程序

    4.访问courier.html时,因为需要特别权限,调用自定义Realm的doGetAuthorizationInfo进行授权,如果没有权限,跳转


    自定义Realm实现用户授权功能:

    1、在数据表中导入预定义角色、权限数据,导入t_user_role_permission.sql

    1)t_permission权限数据

    2)t_role角色数据

    3)t_role_permission角色权限关系表

    4)t_user用户表

    5)t_user_role用户角色关系表

    2、实现Realm的授权方法

    
    	@Override
    	// 授权...
    	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) {
    		System.out.println("shiro 授权管理...");
    		SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
    		// 根据当前登录用户 查询对应角色和权限
    		Subject subject = SecurityUtils.getSubject();
    		User user = (User) subject.getPrincipal();
    		// 调用业务层,查询角色
    		List<Role> roles = roleService.findByUser(user);
    		for (Role role : roles) {
    			authorizationInfo.addRole(role.getKeyword());
    		}
    		// 调用业务层,查询权限
    		List<Permission> permissions = permissionService.findByUser(user);
    		for (Permission permission : permissions) {
    			authorizationInfo.addStringPermission(permission.getKeyword());
    		}
    
    		return authorizationInfo;
    	}
    

    配置注解实现业务层方法级别权限控制:

    自定义注解(加在方法上,在注解中描述需要权限信息),对目标业务对象创建代理对象,在代理方法中使用反射技术读取注解信息,获取需要权限,查询当前登录用户具有权限是否满足;

    1. 配置applicationContext-shiro.xml激活注解
    <!-- 开启shiro注解模式  -->
    	<bean
    		class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
    		depends-on="lifecycleBeanPostProcessor" >
    		<property name="proxyTargetClass" value="true" />
    	</bean>
    		
    	<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
    		 <property name="securityManager" ref="securityManager"/>
    	</bean>
    1. 配置快递员添加方法,业务层使用shiro注解

    3、注意问题:

    1)针对CourierServiceImpl对象进行代理,而不是针对接口进行代理

    解决:

    applicationContext-dataSource.xml

    applicationContext-shiro.xml

    2)使用方法注解进行权限控制,当权限不足时,代理对象抛出一个异常

    21:53:00,549 ERROR DefaultDispatcherErrorHandler:42 - Exception occurred during processing request: Subject does not have permission [courier:add]

    org.apache.shiro.authz.UnauthorizedException: Subject does not have permission [courier:add]

    Caused by: org.apache.shiro.authz.AuthorizationException: Not authorized to invoke method: public void cn.itcast.bos.service.base.impl.CourierServiceImpl.save(cn.itcast.bos.domain.base.Courier)

    at org.apache.shiro.authz.aop.AuthorizingAnnotationMethodInterceptor.assertAuthorized(AuthorizingAnnotationMethodInterceptor.java:90)

    ... 122 more


    通过shiro自定义标签控制页面元素显示(控制功能按钮的权限):

    在页面引入shiro标签库

    通过shiro自带标签控制按钮是否显示


    三、shiro几种权限控制方式小结:

    第一种:URL级别粗粒度权限控制(重点)

    • 配置web.xml的shiroFilter拦截 /*
    • 在spring的applicationContext*.xml配置文件中配置同名的bean,配置filterChainDefinitions拦截控制规则
    • xxx.html*=anon(未登录可以访问)
    • xxx.html*=authc(必须登录才能访问)
    • xxx.html*=perms[权限](需要特定权限才能访问 )
    • xxx.html*=roles[角色](需要特定角色才能访问)

    第二种:方法级别细粒度权限控制:

    在spring的applicationContext*.xml配置spring aop对spring管理bean对象开启shiro注解支持

    @RequiresPermissions(权限) 需要特定权限才能访问

    @RequiresRoles(角色) 需要特定角色才能访问

    @RequiresAuthentication  需要认证才能访问

    第三种:通过shiro自定义标签,实现页面元素显示控制:

    <shiro:authenticated> 登录后才能访问

    <shiro:hasPermission name=”abc”> 需要特定权限才能访问

    <shior:hasRole name=”abc”> 需要特定角色才能访问

    第四种:在程序中通过代码,判断用户是否具有指定权限(不太常用,有代码侵入)

    补充:权限表达式“:”代表子权限

    权限courier包含courier:add、courier:list、courier:*,如果用户具有父权限,操作所有子权限功能

    比如:权限courier:list 包含courier:list:10


    基础介绍:https://www.cnblogs.com/tohxyblog/p/6870261.html


    Springboot整合Shiro:

    pom文件:

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-starter</artifactId>
      <version>2.0.1</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-spring</artifactId>
      <version>1.4.0</version>
    </dependency>

    user.java

    package com.wsl.bean;
    
    import java.util.Set;
    
    public class User {
        private String id;
        private String userName;
        private String password;
        /**
         * 用户对应的角色集合
         */
        private Set<Role> roles;
        //省略set、get方法等.....
    }

    Role.java(角色对应实体类):
     

    package com.wsl.bean;
    
    import java.util.Set;
    
    public class Role {
    
        private String id;
        private String roleName;
        /**
         * 角色对应权限集合
         */
        private Set<Permissions> permissions;
        //省略set、get方法等.....
    }

    Permissions.java(权限对应实体类):

    public class Permissions {
        private String id;
        private String permissionsName;
        //省略set、get方法等.....
    }

    LoginServiceImpl.java:

    package com.wsl.service.impl;
    
    import com.wsl.bean.Permissions;
    import com.wsl.bean.Role;
    import com.wsl.bean.User;
    import com.wsl.service.LoginService;
    import org.springframework.stereotype.Service;
    
    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.Map;
    import java.util.Set;
    
    @Service
    public class LoginServiceImpl implements LoginService {
    
        @Override
        public User getUserByName(String getMapByName) {
            //模拟数据库查询,正常情况此处是从数据库或者缓存查询。
            return getMapByName(getMapByName);
        }
    
        /**
         * 模拟数据库查询
         * @param userName
         * @return
         */
        private User getMapByName(String userName){
            //共添加两个用户,两个用户都是admin一个角色,
            //wsl有query和add权限,zhangsan只有一个query权限
            Permissions permissions1 = new Permissions("1","query");
            Permissions permissions2 = new Permissions("2","add");
            Set<Permissions> permissionsSet = new HashSet<>();
            permissionsSet.add(permissions1);
            permissionsSet.add(permissions2);
            Role role = new Role("1","admin",permissionsSet);
            Set<Role> roleSet = new HashSet<>();
            roleSet.add(role);
            User user = new User("1","wsl","123456",roleSet);
            Map<String ,User> map = new HashMap<>();
            map.put(user.getUserName(), user);
    
            Permissions permissions3 = new Permissions("3","query");
            Set<Permissions> permissionsSet1 = new HashSet<>();
            permissionsSet1.add(permissions3);
            Role role1 = new Role("2","user",permissionsSet1);
            Set<Role> roleSet1 = new HashSet<>();
            roleSet1.add(role1);
            User user1 = new User("2","zhangsan","123456",roleSet1);
            map.put(user1.getUserName(), user1);
            return map.get(userName);
        }
    }

    自定义Realm用于查询用户的角色和权限信息并保存到权限管理器:

    CustomRealm.java

    package com.wsl.shiro;
    
    import com.wsl.bean.Permissions;
    import com.wsl.bean.Role;
    import com.wsl.bean.User;
    import com.wsl.service.LoginService;
    import org.apache.shiro.authc.AuthenticationException;
    import org.apache.shiro.authc.AuthenticationInfo;
    import org.apache.shiro.authc.AuthenticationToken;
    import org.apache.shiro.authc.SimpleAuthenticationInfo;
    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.springframework.beans.factory.annotation.Autowired;
    
    public class CustomRealm extends AuthorizingRealm {
    
        @Autowired
        private LoginService loginService;
    
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            //获取登录用户名
            String name = (String) principalCollection.getPrimaryPrincipal();
            //根据用户名去数据库查询用户信息
            User user = loginService.getUserByName(name);
            //添加角色和权限
            SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
            for (Role role : user.getRoles()) {
                //添加角色
                simpleAuthorizationInfo.addRole(role.getRoleName());
                //添加权限
                for (Permissions permissions : role.getPermissions()) {
                    simpleAuthorizationInfo.addStringPermission(permissions.getPermissionsName());
                }
            }
            return simpleAuthorizationInfo;
        }
    
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            //加这一步的目的是在Post请求的时候会先进认证,然后在到请求
            if (authenticationToken.getPrincipal() == null) {
                return null;
            }
            //获取用户信息
            String name = authenticationToken.getPrincipal().toString();
            User user = loginService.getUserByName(name);
            if (user == null) {
                //这里返回后会报出对应异常
                return null;
            } else {
                //这里验证authenticationToken和simpleAuthenticationInfo的信息
                SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(name, user.getPassword().toString(), getName());
                return simpleAuthenticationInfo;
            }
        }
    }
    

    把CustomRealm和SecurityManager等加入到spring容器:

    ShiroConfig.java:

    package com.wsl.config;
    
    import com.wsl.shiro.CustomRealm;
    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.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import java.util.HashMap;
    import java.util.Map;
    
    @Configuration
    public class shiroConfig {
        //不加这个注解不生效,具体不详
        @Bean
        @ConditionalOnMissingBean
        public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
            DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
            defaultAAP.setProxyTargetClass(true);
            return defaultAAP;
        }
    
        //将自己的验证方式加入容器
        @Bean
        public CustomRealm myShiroRealm() {
            CustomRealm customRealm = new CustomRealm();
            return customRealm;
        }
    
        //权限管理,配置主要是Realm的管理认证
        @Bean
        public SecurityManager securityManager() {
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            securityManager.setRealm(myShiroRealm());
            return securityManager;
        }
    
        //Filter工厂,设置对应的过滤条件和跳转条件
        @Bean
        public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
            shiroFilterFactoryBean.setSecurityManager(securityManager);
            Map<String, String> map = new HashMap<>();
            //登出
            map.put("/logout", "logout");
            //对所有用户认证
            map.put("/**", "authc");
            //登录
            shiroFilterFactoryBean.setLoginUrl("/login");
            //首页
            shiroFilterFactoryBean.setSuccessUrl("/index");
            //错误页面,认证不通过跳转
            shiroFilterFactoryBean.setUnauthorizedUrl("/error");
            shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
            return shiroFilterFactoryBean;
        }
    
        //加入注解的使用,不加入这个注解不生效
        @Bean
        public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
            AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
            authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
            return authorizationAttributeSourceAdvisor;
        }
    }

    LoginController.java:

    package com.wsl.controller;
    
    import com.wsl.bean.User;
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.AuthenticationException;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.authz.AuthorizationException;
    import org.apache.shiro.authz.annotation.RequiresPermissions;
    import org.apache.shiro.authz.annotation.RequiresRoles;
    import org.apache.shiro.subject.Subject;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class LoginController {
    
        @RequestMapping("/login")
        public String login(User user) {
            //添加用户认证信息
            Subject subject = SecurityUtils.getSubject();
            UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(
                    user.getUserName(),
                    user.getPassword()
            );
            try {
                //进行验证,这里可以捕获异常,然后返回对应信息
                subject.login(usernamePasswordToken);
    //            subject.checkRole("admin");
    //            subject.checkPermissions("query", "add");
            } catch (AuthenticationException e) {
                e.printStackTrace();
                return "账号或密码错误!";
            } catch (AuthorizationException e) {
                e.printStackTrace();
                return "没有权限";
            }
            return "login success";
        }
         //注解验角色和权限
        @RequiresRoles("admin")
        @RequiresPermissions("add")
        @RequestMapping("/index")
        public String index() {
            return "index!";
        }
    }

    注解验证角色和权限的话无法捕捉异常,从而无法正确的返回给前端错误信息,所以我加了一个类用于拦截异常,具体代码如下

    MyExceptionHandler.java

    package com.wsl.filter;
    
    import lombok.extern.slf4j.Slf4j;
    import org.apache.shiro.authz.AuthorizationException;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    
    @ControllerAdvice
    @Slf4j
    public class MyExceptionHandler {
    
        @ExceptionHandler
        @ResponseBody
        public String ErrorHandler(AuthorizationException e) {
            log.error("没有通过权限验证!", e);
            return "没有通过权限验证!";
        }
    }

     

     

     

     

     

     

    展开全文
  • doGetAuthenticationInfo方法进行认证,但是我们在实现doGetAuthorizationInfo权限控制这个方法的时候发现以下两个问题: 第一个问题:我们在ShiroConfig中配置链接权限的时候,每次只要有一个新的链接,或则权限...

    问题描述

    之前我们整合Shiro,完成了登录认证和权限管理的实现,登录认证没什么说的,需要实现AuthorizingRealm中的
    doGetAuthenticationInfo方法进行认证,但是我们在实现doGetAuthorizationInfo权限控制这个方法的时候发现以下两个问题:

    • 第一个问题:我们在ShiroConfig中配置链接权限的时候,每次只要有一个新的链接,或则权限需要改动,都要在ShiroConfig.java中进行权限的修改。而且改动后还需要重新启动程序新的权限才会生效,很麻烦。解决办法就是将这些链接的权限存入数据库,在前端可以提供增删改查的功能,在配置文件中编写权限的时候从数据库读取,当权限发生变更的时候利用ShiroFilterFactoryBean的清空功能,先clear,再set。这样就可以做到到动态的管理权限了。

    • 第二个问题:每次在访问设置了权限的页面时,都会去执行doGetAuthorizationInfo方法来判断当前用户是否具备访问权限,由于在实际情况中,权限是不会经常改变的。解决办法就是进行缓存处理。

    第一个问题解决步骤

    (1) 建立数据库表
    我们从ShiroConfig中的filterChainDefinitionMap.put("/add", "perms[权限添加]"); 配置可以看出,我们需要存储链接,和链接需要具备的权限这两个关键字段。还有这个权限的读取是有顺序的,所以还要进行排序控制,所以我新建表为:

    -- ----------------------------
    -- Table structure for sys_permission_init
    -- ----------------------------
    DROP TABLE IF EXISTS `sys_permission_init`;
    CREATE TABLE `sys_permission_init` (
      `id` varchar(255) NOT NULL,
      `url` varchar(255) DEFAULT NULL COMMENT '链接地址',
      `permission_init` varchar(255) DEFAULT NULL COMMENT '需要具备的权限',
      `sort` int(50) DEFAULT NULL COMMENT '排序',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    

    当然可以按实际情况进行表的设计,这里只做简单学习。
    (2) 改造ShiroConfig.java

    @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 filterChainDefinitionMap = new LinkedHashMap();
      // 配置不会被拦截的链接 顺序判断
      filterChainDefinitionMap.put("/static/**", "anon");
      filterChainDefinitionMap.put("/ajaxLogin", "anon");
      // 配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了
      filterChainDefinitionMap.put("/logout", "logout");
      filterChainDefinitionMap.put("/add", "perms[权限添加]");
      // :这是一个坑呢,一不小心代码就不好使了;
      // 
      filterChainDefinitionMap.put("/**", "authc");
      shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
      System.out.println("Shiro拦截器工厂类注入成功");
      return shiroFilterFactoryBean;
    }
    

    改造后:

    @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.
      Map filterChainDefinitionMap = new LinkedHashMap();
      //从数据库获取
      List list = sysPermissionInitService.selectAll();
      for (SysPermissionInit sysPermissionInit : list) {
        filterChainDefinitionMap.put(sysPermissionInit.getUrl(),
            sysPermissionInit.getPermissionInit());
      }
      shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
      System.out.println("Shiro拦截器工厂类注入成功");
      return shiroFilterFactoryBean;
    }
    

    这里的selectAll()就是从数据库查询之前创建的权限管理列表,这里就不贴具体的查询代码了。
    (3) 添加权限
    在数据库中添加权限如下图:
    在这里插入图片描述
    现在启动程序,在控制台可以发现启动的时候程序在数据库查询了权限的列表信息。做到这步之后还没有达到动态的目的,比如现在到数据库手动修改/add链接的权限,这时不重启程序,权限是不会修改的。

    (4) 动态更改权限实现
    ShiroService.java

    /**
     * @author 作者: z77z
     * @date 创建时间:2017年2月15日 下午4:16:07
     */
    @Service
    public class ShiroService {
      
      @Autowired
      ShiroFilterFactoryBean shiroFilterFactoryBean;
      
      @Autowired
      SysPermissionInitService sysPermissionInitService;
      
      /**
       * 初始化权限
       */
      public Map loadFilterChainDefinitions() {
        // 权限控制map.从数据库获取
        Map filterChainDefinitionMap = new LinkedHashMap();
        List list = sysPermissionInitService.selectAll();
        for (SysPermissionInit sysPermissionInit : list) {
          filterChainDefinitionMap.put(sysPermissionInit.getUrl(),
              sysPermissionInit.getPermissionInit());
        }
        return filterChainDefinitionMap;
      }
      /**
       * 重新加载权限
       */
      public void updatePermission() {
        synchronized (shiroFilterFactoryBean) {
          AbstractShiroFilter shiroFilter = null;
          try {
            shiroFilter = (AbstractShiroFilter) shiroFilterFactoryBean
                .getObject();
          } catch (Exception e) {
            throw new RuntimeException(
                "get ShiroFilter from shiroFilterFactoryBean error!");
          }
          PathMatchingFilterChainResolver filterChainResolver = (PathMatchingFilterChainResolver) shiroFilter
              .getFilterChainResolver();
          DefaultFilterChainManager manager = (DefaultFilterChainManager) filterChainResolver
              .getFilterChainManager();
          // 清空老的权限控制
          manager.getFilterChains().clear();
          shiroFilterFactoryBean.getFilterChainDefinitionMap().clear();
          shiroFilterFactoryBean
              .setFilterChainDefinitionMap(loadFilterChainDefinitions());
          // 重新构建生成
          Map chains = shiroFilterFactoryBean
              .getFilterChainDefinitionMap();
          for (Map.Entry entry : chains.entrySet()) {
            String url = entry.getKey();
            String chainDefinition = entry.getValue().trim()
                .replace(" ", "");
            manager.createChain(url, chainDefinition);
          }
          System.out.println("更新权限成功!!");
        }
      }
    }
    

    这样,可以在修改权限之后,执行updatePermission()这个方法,权限就会先被clear,然后重新查询权限列表后再set。动态修改就实现了!

    注意:在本学习项目里面,我在设置登录用户的权限的时候是写死了的,所以每个登录用户权限都是一样的,实际开发中在MyShiroRealm文件中设置登录用户的权限是从数据库获取的。还有在实际开发中sys_permission_init权限管理这种表是会在前端提供增删改查功能的,我学习的时候是直接在数据库手动修改。说到底,本人很懒!

    第二个问题的解决步骤

    我们知道Shiro 提供了一系列让我们自己实现的接口,包括org.apache.shiro.cache.CacheManagerorg.apache.shiro.cache.Cache 等接口。那么我们要对这些做实现,就实现了ShiroSession 和用户认证信息、用户缓存信息等的缓存,存储。我们可以用缓存,如 RedismemcacheEHCache 等,甚至我们可以用数据库,如 OracleMysql 等,都可以,只有效率的快慢问题,功能都可以达到。

    那么我的教程是采用了 Redis ,而且是用了JedisJedis 可以实现poolhash 的集群Redis

    本来我想是在网上学习学习,自己实现redis的集成。最后发现已经有大神已经做了这个插件,对shiro提供的CacheManagerCache ,这些接口使用redis都有了很好的实现。我就不需要再费心学习了,我们就直接拿来用。

    pom.xml依赖添加

    <dependency>
        <groupId>org.crazycake</groupId>
        <artifactId>shiro-redis</artifactId>
        <version>2.4.2.1-RELEASE</version>
    </dependency>
    

    改造ShiroConfig.java文件

    /**
     * @author 作者 z77z
     * @date 创建时间:2017年2月10日 下午1:16:38
     * 
     */
    @Configuration
    public class ShiroConfig {
      @Autowired
      SysPermissionInitService sysPermissionInitService;
      @Value("${spring.redis.host}")
      private String host;
      @Value("${spring.redis.port}")
      private int port;
      
      @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.
        Map filterChainDefinitionMap = new LinkedHashMap();
        // 从数据库获取
        List list = sysPermissionInitService.selectAll();
        for (SysPermissionInit sysPermissionInit : list) {
          filterChainDefinitionMap.put(sysPermissionInit.getUrl(),
              sysPermissionInit.getPermissionInit());
        }
        shiroFilterFactoryBean
            .setFilterChainDefinitionMap(filterChainDefinitionMap);
        System.out.println("Shiro拦截器工厂类注入成功");
        return shiroFilterFactoryBean;
      }
      @Bean
      public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 设置realm.
        securityManager.setRealm(myShiroRealm());
        // 自定义缓存实现 使用redis
        securityManager.setCacheManager(cacheManager());
        // 自定义session管理 使用redis
        securityManager.setSessionManager(SessionManager());
        return securityManager;
      }
      /**
       * 身份认证realm; (这个需要自己写,账号密码校验;权限等)
       * 
       * @return
       */
      @Bean
      public MyShiroRealm myShiroRealm() {
        MyShiroRealm myShiroRealm = new MyShiroRealm();
        return myShiroRealm;
      }
      /**
       * 配置shiro redisManager
       * 
       * @return
       */
      public RedisManager redisManager() {
        RedisManager redisManager = new RedisManager();
        redisManager.setHost(host);
        redisManager.setPort(port);
        redisManager.setExpire(1800);// 配置过期时间
        // redisManager.setTimeout(timeout);
        // redisManager.setPassword(password);
        return redisManager;
      }
      /**
       * cacheManager 缓存 redis实现
       * 
       * @return
       */
      public RedisCacheManager cacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        return redisCacheManager;
      }
      /**
       * RedisSessionDAO shiro sessionDao层的实现 通过redis
       */
      public RedisSessionDAO redisSessionDAO() {
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        return redisSessionDAO;
      }
      /**
       * shiro session的管理
       */
      public DefaultWebSessionManager SessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setSessionDAO(redisSessionDAO());
        return sessionManager;
      }
    }
    

    这里,因为使用的是redis来做容器缓存,所以要创建redisManager来配置shiroSessionManager()cacheManager()这两个类都是插件给我们写好了的,里面就是对shiro提供的接口的redis实现方式。

    使用插件就是这么简单,直接启动程序,多访问几次具有权限的页面,查看控制台发现,权限认证方法:MyShiroRealm.doGetAuthorizationInfo()会只执行了一次。说明我们的缓存生效了。

    总结

    到此,我们集成shiroredis,学习了一下功能的实现:

    1. 用户必须要登陆之后才能访问定义链接,否则跳转到登录页面,被禁用户不能登录。并且对一些敏感操作链接设置权限,只有满足权限的才可以访问。
    2. 每个链接的权限信息保存在数据库,可以动态进行设置,并且热加载权限。
    3. 使用redisshiro的用户信息进行缓存,不用每次都去执行MyShiroRealm.doGetAuthorizationInfo()权限认证方法。
    4. 之前有很多同学下载我的项目时,运行会报错,那是因为最近都在不断修改提交,有可能会出现版本问题,现在 我在我的码云上面创建了stable_version分支,都是可以跑起来的。sqltable放在resource目录下面。
    5. 下一博,我应该会写对在线用户的管理,踢出登录的功能学习记录。

    项目地址: https://gitee.com/z77z/springboot_mybatisplus

    展开全文
  • 本文将带你从0开始搭建一个Springboot+shiro动态权限控制 首先你得知道什么是Shiro? Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解API,你可以快速、...

    本文将带你从0开始搭建一个Springboot+shiro动态权限控制


    首先你得知道什么是Shiro?

    Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解API,你可以快速、轻松地获取任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
    官网:[点我进入]

    开发环境:

    <!-- shiro -->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>1.4.0</version>
    </dependency>
    
    <!-- springboot -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
        <relativePath/>
    </parent>

     

    1.编写LoginController

        @PostMapping("/login")
        @ApiOperation("登录系统")
        @Log(module = "登录系统", description = "登陆系统")
        public ResultResponse login(@RequestBody LoginInoutDTO inoutDTO) {
            if (StringUtils.isBlank(inoutDTO.getUsername()) || StringUtils.isBlank(inoutDTO.getPassword())) {
                return new ResultResponse(false, ErrorDetail.RC_0401001.getIndex(), "参数不全", null);
            }
    
            //当前登录的用户
            Subject currentUser = SecurityUtils.getSubject();
            // 如果这个用户没有登录,进行登录功能
            if (!currentUser.isAuthenticated()) {
                try {
                    // 验证身份和登陆
                    UsernamePasswordToken token = new UsernamePasswordToken(inoutDTO.getUsername(), inoutDTO.getPassword());
                    currentUser.login(token);
                    permissionsConfig.updatePermission();
                } catch (UnknownAccountException e) {
                    return new ResultResponse(false, ErrorDetail.RC_0401001.getIndex(), "此账号不存在!", null);
                } catch (IncorrectCredentialsException e) {
                    return new ResultResponse(false, ErrorDetail.RC_0401001.getIndex(), "用户名或者密码错误,请重试!", null);
                } catch (LockedAccountException e) {
                    return new ResultResponse(false, ErrorDetail.RC_0401001.getIndex(), "该账号已被锁定,请联系管理员!", null);
                } catch (AuthenticationException e) {
                    return new ResultResponse(false, ErrorDetail.RC_0401001.getIndex(), "未知错误,请联系管理员!", null);
                }
            }
            return new ResultResponse(true, ErrorDetail.RC_0000000.getIndex(), "登录成功", userInfo);
            }
        }

     

    2.自定义Realm

    /**
     * Created by Eagga_Lo on 2019/11/26 14:19
     */
    @Slf4j
    public class ShiroRealm extends AuthorizingRealm {
    
        @Autowired
        UserService userService;
    
        @Autowired
        RoleService roleService;
    
        @Autowired
        MenuService menuService;
    
        /**
         * 授权
         */
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            log.info("--------------开始授权操作--------------");
    
            SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
            User user = (User) principalCollection.getPrimaryPrincipal();
            log.info("user:{}", user.toString());
    
            Set<String> rolesSet = new HashSet<>();
            Set<String> permsSet = new HashSet<>();
    
            try {
                List<Role> roleList = roleService.selectRoleByUserId(user.getById());
                for (Role role : roleList) {
                    rolesSet.add(role.getRoleId());
                    List<XtZzjgMenu> menuList = menuService.selectMenuByRoleId(role.getRoleId());
                    for (XtZzjgMenu menu : menuList) {
                        permsSet.add(menu.getResource());
                    }
                }
    
                log.info("--------------当前用户的角色:{}--------------", rolesSet);
                log.info("--------------当前角色的权限:{}--------------", permsSet);
    
                //将查到的权限和角色分别传入authorizationInfo中
                authorizationInfo.setStringPermissions(permsSet);
                authorizationInfo.setRoles(rolesSet);
                log.info("--------------赋予角色和权限成功---------------");
            } catch (Exception e) {
                e.printStackTrace();
                log.info("--------------赋予角色和权限失败{}--------------", e.getMessage());
            }
            return authorizationInfo;
    
        }
    
        /**
         * 认证
         */
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            log.info("---------------开始认证操作---------------");
    
            UsernamePasswordToken tokenInfo = (UsernamePasswordToken) authenticationToken;
            // 获取用户输入的账号
            String username = tokenInfo.getUsername();
            // 获取用户输入的密码
            String password = String.valueOf(tokenInfo.getPassword());
    
            //数据库中的user
            User user = userService.selectByPrimaryKey(username);
        
            if (user == null) {
                return null;
            }
    
            // 判断是否为冻结状态
            if ("Y".equals(user.getSfsc())) {
                throw new LockedAccountException();
            }
    
            /*
             * 进行验证 -> 注:shiro会自动验证密码
             * 参数1:principal -> 放对象就可以在页面任意地方拿到该对象里面的值
             * 参数2:hashedCredentials -> 密码
             * 参数3:credentialsSalt -> 设置盐值
             * 参数4:realmName -> 自定义的Realm
             */
            SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user, user.getPassword(), ByteSource.Util.bytes(user.getSalt()), getName());
            log.info("--------------- 认证完毕! ---------------");
            return authenticationInfo;
        }
    
    
    }
    

     

    3.前后端分离需要过滤OPTIONS请求,否则可能出现302错误。

    @Slf4j
    public class MyFormAuthenticationFilter extends FormAuthenticationFilter {
    
        public MyFormAuthenticationFilter() {
            super();
        }
    
        /**
         * 过滤OPTIONS请求  不配置可能会报302错误
         */
        @Override
        protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
            //过滤OPTIONS请求
            String method = ((HttpServletRequest) request).getMethod().toUpperCase();
            if (((HttpServletRequest) request).getMethod().toUpperCase().equals("OPTIONS")) {
                return true;
            }
            return super.isAccessAllowed(request, response, mappedValue);
        }
    
        @Override
        protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
            if (isLoginRequest(request, response)) {
                if (isLoginSubmission(request, response)) {
                    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.");
                    }
                    return true;
                }
            } else {
                HttpServletRequest req = (HttpServletRequest) request;
                HttpServletResponse resp = (HttpServletResponse) response;
                if (req.getMethod().equals(RequestMethod.OPTIONS.name())) {
                    resp.setStatus(HttpStatus.OK.value());
                    return true;
                }
    
                if (log.isTraceEnabled()) {
                    log.trace("Attempting to access a path which requires authentication.  Forwarding to the " +
                            "Authentication url [" + getLoginUrl() + "]");
                }
                //前端Ajax请求时requestHeader里面带一些参数,用于判断是否是前端的请求
                String test = req.getHeader("test");
                if (test != null || req.getHeader("wkcheck") != null) {
                    //前端Ajax请求,则不会重定向
                    resp.setHeader("Access-Control-Allow-Origin", req.getHeader("Origin"));
                    resp.setHeader("Access-Control-Allow-Credentials", "true");
                    resp.setContentType("application/json; charset=utf-8");
                    resp.setCharacterEncoding("UTF-8");
                    PrintWriter out = resp.getWriter();
                    JSONObject result = new JSONObject();
                    result.put("message", "登录失效");
                    result.put("resultCode", 1000);
                    out.println(result);
                    out.flush();
                    out.close();
                } else {
                    saveRequestAndRedirectToLogin(request, response);
                }
                return false;
            }
        }
    }
    

     

    4.自定义过滤器继承PermissionsAuthorizationFilter解决由于权限不足直接报302错误。

    /**
     * 自定义url过滤权限
     */
    @Slf4j
    public class MyPermissionsAuthorizationFilter extends PermissionsAuthorizationFilter {
    
        /**
         * 解决权限不足302问题
         */
        @Override
        protected boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
            HttpServletRequest httpRequest = (HttpServletRequest) request;
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            String requestUrl = httpRequest.getServletPath();
            log.info("请求的url:  " + requestUrl);
    
            // 检查是否拥有访问权限
            Subject subject = this.getSubject(request, response);
            if (subject.getPrincipal() == null) {
                this.saveRequestAndRedirectToLogin(request, response);
            } else {
                // 转换成http的请求和响应
                HttpServletRequest req = (HttpServletRequest) request;
                HttpServletResponse resp = (HttpServletResponse) response;
    
                // 获取请求头的值
                String header = req.getHeader("X-Requested-With");
                // ajax 的请求头里有X-Requested-With: XMLHttpRequest 正常请求没有
                if ("XMLHttpRequest".equals(header)) {
                    resp.setContentType("text/json,charset=UTF-8");
                    ResultResponse resultResponse = new ResultResponse(false, "302", "权限不足", null);
                    log.info("----------权限不足---------");
                    resp.getWriter().print(resultResponse);
                } else {
                    //正常请求
                    String unauthorizedUrl = this.getUnauthorizedUrl();
                    if (StringUtils.hasText(unauthorizedUrl)) {
                        WebUtils.issueRedirect(request, response, unauthorizedUrl);
                    } else {
                        WebUtils.toHttp(response).sendError(401);
                    }
                }
            }
            return false;
        }
    }
    

     

    5.自定义过滤器继承AuthorizationFilter,原生过滤器必须满足所有角色都有才允许访问。

    /**
     * 原生的角色过滤器RolesAuthorizationFilter 默认是必须同时满足roles[admin,guest]才有权限
     */
    @Slf4j
    public class MyRolesAuthorizationFilter extends AuthorizationFilter {
    
        @Override
        protected boolean isAccessAllowed(ServletRequest req, ServletResponse resp, Object mappedValue) throws Exception {
            Subject subject = getSubject(req, resp);
            String[] rolesArray = (String[]) mappedValue;
            // 没有角色限制,有权限访问
            if (rolesArray == null || rolesArray.length == 0) {
                return true;
            }
            for (String s : rolesArray) {
                //若当前用户是rolesArray中的任何一个,则有权限访问
                if (subject.hasRole(s)) {
                    return true;
                }
            }
            return false;
        }
    
    }
    

     

    6.自定义Session管理器 继承DefaultWebSessionManager

    注意:此处没有采用Redis作为session缓存,让前端加请求头进行session传递。请求头必须与“AUTHORIZATION ”的值一致。

    /**
     * Created by Eagga_Lo on 2019/11/26 14:16
     */
    
    @Slf4j
    public class MySessionManager extends DefaultWebSessionManager {
    
        private static final String AUTHORIZATION = "Authorization";
    
        /**
         * 采用自定义SessionManager的方式自己来管理sessionid的获取
         * 这样前端需要做的就是每次请求,要把后端传给它的sessionid即token
         * 放到请求头里key为Authorization,value为后台传过来的token
         * 然后用自定义的SessionManager获取就可以了
         */
        @Override
        protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
            //前端ajax的headers中必须传入Authorization的值
            String sessionId = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
            //如果请求头中有 Authorization 则其值为sessionId
            if (!StringUtils.isEmpty(sessionId)) {
                request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "Stateless request");
                request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sessionId);
                request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
                return sessionId;
            } else {
                //否则按默认规则从cookie取sessionId
                return super.getSessionId(request, response);
            }
        }
    }
    

    7.自定义ShiroConfig配置类

    /**
     * Created by Eagga_Lo on 2019/11/26 14:14
     */
    
    @Configuration
    public class ShiroConfig {
    
        @Autowired
        MenuMapper menuMapper;
    
        @Autowired
        RoleMapper roleMapper;
    
        @Autowired
        PermissionsConfig permissionsConfig;
    
        /**
         * 请求拦截
         *
         * @param securityManager
         * @return
         */
        @Bean
        public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
            shiroFilterFactoryBean.setSecurityManager(securityManager);
    
            // 自定义过滤器
            Map<String, Filter> filterMap = new LinkedHashMap<>();
            filterMap.put("MyAuthc", new MyFormAuthenticationFilter());
            filterMap.put("MyPerms", new MyPermissionsAuthorizationFilter());
            filterMap.put("MyRoles", new MyRolesAuthorizationFilter());
            shiroFilterFactoryBean.setFilters(filterMap);
    
            // 登录的路径: 如果你没有登录则会跳到这个页面中 - 如果没有设置值则会默认跳转到工程根目录下的"/login.jsp"页面 或 "/login" 映射
            shiroFilterFactoryBean.setLoginUrl("/unLogin");
            // 设置没有权限时跳转的url
            shiroFilterFactoryBean.setUnauthorizedUrl("/unauth");
    
            // 加载权限
            shiroFilterFactoryBean.setFilterChainDefinitionMap(permissionsConfig.loadFilterChainDefinitions());
            return shiroFilterFactoryBean;
        }
    
        /**
         * @Description: SecurityManager,权限管理,这个类组合了登陆,登出,权限,session的处理
         */
        @Bean
        public SecurityManager securityManager() {
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            securityManager.setRealm(ShiroRealm());
            securityManager.setSessionManager(sessionManager());
            return securityManager;
        }
    
        /**
         * 自定义认证
         *
         * @return MyShiroRealm
         * @Title: myShiroRealm
         * @Description: ShiroRealm,这是个自定义的认证类,继承自AuthorizingRealm,负责用户的认证和权限的处理
         */
        @Bean
        public ShiroRealm ShiroRealm() {
            ShiroRealm shiroRealm = new ShiroRealm();
            shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
            return shiroRealm;
        }
    
        /**
         * 密码凭证匹配器,作为自定义认证的基础 (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了 )
         *
         * @return
         */
        @Bean
        public HashedCredentialsMatcher hashedCredentialsMatcher() {
            HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
            //加密算法:这里使用MD5算法;
            hashedCredentialsMatcher.setHashAlgorithmName("MD5");
            //加密的次数
            hashedCredentialsMatcher.setHashIterations(1024);
            //此处的设置,true加密用的hex编码,false用的base64编码
            hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
            return hashedCredentialsMatcher;
        }
    
        /**
         * 自定义sessionManager,用户的唯一标识,即Token或Authorization的认证
         */
        @Bean
        public SessionManager sessionManager() {
            MySessionManager mySessionManager = new MySessionManager();
            mySessionManager.setGlobalSessionTimeout(-1L);
            return mySessionManager;
        }
    
    }
    

     

    8.自定义实现动态加载权限

    最后一步也是最重要的一步,动态加载FilterChainDefinitionMap,也就是shiroFilterFactoryBean.setFilterChainDefinitionMap(permissionsConfig.loadFilterChainDefinitions())这一步。

    之所以需要动态刷新权限,是因为当我们新增了用户,新增角色,然后重新分配权限,用这个账户直接登录时,这个用户和角色并没有被初始化到FilterChainDefinitionMap里面,大家debug这一步的时候想必也知道只有在开启服务的时候会调用一次,所以这时候就需要动态加载FilterChainDefinitionMap,在增删改查涉及到角色,权限等地方调用进行刷新权限。刷新之后FilterChainDefinitionMap里就会加载出我们动态查询出的所有权限了。

    /**
     * Created by Eagga_Lo on 2019/12/23 16:41
     */
    @Service
    @Slf4j
    public class PermissionsConfig {
    
        @Autowired
        MenuMapper menuMapper;
    
        @Autowired
        RoleMapper roleMapper;
    
        public Map<String, String> loadFilterChainDefinitions() {
            // 权限控制
            Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
            // 配置不会被拦截的链接 顺序判断
            //authc 必须认证通过才可以访问;
            //anon 可以匿名访问
            filterChainDefinitionMap.put("/druid/**", "anon");
            filterChainDefinitionMap.put("/swagger-ui.html", "anon");
            filterChainDefinitionMap.put("/swagger/**", "anon");
            filterChainDefinitionMap.put("/swagger-resources/**", "anon");
            filterChainDefinitionMap.put("/v2/**", "anon");
            filterChainDefinitionMap.put("/webjars/**", "anon");
            // 登录
            filterChainDefinitionMap.put("/login", "anon");
            // 退出登录
            filterChainDefinitionMap.put("/logout", "anon");
            // 未登录
            filterChainDefinitionMap.put("/unLogin", "anon");
            // 未授权
            filterChainDefinitionMap.put("/unauth", "anon");
    
            // 自定义权限
            List<Menu> permissionList = menuMapper.selectAll();
            if (!CollectionUtils.isEmpty(permissionList)) {
                permissionList.forEach(e -> {
                    if (StringUtils.isNotBlank(e.getUrl())) {
                        // 根据url查询相关联的角色名,拼接自定义的角色权限
                        List<Role> roleList = roleMapper.selectRoleByMenuId(e.getMenuId());
                        StringJoiner MyRoles = new StringJoiner(",", "MyRoles[", "]");
                        if (!CollectionUtils.isEmpty(roleList)) {
                            roleList.forEach(f -> {
                                MyRoles.add(f.getRoleDes());
                            });
                        }
                        filterChainDefinitionMap.put(e.getUrl(), "MyAuthc," + MyRoles.toString() + ",MyPerms[" + e.getResource() + "]");
                    }
                });
            }
            filterChainDefinitionMap.put("/**", "MyAuthc");
            return filterChainDefinitionMap;
        }
    
        /**
         * 更新权限,解决需要重启tomcat才能生效权限的问题
         */
        public void updatePermission() {
            log.info("-----------------正在更新权限...--------------");
            try {
                HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
                ServletContext servletContext = request.getSession().getServletContext();
                    //注意bean的名字不要写错了 是自己的shiroFilter名称
                AbstractShiroFilter shiroFilter = (AbstractShiroFilter) WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext).getBean("shiroFilter");
                synchronized (shiroFilter) {
                    // 获取过滤管理器
                    PathMatchingFilterChainResolver filterChainResolver = (PathMatchingFilterChainResolver) shiroFilter.getFilterChainResolver();
                    DefaultFilterChainManager manager = (DefaultFilterChainManager) filterChainResolver.getFilterChainManager();
                    // 清空初始权限配置
                    manager.getFilterChains().clear();
                    // 重新获取资源
                    Map<String, String> chains = loadFilterChainDefinitions();
                    for (Map.Entry<String, String> entry : chains.entrySet()) {
                        String url = entry.getKey();
                        String chainDefinition = entry.getValue().trim().replace(" ", "");
                        manager.createChain(url, chainDefinition);
                    }
                    log.info("-----------------更新权限成功!!--------------");
                }
            } catch (Exception e) {
                log.error(e.getMessage());
            }
        }
    }
    

     

    以上就是全部过程,有不对或者不足的地方欢迎指出。有不明白的地方可以在评论区回复或者私信。

    展开全文
  • 整体思路 会话开始之初,先初始化一个只有登录路由的Vue实例,在根组件...然后获取当前用户的权限数据,用户登录成功之后,会在全局钩子router.beforeEach中拦截路由,判断是否已获得token,在获得token之后就要去获...

    整体思路

    会话开始之初,先初始化一个只有登录路由的Vue实例,在根组件通过 handleLogin() 将路由定向到登录页,再拉取登录框的数据,让每个请求携带token-- [‘X-Litemall-Admin-Token’]实现用户鉴权.

    然后获取当前用户的权限数据,用户登录成功之后,会在全局钩子router.beforeEach中拦截路由,判断是否已获得token,在获得token之后就要去获取用户的基本信息了,主要包括路由权限和资源权限。

    之后动态添加路由,路由分为全局路由和异步路由,异步路由需要加上权限信息,再从后台获取权限匹配,根据不同角色拥有的权限动态生成不同的菜单,实现全局权限验证方法,并为axios实例添加请求拦截器,完成权限控制。动态加载路由后,路由组件将随之加载并渲染,而后展现前端界面。

    前端实现方式:

    第一步:编写登录窗口

    index.vue

     <el-form-item prop="password">
            <span class="svg-container">
              <svg-icon icon-class="password" />
            </span>
            <el-input :type="passwordType" v-model="loginForm.password" name="password" auto-complete="on" placeholder="password" @keyup.enter.native="handleLogin" />
            <span class="show-pwd" @click="showPwd">
              <svg-icon icon-class="eye" />
            </span>
          </el-form-item>
    
          <el-button :loading="loading" type="primary" style="width:100%;margin-bottom:30px;" @click.native.prevent="handleLogin">登录</el-button>
    
    

    在使用vue写的登录窗口中通过 @keyup.enter.native=“handleLogin” 绑定handleLogin事件

    第二步:编写 handleLogin() 事件

    handleLogin() {
          this.$refs.loginForm.validate(valid => {
            if (valid && !this.loading) {
              this.loading = true
              this.$store.dispatch('LoginByUsername', this.loginForm).then(() => {
                this.loading = false
                this.$router.push({ path: this.redirect || '/' })
              }).catch(response => {
                this.$notify.error({
                  title: '失败',
                  message: response.data.errmsg
                })
                this.loading = false
              })
            } else {
              return false
            }
          })
        }
    
    

    这里重点关注两行代码

     this.$store.dispatch('LoginByUsername', this.loginForm).then(() => {
     this.$router.push({ path: this.redirect || '/' })
    

    这两行代码意思是将请求转发到 LoginByUsername 事件,并且转发发送请求后加上 ‘/’ 。

    第三步:使用watch监听

    watch: {
        $route: {
          handler: function(route) {
            this.redirect = route.query && route.query.redirect
          },
          immediate: true
        }
      },
    
    

    第四步:编写第二步绑定的js事件 loginByUsername

    export function loginByUsername(username, password) {
      const data = {
        username,
        password
      }
      return request({
        url: '/auth/login',
        method: 'post',
        data
      })
    }
    
    

    注意:在这里,请求方式为post,请求路径为 /auth/login 。

    第五步:token验证(重点关注)

    router.beforeEach((to, from, next) => {
      NProgress.start() 
      if (getToken()) { 
        /* has token*/
        if (to.path === '/login') {
          next({ path: '/' })
          NProgress.done() 
        } else {
          if (store.getters.perms.length === 0) { // 判断当前用户是否已拉取完user_info信息
            store.dispatch('GetUserInfo').then(res => { // 拉取user_info
              const perms = res.data.data.perms // note: perms must be a array! such as: ['GET /aaa','POST /bbb']
              store.dispatch('GenerateRoutes', { perms }).then(() => { // 根据perms权限生成可访问的路由表
                router.addRoutes(store.getters.addRouters) // 动态添加可访问路由表
                next({ ...to, replace: true }) // hack方法 确保addRoutes已完成 ,set the replace: true so the navigation will not leave a history record
              })
            }).catch((err) => {
              store.dispatch('FedLogOut').then(() => {
                Message.error(err || 'Verification failed, please login again')
                next({ path: '/' })
              })
            })
          } else {
            // 没有动态改变权限的需求可直接next() 删除下方权限判断 ↓
            if (hasPermission(store.getters.perms, to.meta.perms)) {
              next()
            } else {
              next({ path: '/401', replace: true, query: { noGoBack: true }})
            }
            // 可删 ↑
          }
        }
      } else {
        /* has no token*/
        if (whiteList.indexOf(to.path) !== -1) { // 在免登录白名单,直接进入
          next()
        } else {
          next(`/login?redirect=${to.path}`) // 否则全部重定向到登录页
          NProgress.done() // if current page is login will not trigger afterEach hook, so manually handle it
        }
      }
    }
    
    

    在这里需要注意以下几行代码:

    store.dispatch('GetUserInfo').then(res => { // 拉取user_info
    
      这里拉取userinfo,肯定要从表单获取,因此要写一个getUserInfo事件如下
    export function getUserInfo(token) {
      return request({
        url: '/auth/info',
        method: 'get',
        params: { token }
      })
    }
    
    
    store.dispatch('GenerateRoutes', { perms }).then(() => {
    
    表示根据perms权限生成可访问的路由表
    
    绑定的GenerateRoutes
    GenerateRoutes({ commit }, data) {
          return new Promise(resolve => {
            const { perms } = data
            let accessedRouters
            if (perms.includes('*')) {
              accessedRouters = asyncRouterMap
            } else {
              accessedRouters = filterAsyncRouter(asyncRouterMap, perms)
            }
            commit('SET_ROUTERS', accessedRouters)
            resolve()
          })
        }
    
    

    第六步:根据权限动态修改路由(重点关注!!!)

    import { asyncRouterMap, constantRouterMap } from '@/router'
    
    /**
     * @param {
     * @param {*} route
     */
    
    /**
     * 通过meta.perms判断是否与当前用户权限匹配
     * @param perms
     * @param route
     */
    function hasPermission(perms, route) {
      if (route.meta && route.meta.perms) {
        return perms.some(perm => route.meta.perms.includes(perm))
      } else {
        return true
      }
    }
    
    /**
     * 递归过滤异步路由表,返回符合用户角色权限的路由表
     * @param routes asyncRouterMap
     * @param perms
     */
    function filterAsyncRouter(routes, perms) {
      const res = []
    
      routes.forEach(route => {
        const tmp = { ...route }
        if (tmp.children) {
          tmp.children = filterAsyncRouter(tmp.children, perms)
          if (tmp.children && tmp.children.length > 0) {
            res.push(tmp)
          }
        } else {
          if (hasPermission(perms, tmp)) {
            res.push(tmp)
          }
        }
      })
    
      return res
    }
    
    const permission = {
      state: {
        routers: constantRouterMap,
        addRouters: []
      },
      mutations: {
        SET_ROUTERS: (state, routers) => {
          state.addRouters = routers
          state.routers = constantRouterMap.concat(routers) // concat() 方法用于连接两个或多个数组。该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本。
        }
      },
      actions: {
        GenerateRoutes({ commit }, data) {
          return new Promise(resolve => {
            const { perms } = data
            let accessedRouters
            if (perms.includes('*')) {
              accessedRouters = asyncRouterMap
            } else {
              accessedRouters = filterAsyncRouter(asyncRouterMap, perms)
            }
            commit('SET_ROUTERS', accessedRouters) // 保存用户权限标识集合
            resolve()
          })
        }
      }
    }
    
    export default permission
    
    

    这里的代码说白了就是干了一件事,通过用户的权限和之前在router.js里面asyncRouterMap的每一个页面所需要的权限做匹配,最后返回一个该用户能够访问路由有哪些,其中,主路由里每一个异步路由都有一个perms标签控制权限信息,如下

     {
            path: 'issue',
            component: () => import('@/views/mall/issue'),
            name: 'issue',
            meta: {
              perms: ['GET /admin/issue/list', 'POST /admin/issue/create', 'GET /admin/issue/read', 'POST /admin/issue/update', 'POST /admin/issue/delete'],
              title: '通用问题',
              noCache: true
            }
          },
    
    

    后台数据库中获取相应的权限信息通过 第六步 的代码进行匹配,成功则返回相应路由,从而达到动态展示菜单的效果

    后台代码重点需要观察以下几块

    ShiroConfig:

    @Configuration
    public class ShiroConfig {
    
        @Bean
        public Realm realm() {
            return new AdminAuthorizingRealm();
        }
    
        @Bean
        public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
            shiroFilterFactoryBean.setSecurityManager(securityManager);
            Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
            filterChainDefinitionMap.put("/admin/auth/login", "anon");
            filterChainDefinitionMap.put("/admin/auth/401", "anon");
            filterChainDefinitionMap.put("/admin/auth/index", "anon");
            filterChainDefinitionMap.put("/admin/auth/403", "anon");
            filterChainDefinitionMap.put("/admin/index/index", "anon");
    
            filterChainDefinitionMap.put("/admin/**", "authc");
            shiroFilterFactoryBean.setLoginUrl("/admin/auth/401");
            shiroFilterFactoryBean.setSuccessUrl("/admin/auth/index");
            shiroFilterFactoryBean.setUnauthorizedUrl("/admin/auth/403");
            shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
            return shiroFilterFactoryBean;
        }
    
        @Bean
        public SessionManager sessionManager() {
    
            return new AdminWebSessionManager();
        }
    
        @Bean
        public DefaultWebSecurityManager defaultWebSecurityManager() {
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            securityManager.setRealm(realm());
            securityManager.setSessionManager(sessionManager());
            return securityManager;
        }
    
        @Bean
        public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
            AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor =
                    new AuthorizationAttributeSourceAdvisor();
            authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
            return authorizationAttributeSourceAdvisor;
        }
    
        @Bean
        @DependsOn("lifecycleBeanPostProcessor")
        public static DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
            DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
            creator.setProxyTargetClass(true);
            return creator;
        }
    }
    
    

    LitemallPermissionService

    @Service
    public class LitemallPermissionService {
        @Resource
        private LitemallPermissionMapper permissionMapper;
    
        public Set<String> queryByRoleIds(Integer[] roleIds) {  //java中Set集合是一个不包含重复元素的Collection
            Set<String> permissions = new HashSet<String>();
            if(roleIds.length == 0){
                return permissions;
            }
    
            LitemallPermissionExample example = new LitemallPermissionExample();
            example.or().andRoleIdIn(Arrays.asList(roleIds)).andDeletedEqualTo(false);
            List<LitemallPermission> permissionList = permissionMapper.selectByExample(example);
    
            for(LitemallPermission permission : permissionList){
                permissions.add(permission.getPermission());
            }
    
            return permissions;
        }
    
    
        public Set<String> queryByRoleId(Integer roleId) {
            Set<String> permissions = new HashSet<String>();
            if(roleId == null){
                return permissions;
            }
    
            LitemallPermissionExample example = new LitemallPermissionExample();
            example.or().andRoleIdEqualTo(roleId).andDeletedEqualTo(false);
            List<LitemallPermission> permissionList = permissionMapper.selectByExample(example);
    
            for(LitemallPermission permission : permissionList){
                permissions.add(permission.getPermission());
            }
    
            return permissions;
        }
    
        public boolean checkSuperPermission(Integer roleId) {
            if(roleId == null){
                return false;
            }
    
            LitemallPermissionExample example = new LitemallPermissionExample();
            example.or().andRoleIdEqualTo(roleId).andPermissionEqualTo("*").andDeletedEqualTo(false);
            return permissionMapper.countByExample(example) != 0;
        }
    
        public void deleteByRoleId(Integer roleId) {
            LitemallPermissionExample example = new LitemallPermissionExample();
            example.or().andRoleIdEqualTo(roleId).andDeletedEqualTo(false);
            permissionMapper.logicalDeleteByExample(example);
        }
    
        public void add(LitemallPermission litemallPermission) {
            litemallPermission.setAddTime(LocalDateTime.now());
            litemallPermission.setUpdateTime(LocalDateTime.now());
            permissionMapper.insertSelective(litemallPermission);
        }
    }
    
    

    这里需要关注以下几行代码:

    1.判断是否是超级管理员,是的话赋予赋予所有权限,( “*”)

    LitemallPermissionExample example = new LitemallPermissionExample();
        example.or().andRoleIdEqualTo(roleId).andPermissionEqualTo("*").andDeletedEqualTo(false);
        return permissionMapper.countByExample(example) != 0;
    
    

    2.查找出对应角色拥有的资源权限信息,遍历并返回权限信息。
    注意:这里在数据库表中通过外键管理了角色表和权限表,

    LitemallPermissionExample example = new LitemallPermissionExample();
    example.or().andRoleIdEqualTo(roleId).andDeletedEqualTo(false);
    List<LitemallPermission> permissionList = permissionMapper.selectByExample(example);
    
    for(LitemallPermission permission : permissionList){
        permissions.add(permission.getPermission());
    }
    
    return permissions;
    
    

    权限表如下:
    在这里插入图片描述

    这里是引号,但是在后台代码中会转换为“/”,实现代码如下

    data.put("roles", roles);
    // NOTE
    // 这里需要转换perms结构,因为对于前端而已API形式的权限更容易理解
    data.put("perms", toApi(permissions));
    
    

    AdminAuthorizingRealm:

    
    public class AdminAuthorizingRealm extends AuthorizingRealm {
    
        @Autowired
        private LitemallAdminService adminService;
        @Autowired
        private LitemallRoleService roleService;
        @Autowired
        private LitemallPermissionService permissionService;
    
        @Override
        protected AuthorizationInfo     doGetAuthorizationInfo(PrincipalCollection principals) {//PrincipalCollection是一个身份集合,因为我们可以在Shiro中同时配置多个Realm,所以呢身份信息可能就有多个;因此其提供了PrincipalCollection用于聚合这些身份信息:
            if (principals == null) {
                throw new AuthorizationException("PrincipalCollection method argument cannot be null.");
            }
    
            LitemallAdmin admin = (LitemallAdmin) getAvailablePrincipal(principals);
            Integer[] roleIds = admin.getRoleIds();
            Set<String> roles = roleService.queryByIds(roleIds);
            Set<String> permissions = permissionService.queryByRoleIds(roleIds);
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //SimpleAuthenticationInfo 会合并多个Principal为一个PrincipalCollection
            info.setRoles(roles);
            info.setStringPermissions(permissions);
            return info;
        }
    
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    
            UsernamePasswordToken upToken = (UsernamePasswordToken) token;
            String username = upToken.getUsername();
            String password = new String(upToken.getPassword());
    
            if (StringUtils.isEmpty(username)) {
                throw new AccountException("用户名不能为空");
            }
            if (StringUtils.isEmpty(password)) {
                throw new AccountException("密码不能为空");
            }
    
            List<LitemallAdmin> adminList = adminService.findAdmin(username);
            Assert.state(adminList.size() < 2, "同一个用户名存在两个账户");
            if (adminList.size() == 0) {
                throw new UnknownAccountException("找不到用户(" + username + ")的帐号信息");
            }
            LitemallAdmin admin = adminList.get(0);
    
            BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
            if (!encoder.matches(password, admin.getPassword())) {
                throw new UnknownAccountException("找不到用户(" + username + ")的帐号信息");
            }
    
            return new SimpleAuthenticationInfo(admin, password, getName());
        }
    
    }
    
    

    PrincipalCollection是一个身份集合,因为我们可以在Shiro中同时配置多个Realm, 所以身份信息可能就有多个;因此其提供了PrincipalCollection用于聚合这些身份信息。

    参考博客一:https://refined-x.com/2017/11/28/Vue2.0%E7%94%A8%E6%88%B7%E6%9D%83%E9%99%90%E6%8E%A7%E5%88%B6%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/#%E8%AF%B7%E6%B1%82%E6%8E%A7%E5%88%B6

    参考博客二:https://juejin.im/post/591aa14f570c35006961acac
    ————————————————
    版权声明:本文为CSDN博主「冰阔落DG」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/HR18770171448/article/details/103430555

    展开全文
  • 利用shiro实现权限动态控制

    千次阅读 2017-09-18 10:01:56
    使用shiro对登陆进行权限验证,以及实现权限动态管理。 shiro配置文件  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"  xmlns:context=...
  • shiro动态控制url资源

    2018-04-09 11:43:00
    所谓动态控制url就是url的权限控制不是手动写死在配置文件中,而是根据数据库的变化而变化。 表结构: user2:用户表 t_role:角色表 t_user_role:用户角色表 t_privilege:权限资源表 t_r...
  • shiro动态更新权限失败

    千次阅读 2018-07-24 13:49:18
     在之前的spring集成shiro权限控制一文中,实现了动态更新的权限的方法,即对权限表做修改后,需要更新到shiro的filterchain。后来测试的时候,发现两个问题: 1、更改权限后,并没有动态更新到shiro中去,取消权限...
  • 1.shiro处理权限流程 1.1 从数据库查询所有权限交给shiro管理 List<Permission> permissions = permissionService.findAll(); for (Permission permission : permissions) { mp.put(permission.getUrl(), ...
  • 用过Spring Security的朋友应该比较熟悉对URL进行全局的权限控制,即访问URL时进行权限匹配;如果没有权限直接跳到相应的错误页面。...本章将介绍如何在Shiro中完成动态URL权限控制。   本章代码基于
  • Springboot+Shiro 基于URL 动态控制权限

    万次阅读 2018-08-13 19:21:57
     权限控制有 注解的方式,jsp shiro标签的方式,还有url 动态控制的方式。这里我使用最后一种方式来控制权限 思路: 0.利用 PathMatchingFilter 拦截器 1.根据用户名 来查询角色, 2.根据角色查询权限 3.获取...

空空如也

空空如也

1 2 3 4 5 ... 11
收藏数 215
精华内容 86
关键字:

shiro动态权限控制