SpringBoot整合mybatis、shiro、redis实现基于数据库的细粒度动态权限管理系统实例(转)...
SpringBoot整合mybatis、shiro、redis实现基于数据库的细粒度动态权限管理系统实例
目录(?)[+]
- 前言
- 表结构
- maven配置
- 配置Druid
- 配置mybatis
- MyMapper
- thymeleaf配置
- shiro配置
- 配置文件ShiroConfig
- 配置自定义Realm
- 认证
- 授权
- 会话管理
- 按钮控制
- 效果图
- 运行下载
1.前言
本文主要介绍使用SpringBoot与shiro实现基于数据库的细粒度动态权限管理系统实例。
使用技术:SpringBoot、mybatis、shiro、thymeleaf、pagehelper、Mapper插件、druid、dataTables、ztree、jQuery
开发工具:intellij idea
数据库:mysql、redis
基本上是基于使用SpringSecurity的demo上修改而成,地址 http://blog.csdn.net/poorcoder_/article/details/70231779
2.表结构
还是是用标准的5张表来展现权限。如下图:
分别为用户表,角色表,资源表,用户角色表,角色资源表。在这个demo中使用了mybatis-generator自动生成代码。运行mybatis-generator:generate -e 根据数据库中的表,生成 相应的model,mapper单表的增删改查。不过如果是导入本项目的就别运行这个命令了。新增表的话,也要修改mybatis-generator-config.xml中的tableName,指定表名再运行。
3.maven配置
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion>
<groupId>com.study</groupId><artifactId>springboot-shiro</artifactId><version>0.0.1-SNAPSHOT</version><packaging>jar</packaging>
<name>springboot-shiro</name><description>Demo project for Spring Boot</description>
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>1.5.2.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent>
<properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><java.version>1.8</java.version></properties>
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper-spring-boot-starter</artifactId><version>1.1.0</version></dependency><dependency><groupId>tk.mybatis</groupId><artifactId>mapper-spring-boot-starter</artifactId><version>1.1.1</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.3.2</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.0.29</version></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><groupId>net.sourceforge.nekohtml</groupId><artifactId>nekohtml</artifactId><version>1.9.22</version></dependency><dependency><groupId>com.github.theborakompanioni</groupId><artifactId>thymeleaf-extras-shiro</artifactId><version>1.2.1</version></dependency><dependency><groupId>org.crazycake</groupId><artifactId>shiro-redis</artifactId><version>2.4.2.1-RELEASE</version></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin><plugin><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-maven-plugin</artifactId><version>1.3.5</version><configuration><configurationFile>${basedir}/src/main/resources/generator/generatorConfig.xml</configurationFile><overwrite>true</overwrite><verbose>true</verbose></configuration><dependencies><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>${mysql.version}</version></dependency><dependency><groupId>tk.mybatis</groupId><artifactId>mapper</artifactId><version>3.4.0</version></dependency></dependencies></plugin></plugins></build>
</project>
4.配置Druid
package com.study.config;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/*** Created by yangqj on 2017/4/19.*/
@Configuration
public class DruidConfig {@Beanpublic ServletRegistrationBean druidServlet() {
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");//登录查看信息的账号密码.
servletRegistrationBean.addInitParameter("loginUsername","admin");servletRegistrationBean.addInitParameter("loginPassword","123456");return servletRegistrationBean;}@Beanpublic FilterRegistrationBean filterRegistrationBean() {FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();filterRegistrationBean.setFilter(new WebStatFilter());filterRegistrationBean.addUrlPatterns("/*");filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");return filterRegistrationBean;}
}
在application.properties中加入:
# 数据源基础配置
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/shiro
spring.datasource.username=root
spring.datasource.password=root# 连接池配置# 初始化大小,最小,最大
spring.datasource.initialSize=1
spring.datasource.minIdle=1
spring.datasource.maxActive=20
配置好后,运行项目访问http://localhost:8080/druid/ 输入配置的账号密码admin,123456进入:
5.配置mybatis
使用springboot 整合mybatis非常方便,只需在application.properties
mybatis.type-aliases-package=com.study.model
mybatis.mapper-locations=classpath:mapper/*.xml
mapper.mappers=com.study.util.MyMapper
mapper.not-empty=false
mapper.identity=MYSQL
pagehelper.helperDialect=mysql
pagehelper.reasonable=true
pagehelper.supportMethodsArguments=true
pagehelper.params=count\=countSql
将相应的路径改成项目包所在的路径即可。配置文件中可以看出来还加入了pagehelper 和Mapper插件。如果不需要,把上面配置文件中的 pagehelper删除。
MyMapper:
package com.study.util;
/*** Created by yangqj on 2017/4/20.*/
import tk.mybatis.mapper.common.Mapper;
import tk.mybatis.mapper.common.MySqlMapper;public interface MyMapper<T> extends Mapper<T>, MySqlMapper<T> {
}
对于Springboot整合mybatis可以参考https://github.com/abel533/MyBatis-Spring-Boot
6.thymeleaf配置
thymeleaf是springboot官方推荐的,所以来试一下。
首先加入配置:
#spring.thymeleaf.prefix=classpath:/templates/#spring.thymeleaf.suffix=.html#spring.thymeleaf.mode=HTML5#spring.thymeleaf.encoding=UTF-8# ;charset=<encoding> is added#spring.thymeleaf.content-type=text/html# set to false for hot refresh
spring.thymeleaf.cache=false
spring.thymeleaf.mode=LEGACYHTML5
可以看到其实上面都是注释了的,因为springboot会根据约定俗成的方式帮我们配置好。所以上面注释部分是springboot自动配置的,如果需要自定义配置,只需要修改上注释部分即可。
后两行没有注释的部分,spring.thymeleaf.cache=false表示关闭缓存,这样修改文件后不需要重新启动,缓存默认是开启的,所以指定为false。但是在intellij idea中还需要按Ctrl + Shift + F9.
对于spring.thymeleaf.mode=LEGACYHTML5。thymeleaf对html中的语法要求非常严格,像我从网上找的模板,使用thymeleaf后报一堆的语法错误,后来没办法,使用弱语法校验,所以加入配置spring.thymeleaf.mode=LEGACYHTML5。加入这个配置后还需要在maven中加入
<dependency><groupId>net.sourceforge.nekohtml</groupId><artifactId>nekohtml</artifactId><version>1.9.22</version></dependency>
否则会报错的。
在前端页面的头部加入一下配置后,就可以使用thymeleaf了
<link rel="stylesheet" th:href="@{/css/bootstrap.min.css}" />
不过这个项目因为使用了datatables都是使用jquery 的ajax来访问数据与处理数据,所以用到的thymeleaf语法非常少,基本上可以参考的就是js即css的导入和类似于jsp的include功能的部分页面引入。
对于静态文件的引入:
<link rel="stylesheet" th:href="@{/css/bootstrap.min.css}" />
而文件在项目中的位置是static-css-bootstrap.min.css。为什么这样可以访问到该文件,也是因为springboot对于静态文件会自动查找/static public、/resources、/META-INF/resources下的文件。所以不需要加static.
页面引入:
局部页面如下:
<div th:fragment="top">...
</div>
主体页面映入方式:
<div th:include="common/top :: top"></div>
inclide=”文件路径::局部代码片段名称”
7.shiro配置
配置文件ShiroConfig
package com.study.config;import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;import com.github.pagehelper.util.StringUtil;import com.study.model.Resources;import com.study.service.ResourcesService;import com.study.shiro.MyShiroRealm;import org.apache.shiro.authc.credential.HashedCredentialsMatcher;import org.apache.shiro.mgt.SecurityManager;import org.apache.shiro.spring.LifecycleBeanPostProcessor;import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;import org.apache.shiro.spring.web.ShiroFilterFactoryBean;import org.apache.shiro.web.mgt.DefaultWebSecurityManager;import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;import org.crazycake.shiro.RedisCacheManager;import org.crazycake.shiro.RedisManager;import org.crazycake.shiro.RedisSessionDAO;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;import java.util.List;import java.util.Map;
/*** Created by yangqj on 2017/4/23.*/@Configurationpublic class ShiroConfig {@Autowired(required = false)private ResourcesService resourcesService;
@Value("${spring.redis.host}")private String host;
@Value("${spring.redis.port}")private int port;
@Value("${spring.redis.timeout}")private int timeout;
@Beanpublic static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {return new LifecycleBeanPostProcessor();}/*** ShiroDialect,为了在thymeleaf里使用shiro的标签的bean* @return*/@Beanpublic ShiroDialect shiroDialect() {return new ShiroDialect();}/*** ShiroFilterFactoryBean 处理拦截资源文件问题。* 注意:单独一个ShiroFilterFactoryBean配置是或报错的,因为在* 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager*Filter Chain定义说明1、一个URL可以配置多个Filter,使用逗号分隔2、当设置多个过滤器时,全部验证通过,才视为通过3、部分过滤器可指定参数,如perms,roles**/@Beanpublic ShiroFilterFactoryBean shirFilter(SecurityManager securityManager){System.out.println("ShiroConfiguration.shirFilter()");ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置 SecurityManagershiroFilterFactoryBean.setSecurityManager(securityManager);// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面shiroFilterFactoryBean.setLoginUrl("/login");// 登录成功后要跳转的链接shiroFilterFactoryBean.setSuccessUrl("/usersPage");//未授权界面;shiroFilterFactoryBean.setUnauthorizedUrl("/403");//拦截器.Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
//配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了filterChainDefinitionMap.put("/logout", "logout");filterChainDefinitionMap.put("/css/**","anon");filterChainDefinitionMap.put("/js/**","anon");filterChainDefinitionMap.put("/img/**","anon");filterChainDefinitionMap.put("/font-awesome/**","anon");//<!-- 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;//<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->//自定义加载权限资源关系List<Resources> resourcesList = resourcesService.queryAll();for(Resources resources:resourcesList){
if (StringUtil.isNotEmpty(resources.getResurl())) {String permission = "perms[" + resources.getResurl()+ "]";filterChainDefinitionMap.put(resources.getResurl(),permission);}}filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);return shiroFilterFactoryBean;}@Beanpublic SecurityManager securityManager(){DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();//设置realm.securityManager.setRealm(myShiroRealm());// 自定义缓存实现 使用redis//securityManager.setCacheManager(cacheManager());// 自定义session管理 使用redissecurityManager.setSessionManager(sessionManager());return securityManager;}
@Beanpublic MyShiroRealm myShiroRealm(){MyShiroRealm myShiroRealm = new MyShiroRealm();myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());return myShiroRealm;}
/*** 凭证匹配器* (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了* 所以我们需要修改下doGetAuthenticationInfo中的代码;* )* @return*/@Beanpublic HashedCredentialsMatcher hashedCredentialsMatcher(){HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));
return hashedCredentialsMatcher;}/*** 开启shiro aop注解支持.* 使用代理方式;所以需要开启代码支持;* @param securityManager* @return*/@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);return authorizationAttributeSourceAdvisor;}/*** 配置shiro redisManager* 使用的是shiro-redis开源插件* @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实现* 使用的是shiro-redis开源插件* @return*/public RedisCacheManager cacheManager() {RedisCacheManager redisCacheManager = new RedisCacheManager();redisCacheManager.setRedisManager(redisManager());return redisCacheManager;}/*** RedisSessionDAO shiro sessionDao层的实现 通过redis* 使用的是shiro-redis开源插件*/@Beanpublic RedisSessionDAO redisSessionDAO() {RedisSessionDAO redisSessionDAO = new RedisSessionDAO();redisSessionDAO.setRedisManager(redisManager());return redisSessionDAO;}/*** shiro session的管理*/@Beanpublic DefaultWebSessionManager sessionManager() {DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();sessionManager.setSessionDAO(redisSessionDAO());return sessionManager;}}
配置自定义Realm
package com.study.shiro;import com.study.model.Resources;import com.study.model.User;import com.study.service.ResourcesService;import com.study.service.UserService;import org.apache.shiro.SecurityUtils;import org.apache.shiro.authc.*;import org.apache.shiro.authz.AuthorizationInfo;import org.apache.shiro.authz.SimpleAuthorizationInfo;import org.apache.shiro.realm.AuthorizingRealm;import org.apache.shiro.session.Session;import org.apache.shiro.subject.PrincipalCollection;import org.apache.shiro.util.ByteSource;
import javax.annotation.Resource;import java.util.HashMap;import java.util.List;import java.util.Map;/*** Created by yangqj on 2017/4/21.*/public class MyShiroRealm extends AuthorizingRealm {
@Resourceprivate UserService userService;
@Resourceprivate ResourcesService resourcesService;//授权@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {User user= (User) SecurityUtils.getSubject().getPrincipal();//User{id=1, username='admin', password='3ef7164d1f6167cb9f2658c07d3c2f0a', enable=1}Map<String,Object> map = new HashMap<String,Object>();map.put("userid",user.getId());List<Resources> resourcesList = resourcesService.loadUserResources(map);// 权限信息对象info,用来存放查出的用户的所有的角色(role)及权限(permission)SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();for(Resources resources: resourcesList){info.addStringPermission(resources.getResurl());}return info;}//认证@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {//获取用户的输入的账号.String username = (String)token.getPrincipal();User user = userService.selectByUsername(username);if(user==null) throw new UnknownAccountException();if (0==user.getEnable()) {throw new LockedAccountException(); // 帐号锁定}SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user, //用户user.getPassword(), //密码ByteSource.Util.bytes(username),getName() //realm name);// 当验证都通过后,把用户信息放在session里Session session = SecurityUtils.getSubject().getSession();session.setAttribute("userSession", user);session.setAttribute("userSessionId", user.getId());return authenticationInfo;}
}
认证:
shiro的主要模块分别就是授权和认证和会话管理。
我们先讲认证。认证就是验证用户。比如用户登录的时候验证账号密码是否正确。
我们可以把对登录的验证交给shiro。我们执行要查询相应的用户信息,并传给shiro。如下代码则为用户登录:
@RequestMapping(value="/login",method=RequestMethod.POST)public String login(HttpServletRequest request, User user, Model model){if (StringUtils.isEmpty(user.getUsername()) || StringUtils.isEmpty(user.getPassword())) {request.setAttribute("msg", "用户名或密码不能为空!");return "login";}Subject subject = SecurityUtils.getSubject();UsernamePasswordToken token=new UsernamePasswordToken(user.getUsername(),user.getPassword());try {subject.login(token);return "redirect:usersPage";}catch (LockedAccountException lae) {token.clear();request.setAttribute("msg", "用户已经被锁定不能登录,请与管理员联系!");return "login";} catch (AuthenticationException e) {token.clear();request.setAttribute("msg", "用户或密码不正确!");return "login";}}
可见用户登陆的代码主要就是 subject.login(token);调用后就会进去我们自定义的realm中的doGetAuthenticationInfo()方法。
//认证@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {//获取用户的输入的账号.String username = (String)token.getPrincipal();User user = userService.selectByUsername(username);if(user==null) throw new UnknownAccountException();if (0==user.getEnable()) {throw new LockedAccountException(); // 帐号锁定}SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user, //用户user.getPassword(), //密码ByteSource.Util.bytes(username),getName() //realm name);// 当验证都通过后,把用户信息放在session里Session session = SecurityUtils.getSubject().getSession();session.setAttribute("userSession", user);session.setAttribute("userSessionId", user.getId());return authenticationInfo;}
而我们在ShiroConfig中配置了凭证匹配器:
@Beanpublic MyShiroRealm myShiroRealm(){MyShiroRealm myShiroRealm = new MyShiroRealm();myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());return myShiroRealm;}
@Beanpublic HashedCredentialsMatcher hashedCredentialsMatcher(){HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));return hashedCredentialsMatcher;}
所以在认证时的密码是加过密的,使用md5散发将密码与盐值组合加密两次。则我们在增加用户的时候,对用户的密码则要进过相同规则的加密才行。
添加用户代码如下:
@RequestMapping(value = "/add")public String add(User user) {User u = userService.selectByUsername(user.getUsername());if(u != null)return "error";try {user.setEnable(1);PasswordHelper passwordHelper = new PasswordHelper();passwordHelper.encryptPassword(user);userService.save(user);return "success";} catch (Exception e) {e.printStackTrace();return "fail";}}
PasswordHelper:
package com.study.util;import com.study.model.User;
import org.apache.shiro.crypto.RandomNumberGenerator;
import org.apache.shiro.crypto.SecureRandomNumberGenerator;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.util.ByteSource;public class PasswordHelper {//private RandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();private String algorithmName = "md5";private int hashIterations = 2;
public void encryptPassword(User user) {//String salt=randomNumberGenerator.nextBytes().toHex();String newPassword = new SimpleHash(algorithmName, user.getPassword(), ByteSource.Util.bytes(user.getUsername()), hashIterations).toHex();//String newPassword = new SimpleHash(algorithmName, user.getPassword()).toHex();user.setPassword(newPassword);
}public static void main(String[] args) {PasswordHelper passwordHelper = new PasswordHelper();User user = new User();user.setUsername("admin");user.setPassword("admin");passwordHelper.encryptPassword(user);System.out.println(user);}
}
授权:
接下来讲下授权。在自定义relalm中的代码为:
//授权@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {User user= (User) SecurityUtils.getSubject().getPrincipal();//User{id=1, username='admin', password='3ef7164d1f6167cb9f2658c07d3c2f0a', enable=1}Map<String,Object> map = new HashMap<String,Object>();map.put("userid",user.getId());List<Resources> resourcesList = resourcesService.loadUserResources(map);// 权限信息对象info,用来存放查出的用户的所有的角色(role)及权限(permission)SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();for(Resources resources: resourcesList){info.addStringPermission(resources.getResurl());}return info;}
从以上代码中可以看出来,我根据用户id查询出用户的权限,放入SimpleAuthorizationInfo。关联表user_role,role_resources,resources,三张表,根据用户所拥有的角色,角色所拥有的权限,查询出分配给该用户的所有权限的url。当访问的链接中配置在shiro中时,或者使用shiro标签,shiro权限注解时,则会访问该方法,判断该用户是否拥有相应的权限。
在ShiroConfig中有如下代码:
@Beanpublic ShiroFilterFactoryBean shirFilter(SecurityManager securityManager){System.out.println("ShiroConfiguration.shirFilter()");ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置 SecurityManagershiroFilterFactoryBean.setSecurityManager(securityManager);// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面shiroFilterFactoryBean.setLoginUrl("/login");// 登录成功后要跳转的链接shiroFilterFactoryBean.setSuccessUrl("/usersPage");//未授权界面;shiroFilterFactoryBean.setUnauthorizedUrl("/403");//拦截器.Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();//配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了filterChainDefinitionMap.put("/logout", "logout");filterChainDefinitionMap.put("/css/**","anon");filterChainDefinitionMap.put("/js/**","anon");filterChainDefinitionMap.put("/img/**","anon");filterChainDefinitionMap.put("/font-awesome/**","anon");//<!-- 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;//<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->//自定义加载权限资源关系List<Resources> resourcesList = resourcesService.queryAll();for(Resources resources:resourcesList){
if (StringUtil.isNotEmpty(resources.getResurl())) {String permission = "perms[" + resources.getResurl()+ "]";filterChainDefinitionMap.put(resources.getResurl(),permission);}}filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);return shiroFilterFactoryBean;}
该代码片段为配置shiro的过滤器。以上代码将静态文件设置为任何权限都可访问,然后
List<Resources> resourcesList = resourcesService.queryAll();for(Resources resources:resourcesList){
if (StringUtil.isNotEmpty(resources.getResurl())) {String permission = "perms[" + resources.getResurl()+ "]";filterChainDefinitionMap.put(resources.getResurl(),permission);}}
在数据中查询所有的资源,将该资源的url当作key,配置拥有该url权限的用户才可访问该url。
最后加入 filterChainDefinitionMap.put(“/*”, “authc”);表示其他没有配置的链接都需要认证才可访问。注意这个要放最后面,因为shiro的匹配是从上往下,如果匹配到就不继续匹配了,所以把 /放到最前面,则 后面的链接都无法匹配到了。
而这段代码是在项目启动的时候加载的。加载的数据是放到内存中的。但是当权限增加或者删除时,正常情况下不会重新启动来,重新加载权限。所以需要调用以下代码的updatePermission()方法来重新加载权限。其实下面的代码有些重复了,可以稍微调整下,我就先这么写了。
package com.study.shiro;import com.github.pagehelper.util.StringUtil;
import com.study.model.Resources;
import com.study.model.User;
import com.study.service.ResourcesService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.mgt.RealmSecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.subject.support.DefaultSubjectContext;
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.crazycake.shiro.RedisSessionDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.*;/*** Created by yangqj on 2017/4/30.*/
@Service
public class ShiroService {@Autowiredprivate ShiroFilterFactoryBean shiroFilterFactoryBean;@Autowiredprivate ResourcesService resourcesService;@Autowiredprivate RedisSessionDAO redisSessionDAO;/*** 初始化权限*/public Map<String, String> loadFilterChainDefinitions() {// 权限控制map.从数据库获取Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();filterChainDefinitionMap.put("/logout", "logout");filterChainDefinitionMap.put("/css/**","anon");filterChainDefinitionMap.put("/js/**","anon");filterChainDefinitionMap.put("/img/**","anon");filterChainDefinitionMap.put("/font-awesome/**","anon");List<Resources> resourcesList = resourcesService.queryAll();for(Resources resources:resourcesList){
if (StringUtil.isNotEmpty(resources.getResurl())) {String permission = "perms[" + resources.getResurl()+ "]";filterChainDefinitionMap.put(resources.getResurl(),permission);}}filterChainDefinitionMap.put("/**", "authc");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<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);}
System.out.println("更新权限成功!!");}}
}
会话管理
这个例子使用了redis保存session。这样可以实现集群的session共享。在ShiroConfig中有代码:
@Beanpublic SecurityManager securityManager(){DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();//设置realm.securityManager.setRealm(myShiroRealm());// 自定义缓存实现 使用redis//securityManager.setCacheManager(cacheManager());// 自定义session管理 使用redissecurityManager.setSessionManager(sessionManager());return securityManager;}
配置了自定义session,网上已经有大神实现了 使用redis 自定义session管理,直接拿来用,引入包
<dependency><groupId>org.crazycake</groupId><artifactId>shiro-redis</artifactId><version>2.4.2.1-RELEASE</version></dependency>
然后再配置:
/*** 配置shiro redisManager* 使用的是shiro-redis开源插件* @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实现* 使用的是shiro-redis开源插件* @return*/public RedisCacheManager cacheManager() {RedisCacheManager redisCacheManager = new RedisCacheManager();redisCacheManager.setRedisManager(redisManager());return redisCacheManager;}
/*** RedisSessionDAO shiro sessionDao层的实现 通过redis* 使用的是shiro-redis开源插件*/@Beanpublic RedisSessionDAO redisSessionDAO() {RedisSessionDAO redisSessionDAO = new RedisSessionDAO();redisSessionDAO.setRedisManager(redisManager());return redisSessionDAO;}/*** shiro session的管理*/@Beanpublic DefaultWebSessionManager sessionManager() {DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();sessionManager.setSessionDAO(redisSessionDAO());return sessionManager;}
RedisConfig:
package com.study.config;import org.apache.log4j.Logger;import org.springframework.beans.factory.annotation.Value;import org.springframework.cache.annotation.CachingConfigurerSupport;import org.springframework.cache.annotation.EnableCaching;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import redis.clients.jedis.JedisPool;import redis.clients.jedis.JedisPoolConfig;/*** Created by yangqj on 2017/4/30.*/
@Configuration@EnableCachingpublic class RedisConfig extends CachingConfigurerSupport {@Value("${spring.redis.host}")private String host;
@Value("${spring.redis.port}")private int port;
@Value("${spring.redis.timeout}")private int timeout;@Value("${spring.redis.pool.max-idle}")private int maxIdle;
@Value("${spring.redis.pool.max-wait}")private long maxWaitMillis;
@Beanpublic JedisPool redisPoolFactory() {Logger.getLogger(getClass()).info("JedisPool注入成功!!");Logger.getLogger(getClass()).info("redis地址:" + host + ":" + port);JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();jedisPoolConfig.setMaxIdle(maxIdle);jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);JedisPool jedisPool = new JedisPool(jedisPoolConfig, host, port, timeout);return jedisPool;}}
配置文件 application.properties中加入:
#redis# Redis服务器地址
spring.redis.host= localhost# Redis服务器连接端口
spring.redis.port= 6379# 连接池中的最大空闲连接
spring.redis.pool.max-idle= 8# 连接池中的最小空闲连接
spring.redis.pool.min-idle= 0# 连接池最大连接数(使用负值表示没有限制)
spring.redis.pool.max-active= 8# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.pool.max-wait= -1# 连接超时时间(毫秒)
spring.redis.timeout= 0
当然运行的时候要先启动redis。将自己的redis配置在以上配置中。这样session就存在redis中了。
上面ShiroConfig中的securityManager()方法中,我把
//securityManager.setCacheManager(cacheManager());
这行代码注了,是这样的,因为每次在需要验证的地方,比如在subject.hasRole(“admin”) 或 subject.isPermitted(“admin”)、@RequiresRoles(“admin”) 、 shiro:hasPermission=”/users/add”的时候都会调用MyShiroRealm中的doGetAuthorizationInfo()。但是以为这些信息不是经常变的,所以有必要进行缓存。把这行代码的注释打开,的时候都会调用MyShiroRealm中的doGetAuthorizationInfo()的返回结果会被redis缓存。但是这里稍微有个小问题,就是在刚修改用户的权限时,无法立即失效。本来我是使用了ShiroService中的clearUserAuthByUserId()想清除当前session存在的用户的权限缓存,但是没有效果。不知道什么原因。希望哪个大神看到后帮忙弄个解决方法。所以我干脆就把doGetAuthorizationInfo()的返回结果通过spring cache的方式加入缓存。
@Cacheable(cacheNames="resources",key="#map['userid'].toString()+#map['type']")public List<Resources> loadUserResources(Map<String, Object> map) {return resourcesMapper.loadUserResources(map);}
这样也可以实现,然后在修改权限时加上注解
@CacheEvict(cacheNames="resources", allEntries=true)
这样修改权限后可以立即生效。其实我感觉这样不好,因为清楚了我是清除了所有用户的权限缓存,其实只要修改当前session在线中被修改权限的用户就行了。 先这样吧,以后再研究下,修改得更好一点。
按钮控制
在前端页面,对按钮进行细粒度权限控制,只需要在按钮上加上shiro:hasPermission
<button shiro:hasPermission="/users/add" type="button" onclick="$('#addUser').modal();" class="btn btn-info" >新增</button>
这里的参数就是我们在ShiroConfig-shirFilter()权限加载时的过滤器 中的value,也就是资源的url。
filterChainDefinitionMap.put(resources.getResurl(),permission);
8.效果图
9.运行、下载
下载项目后运行resources下的shiro.sql文件。需要运行redis后运行项目。访问http://localhost:8080/ 账号密码:admin admin 或user1 user1.新增的用户也可以登录。
github下载地址:https://github.com/lovelyCoder/springboot-shiro
转载请标明出处:http://blog.csdn.net/poorCoder_/article/details/71374002
阅读全文
上一篇SSM整合SpringSecurity实现权限管理实例 javaconfig配置方式 下一篇MySQL EXPLAIN详解
SpringBoot整合mybatis、shiro、redis实现基于数据库的细粒度动态权限管理系统实例(转)...相关推荐
- SpringBoot整合mybatis、shiro、redis实现基于数据库的细粒度动态权限管理系统实例...
SpringBoot整合mybatis.shiro.redis实现基于数据库的细粒度动态权限管理系统实例 shiro 目录(?)[+] 1.前言 本文主要介绍使用SpringBoot与shiro实现基 ...
- 【SpringBoot整合Mybatis】数据库某字段值为空时,接口未返回该字段 解决办法
[SpringBoot整合Mybatis]数据库字段为空时,接口不返回该字段 解决办法 问题描述: 排查问题: 解决问题: 测试: 参考资料: 今天整合项目的时候,发现了SpringBoot整合Myb ...
- SpringBoot整合Mybatis(高级)
SpringBoot整合Mybatis(高级) 文章目录 SpringBoot整合Mybatis(高级) 前言 基础环境配置 增删改查 ResultMap 复杂查询 多对一 一对多 动态SQL if ...
- springboot整合mybatis
3.springboot整合mybatis 首先新建一个项目,勾选上我们需要的 1.springboot配置数据库连接池druid druid学习地址 https://github.com/aliba ...
- Springboot整合mybatis plus生成代码
一.Springboot整合mybatis plus生成代码 1.介绍 1.1.前言 从零开始搭建一个项目骨架,最好选择合适熟悉的技术,并且在未来易拓展,适合微服务化体系等.所以一般以Springbo ...
- mybatis plugins_[MyBatis] SpringBoot 整合Mybatis
现在基本上搭建一个简单的工程都是三剑客 springboot+mybatis+redis 之前整合Mybatis 都是按照SSM来,所以,这一次带来SpringBoot+MyBatis 的快速整合 p ...
- SpringBoot整合Mybatis超详细流程
SpringBoot整合Mybatis超详细流程 文章目录 SpringBoot整合Mybatis超详细流程 前言 详细流程 0.引入Mybatis 1.创建数据 2.创建程序目录 3.理解后台访问流 ...
- 3、SpringBoot整合MyBatis注解版及配置文件版
目录 1.配置pom.xml 2.配置application.yml 3.配置DruidConfig关联yml的配置文件spring.datasource 4.创建数据库及数据库表结构 5.创建对应的 ...
- (一)SpringBoot 整合 MyBatis
一.工具 IDE:idea.DB:mysql 二.创建SpringBoot工程 在Idea中使用SpringInitializr模板创建SpringBoot工程,依赖选择如下: 这里也可以不选JDBC ...
最新文章
- google breakpad native crash分析工具
- 客户端如何连接 DataSnap Server 调用服务的方法
- 程序员心中都有一个江湖,java世界,就是一个江湖!
- Tomcat Server 配置
- android平板值得买吗,2021年一月更新1000-2000价位最全平板选购指南
- python日期时间
- 知识表示之二——产生式规则表示法
- 2018杭州云栖大会,梁胜博士的演讲PPT来啦!
- 研发工程师如何转型项目经理
- 构造函数和复制函数java_什么是Java构造函数?
- 优衣库KAWS联名款遭哄抢 大打出手场面惨烈 是我不懂时尚了吗?
- 高通发布《5G经济》研究:将催生12万亿美元市场
- 循环冗余校验(CRC,模2运算)
- 区间直觉模糊集相似度及matlab应用
- matlab调和均值滤波_求matlab均值滤波、中值滤波和领域平均滤波算法
- CTA策略01_dualThrust
- 压缩包文件解压找回密码
- ingress 七层负载均衡器
- 万测试验机软件,万测关注检查井盖质量检测
- [Oracle 11g r2(11.2.0.4.0)]集群守护进程gpnp介绍
热门文章
- Android ANR产生的原理和如何避免
- hdoj 1025 Constructing Roads In JGShining's Kingdom(最长上升子序列+二分)
- Android的消息机制(2)
- 数据库防护技术对比分析
- 谷歌Nexus 3开售时间曝光
- 致远互联“平台+生态”抢占数字化升级新赛
- 法总统:英国若“无协议脱欧” 将成最大输家
- C# 图片旋转360度程序
- Java 并发编程中使用 ReentrantLock 替代 synchronized 关键字原语
- ActionScript 3.0 Step By Step系列(五):走在面向对象开发的路上,以类为基础去思考编程问题...