springboot+shiro+mybatis实现角色权限控制
背景
spring+spirngmvc+shiro的整合已经有很多了,之前的项目中也用过,但是最近想在springboot中使用shiro这样,其他项目需要的时候只需要把它依赖进来就可以直接使用,至于shiro的原理其他的blog都有很多介绍。这里只讲几个重点在项目中注意的地方。
shiro官网
http://shiro.apache.org/
shiro配置中重要的几个文件
其实最重要的就是shiro配置文件
ShiroConfiguration.java:shiro启动时候的初始化工作,比如哪些是需要认证,哪些不需要认证;缓存配置设置;shiro权限数据在页面展示时整合需要的模板套件配置,等等。
ShiroRealm.java:shiro权限认证的具体实现代码,因为shiro本身只提供拦截路由,而具体如何数据源则由用户自己提供,不同的项目不同的要求,要不要加缓存登陆验证次数,要不要密码加密设置其他具体方式,这些都由用户自己决定,而shiro只提供给用户权限验证的格式接口,通过用户提供的数据源shrio判断要不要给具体用户授权请求路径的判断。
ShiroRealm 涉及到以下点:
principal:主体,就是登陆的当前用户类型的数据实体
credentials:凭证,用户的密码,具体加密方式用户自己实现,什么都不做就是原文
- Roles:用户拥有的角色标识(角色名称,admin,account,customer_service),字符串格式列表:用户拥有多个角色的可能
- Permissions:用户拥有的权限标识(每个权限唯一标识,比如主键或者权限唯一标识编码),字符串格式列表:用户拥有多个权限的可能
pom
在springboot+mybatis基础上加入shiro依赖
<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.xm.shiro</groupId> <artifactId>springboot-shiro</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>springboot-shiro</name> <url>http://maven.apache.org</url> <repositories><!--ali 代码库 --> <repository> <id>maven-ali</id> <url>http://maven.aliyun.com/nexus/content/groups/public/</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>true</enabled> <updatePolicy>always</updatePolicy> <checksumPolicy>fail</checksumPolicy> </snapshots> </repository> </repositories> <!-- Spring Boot 启动父依赖 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.1.RELEASE</version> </parent> <properties> <mybatis-spring-boot>1.2.0</mybatis-spring-boot> <mysql-connector>5.1.39</mysql-connector> </properties> <dependencies> <!-- Spring Boot Web 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring Boot Test 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- Spring Boot Mybatis 依赖 --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>${mybatis-spring-boot}</version> </dependency> <!-- MySQL 连接驱动依赖 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql-connector}</version> </dependency> <!-- shiro相关 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.2.5</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.2.5</version> </dependency> </dependencies> </project>
表结构
/* Navicat MySQL Data Transfer Source Server : local Source Server Version : 50553 Source Host : localhost:3306 Source Database : springboot_shiro Target Server Type : MYSQL Target Server Version : 50553 File Encoding : 65001 Date: 2017-05-10 20:40:01 */ SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- -- Table structure for `u_permission` -- ---------------------------- DROP TABLE IF EXISTS `u_permission`; CREATE TABLE `u_permission` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `url` varchar(256) DEFAULT NULL COMMENT 'url地址', `name` varchar(64) DEFAULT NULL COMMENT 'url描述', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of u_permission -- ---------------------------- INSERT INTO `u_permission` VALUES ('1', '/user', 'usermanager'); -- ---------------------------- -- Table structure for `u_role` -- ---------------------------- DROP TABLE IF EXISTS `u_role`; CREATE TABLE `u_role` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(32) DEFAULT NULL COMMENT '角色名称', `type` varchar(10) DEFAULT NULL COMMENT '角色类型', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of u_role -- ---------------------------- INSERT INTO `u_role` VALUES ('1', 'admin', '1'); -- ---------------------------- -- Table structure for `u_role_permission` -- ---------------------------- DROP TABLE IF EXISTS `u_role_permission`; CREATE TABLE `u_role_permission` ( `rid` bigint(20) DEFAULT NULL COMMENT '角色ID', `pid` bigint(20) DEFAULT NULL COMMENT '权限ID' ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of u_role_permission -- ---------------------------- INSERT INTO `u_role_permission` VALUES ('1', '1'); -- ---------------------------- -- Table structure for `u_user` -- ---------------------------- DROP TABLE IF EXISTS `u_user`; CREATE TABLE `u_user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `nickname` varchar(20) DEFAULT NULL COMMENT '用户昵称', `email` varchar(128) DEFAULT NULL COMMENT '邮箱|登录帐号', `pswd` varchar(32) DEFAULT NULL COMMENT '密码', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `last_login_time` datetime DEFAULT NULL COMMENT '最后登录时间', `status` bigint(1) DEFAULT '1' COMMENT '1:有效,0:禁止登录', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of u_user -- ---------------------------- INSERT INTO `u_user` VALUES ('1', 'admin', null, '123456', '2017-05-10 20:22:59', null, '1'); -- ---------------------------- -- Table structure for `u_user_role` -- ---------------------------- DROP TABLE IF EXISTS `u_user_role`; CREATE TABLE `u_user_role` ( `uid` bigint(20) DEFAULT NULL COMMENT '用户ID', `rid` bigint(20) DEFAULT NULL COMMENT '角色ID' ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of u_user_role -- ---------------------------- INSERT INTO `u_user_role` VALUES ('1', '1');
配置shiro相关文件
ShiroConfiguration.java
package com.xm.shiro.config; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.cache.ehcache.EhCacheManager; 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.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 org.springframework.context.annotation.DependsOn; import java.util.LinkedHashMap; import java.util.Map; /** * shiro配置项 * Created by Lucare.Feng on 2017/3/6. */ @Configuration public class ShiroConfiguration { @Bean(name = "lifecycleBeanPostProcessor") public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } //处理认证匹配处理器:如果自定义需要实现继承HashedCredentialsMatcher //指定加密方式方式,也可以在这里加入缓存,当用户超过五次登陆错误就锁定该用户禁止不断尝试登陆 // @Bean(name = "hashedCredentialsMatcher") // public HashedCredentialsMatcher hashedCredentialsMatcher() { // HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(); // credentialsMatcher.setHashAlgorithmName("MD5"); // credentialsMatcher.setHashIterations(2); // credentialsMatcher.setStoredCredentialsHexEncoded(true); // return credentialsMatcher; // } @Bean(name = "shiroRealm") @DependsOn("lifecycleBeanPostProcessor") public ShiroRealm shiroRealm() { ShiroRealm realm = new ShiroRealm(); // realm.setCredentialsMatcher(hashedCredentialsMatcher()); return realm; } @Bean(name = "ehCacheManager") @DependsOn("lifecycleBeanPostProcessor") public EhCacheManager ehCacheManager(){ EhCacheManager ehCacheManager = new EhCacheManager(); return ehCacheManager; } @Bean(name = "securityManager") public DefaultWebSecurityManager securityManager(){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(shiroRealm()); securityManager.setCacheManager(ehCacheManager());//用户授权/认证信息Cache, 采用EhCache 缓存 return securityManager; } @Bean(name = "shiroFilter") public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager){ ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); // Map<String, Filter> filters = new LinkedHashMap<String, Filter>(); // LogoutFilter logoutFilter = new LogoutFilter(); // logoutFilter.setRedirectUrl("/login"); // filters.put("logout", logoutFilter); // shiroFilterFactoryBean.setFilters(filters); System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); Map<String, String> filterChainDefinitionManager = new LinkedHashMap<>(); filterChainDefinitionManager.put("/logout", "logout"); filterChainDefinitionManager.put("/user/**", "authc,roles[user]"); filterChainDefinitionManager.put("/shop/**", "authc,roles[shop]"); filterChainDefinitionManager.put("/admin/**", "authc,roles[admin]"); filterChainDefinitionManager.put("/login", "anon");//anon 可以理解为不拦截 filterChainDefinitionManager.put("/ajaxLogin", "anon");//anon 可以理解为不拦截 filterChainDefinitionManager.put("/statistic/**", "anon");//静态资源不拦截 filterChainDefinitionManager.put("/**", "authc,roles[user]");//其他资源全部拦截 shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionManager); shiroFilterFactoryBean.setLoginUrl("/login"); shiroFilterFactoryBean.setSuccessUrl("/"); shiroFilterFactoryBean.setUnauthorizedUrl("/403"); return shiroFilterFactoryBean; } @Bean @ConditionalOnMissingBean public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator(); daap.setProxyTargetClass(true); return daap; } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) { AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor(); aasa.setSecurityManager(securityManager); return aasa; } //thymeleaf模板引擎和shiro整合时使用 /*@Bean(name = "shiroDialect") public ShiroDialect shiroDialect(){ return new ShiroDialect(); }*/ }
ShiroRealm.java
package com.xm.shiro.config; import java.util.ArrayList; import java.util.List; 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.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import com.xm.shiro.admin.dao.UPermissionDao; import com.xm.shiro.admin.dao.URoleDao; import com.xm.shiro.admin.dao.UUserDao; import com.xm.shiro.admin.entity.UPermission; import com.xm.shiro.admin.entity.URole; import com.xm.shiro.admin.entity.UUser; /** * 获取用户的角色和权限信息 * Created by bamboo on 2017/5/10. */ public class ShiroRealm extends AuthorizingRealm { private Logger logger = LoggerFactory.getLogger(ShiroRealm.class); //一般这里都写的是servic,我省略了service的接口和实现方法直接调用的dao @Autowired private UUserDao uUserDao; @Autowired private URoleDao uRoleDao; @Autowired private UPermissionDao uPermissionDao; /** * 登录认证 * * @param authenticationToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; logger.info("验证当前Subject时获取到token为:" + token.toString()); //查出是否有此用户 UUser hasUser = uUserDao.findByName(token.getUsername()); // String md5Pwd = new Md5Hash("123", "lucare",2).toString(); if (hasUser != null) { // 若存在,将此用户存放到登录认证info中,无需自己做密码对比,Shiro会为我们进行密码对比校验 List<URole> rlist = uRoleDao.findRoleByUid(hasUser.getId());//获取用户角色 List<UPermission> plist = uPermissionDao.findPermissionByUid(hasUser.getId());//获取用户权限 List<String> roleStrlist=new ArrayList<String>();////用户的角色集合 List<String> perminsStrlist=new ArrayList<String>();//用户的权限集合 for (URole role : rlist) { roleStrlist.add(role.getName()); } for (UPermission uPermission : plist) { perminsStrlist.add(uPermission.getName()); } hasUser.setRoleStrlist(roleStrlist); hasUser.setPerminsStrlist(perminsStrlist); // Session session = SecurityUtils.getSubject().getSession(); // session.setAttribute("user", hasUser);//成功则放入session // 若存在,将此用户存放到登录认证info中,无需自己做密码对比,Shiro会为我们进行密码对比校验 return new SimpleAuthenticationInfo(hasUser, hasUser.getPswd(), getName()); } return null; } /** * 权限认证 * * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { logger.info("##################执行Shiro权限认证##################"); //获取当前登录输入的用户名,等价于(String) principalCollection.fromRealm(getName()).iterator().next(); // String loginName = (String) super.getAvailablePrincipal(principalCollection); UUser user = (UUser) principalCollection.getPrimaryPrincipal(); // //到数据库查是否有此对象 // User user = null;// 实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法 // user = userMapper.findByName(loginName); if (user != null) { //权限信息对象info,用来存放查出的用户的所有的角色(role)及权限(permission) SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); //用户的角色集合 info.addRoles(user.getRoleStrlist()); //用户的权限集合 info.addStringPermissions(user.getPerminsStrlist()); return info; } // 返回null的话,就会导致任何用户访问被拦截的请求时,都会自动跳转到unauthorizedUrl指定的地址 return null; } }
涉及到的两个方法
-
doGetAuthenticationInfo
获取用户的权限信息,这是为下一步的授权做判断,获取当前用户的角色和这些角色所拥有的权限信息。 -
doGetAuthorizationInfo
根据用户的权限信息做授权判断,这一步是以doGetAuthenticationInfo为基础的,只有在有用户信息后才能根据用户的角色和授权信息做判断是否给用户授权,因此这里的Roles和Permissions是用户的两个重点判断依据