spring boot整合shiro实现权限校验

1.首先导入项目所需jar包

   <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.3.RELEASE</version><relativePath/></parent><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><java.version>1.8</java.version><spring-cloud.version>Greenwich.RELEASE</spring-cloud.version><mysql.version>5.1.47</mysql.version><mybaitsPlus.version>3.1.1</mybaitsPlus.version><druid.version>1.0.31</druid.version><shiro-redis.version>3.1.0</shiro-redis.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!--devtools热部署--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><optional>true</optional><scope>runtime</scope></dependency><!-- mysql驱动 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.47</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.1.1</version></dependency><!--redis存储--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-core</artifactId><version>1.4.0</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring</artifactId><version>1.4.0</version></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.9</version><scope>compile</scope></dependency><!-- Shiro-redis插件 --><dependency><groupId>org.crazycake</groupId><artifactId>shiro-redis</artifactId><version>${shiro-redis.version}</version></dependency><dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId></dependency><dependency><groupId>org.springframework.session</groupId><artifactId>spring-session</artifactId><version>1.3.5.RELEASE</version></dependency><dependency><groupId>com.sun</groupId><artifactId>tools</artifactId><version>1.8.0</version><scope>system</scope><systemPath>${env.JAVA_HOME}/lib/tools.jar</systemPath><optional>true</optional></dependency><!-- 分页插件 --></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><fork>true</fork></configuration></plugin></plugins></build>

2. 编写yml和properties配置文件

yml文件

server:port: 8083#tomcat:#max-http-post-size: -1max-http-header-size: 4048576
spring:profiles:active: diagnoseapplication:name: user-centerdevtools:restart:enabled: true  #   设置开启热部署additional-paths: src/main/java #  重启目录exclude: WEB-INF/**freemarker:cache: false    #  页面不加载缓存,修改即时生效datasource:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://192.168.1.199/zhzd?useUnicode=true&characterEncoding=utf-8&useSSL=falseusername: rootpassword: root#  myBatis-plus
mybatis-plus:#configuration:#log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #  输出sql日志和查询结果日志mapper-locations: classpath*:mapper/**Mapper.xml     #  .xml目录type-aliases-package: com.etouch.mapper          #  接口包global-config:db-config:db-type: mysqlcolumn-underline=true: #  默认驼峰# 只打印sql日志和sql参数
logging:file: logs/sys.loglevel:com:etouch:mapper: debug# shiro自定义配置
shiro:session:jsessionid: x-auth-tokenrole:superRole: superAdmin---
#   Redis数据源
spring:profiles: diagnoseredis:host: localhostport: 6379timeout: 6000password: nulldatabase: 0jedis:pool:max-active: 1000  #   连接池最大连接数(使用负值表示没有限制)max-wait: -1      #   连接池最大阻塞等待时间(使用负值表示没有限制)max-idle: 10      #   连接池中的最大空闲连接min-idle: 5       #   连接池中的最小空闲连接
eureka:instance:prefer-ip-address: trueinstance-id: user-centerlease-expiration-duration-in-seconds: 15lease-renewal-interval-in-seconds: 5client:service-url:defaultZone:  http://eureka:eureka@localhost:8800/eureka/registry-fetch-interval-seconds: 5

properties文件


#redis 中存储的用户登录数据结构
user.login.redis=zhzd:user:login:
#redis 中存储的验证码结构
user.send.code=zhzd:user:code:
#redis 中存储的用户信息
user.info.dto=zhzd:user:login:dto:

3.新增MyShiroRealm继承AuthorizingRealm,

package com.etouch.config.shiro;import com.etouch.DTO.MenuDTO;
import com.etouch.DTO.RoleDTO;
import com.etouch.DTO.UserDTO;
import com.etouch.corecenter.utils.UuidUtils;
import com.etouch.entity.SysMenu;
import com.etouch.entity.SysRole;
import com.etouch.entity.SysUser;
import com.etouch.entity.SysUserToken;
import com.etouch.service.SysUserTokenService;
import com.etouch.service.UserService;
import com.etouch.utils.ShiroUtils;
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.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;public class MyShiroRealm extends AuthorizingRealm {@Autowiredprivate UserService userService;/*** 权限校验** @param principals* @return*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();UserDTO user = (UserDTO) SecurityUtils.getSubject().getPrincipal();UserDTO userDTO = userService.findUserByName(user.getLoginName());for (RoleDTO role : userDTO.getRoleList()) {authorizationInfo.addRole(role.getRoleName());for (MenuDTO menu : role.getMenuDTOList()) {authorizationInfo.addStringPermission(menu.getMenuUrl());}}return authorizationInfo;}/*** 登录认证** @param token* @return* @throws AuthenticationException*/@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {//强转成自己传入的参数,要不然获取不到自己传入的用户名和密码UsernamePasswordToken upToken = (UsernamePasswordToken) token;//获得前台输入的用户名和密码String username = upToken.getUsername();String password = new String(upToken.getPassword());//根据邮箱获得数据库的用户UserDTO user = userService.findUserByName(username);if (user == null) {return null;  //return null 在底层会抛出异常}SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), this.getName());return info;}/*  // 清除缓存public void clearCached() {PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals();super.clearCache(principals);}*/

4. 新增动态获取权限, 校验权限业务类

接口类

package com.etouch.config.shiro.service;import org.apache.shiro.spring.web.ShiroFilterFactoryBean;import java.util.Map;/***  <p> shiro权限处理 </p>*/
public interface ShiroService {/*** 初始化权限 -> 拿全部权限** @param :* @return: java.util.Map<java.lang.String,java.lang.String>*/Map<String, String> loadFilterChainDefinitionMap();/*** 在对uri权限进行增删改操作时,需要调用此方法进行动态刷新加载数据库中的uri权限** @param shiroFilterFactoryBean* @param roleId* @param isRemoveSession:* @return: void*/void updatePermission(ShiroFilterFactoryBean shiroFilterFactoryBean, String roleId, Boolean isRemoveSession);/*** shiro动态权限加载 -> 原理:删除shiro缓存,重新执行doGetAuthorizationInfo方法授权角色和权限** @param roleId* @param isRemoveSession:* @return: void*/void updatePermissionByRoleId(String roleId, Boolean isRemoveSession);}

实现类

package com.etouch.config.shiro.service.impl;import com.etouch.config.shiro.service.ShiroService;
import com.etouch.entity.SysMenu;
import com.etouch.entity.SysUser;
import com.etouch.exception.ZhException;
import com.etouch.mapper.SysMenuMapper;
import com.etouch.mapper.SysRoleMapper;
import com.etouch.mapper.SysUserMapper;
import com.etouch.utils.ShiroUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager;
import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver;
import org.apache.shiro.web.servlet.AbstractShiroFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;/*** <p> shiro权限处理实现类 </p>** @description:* @author: zhengqing* @date: 2019/9/7 0007 13:53*/
@Slf4j
@Service
public class ShiroServiceImpl implements ShiroService {@Autowiredprivate SysMenuMapper menuMapper;@Autowiredprivate SysUserMapper userMapper;@Autowired@Overridepublic Map<String, String> loadFilterChainDefinitionMap() {// 权限控制mapMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();// 配置过滤:不会被拦截的链接 -> 放行 start ----------------------------------------------------------// 放行Swagger2页面,需要放行这些filterChainDefinitionMap.put("/swagger-ui.html", "anon");filterChainDefinitionMap.put("/swagger/**", "anon");filterChainDefinitionMap.put("/swagger-resources/**", "anon");filterChainDefinitionMap.put("/static/**", "anon");// 三方登录
//        filterChainDefinitionMap.put("/auth/loginByQQ", "anon");
//        filterChainDefinitionMap.put("/auth/afterlogin.do", "anon");// 退出// token过期接口filterChainDefinitionMap.put("/sysUser/tokenExpired", "anon");// 被挤下线filterChainDefinitionMap.put("/sysUser/downline", "anon");//手机号,验证码登录filterChainDefinitionMap.put("/sysUser/login", "anon");filterChainDefinitionMap.put("/sysUser/loginByPhone", "anon");filterChainDefinitionMap.put("/sysUser/logOut", "anon");//管理员可操作所有的//发送验证码和校验验证码filterChainDefinitionMap.put("/*/sendCode", "anon");filterChainDefinitionMap.put("/*/checkoutCode", "anon");//swaggerUI// 放行 end ----------------------------------------------------------//TODO 管理员可以访问任何接口filterChainDefinitionMap.put("/**", "roles[superAdmin]");// 从数据库或缓存中查取出来的url与resources对应则不会被拦截 放行List<SysMenu> permissionList = menuMapper.selectList(null);if (!CollectionUtils.isEmpty(permissionList)) {permissionList.forEach(e -> {if (StringUtils.isNotBlank(e.getMenuUrl())) {filterChainDefinitionMap.put(e.getMenuUrl(), "perms["+e.getMenuUrl()+"]");
//                        filterChainDefinitionMap.put("/api/system/user/listPage", "authc,token,zqPerms[user1]"); // 写死的一种用法}});}// ⑤ 认证登录  【注:map不能存放相同key】//TODO  开发阶段不需要登录就可以访问filterChainDefinitionMap.put("/**", "anon");return filterChainDefinitionMap;}@Overridepublic void updatePermission(ShiroFilterFactoryBean shiroFilterFactoryBean, String roleId, Boolean isRemoveSession) {synchronized (this) {AbstractShiroFilter shiroFilter;try {shiroFilter = (AbstractShiroFilter) shiroFilterFactoryBean.getObject();} catch (Exception e) {throw new ZhException("get ShiroFilter from shiroFilterFactoryBean error!");}PathMatchingFilterChainResolver filterChainResolver = (PathMatchingFilterChainResolver) shiroFilter.getFilterChainResolver();DefaultFilterChainManager manager = (DefaultFilterChainManager) filterChainResolver.getFilterChainManager();// 清空拦截管理器中的存储manager.getFilterChains().clear();// 清空拦截工厂中的存储,如果不清空这里,还会把之前的带进去//            ps:如果仅仅是更新的话,可以根据这里的 map 遍历数据修改,重新整理好权限再一起添加shiroFilterFactoryBean.getFilterChainDefinitionMap().clear();// 动态查询数据库中所有权限shiroFilterFactoryBean.setFilterChainDefinitionMap(loadFilterChainDefinitionMap());// 重新构建生成拦截Map<String, String> chains = shiroFilterFactoryBean.getFilterChainDefinitionMap();for (Map.Entry<String, String> entry : chains.entrySet()) {manager.createChain(entry.getKey(), entry.getValue());}log.info("--------------- 动态生成url权限成功! ---------------");// 动态更新该角色相关联的用户shiro权限if (roleId != null) {updatePermissionByRoleId(roleId, isRemoveSession);}}}@Overridepublic void updatePermissionByRoleId(String roleId, Boolean isRemoveSession) {// 查询当前角色的用户shiro缓存信息 -> 实现动态权限List<SysUser> userList = userMapper.selectUserByRoleId(roleId);// 删除当前角色关联的用户缓存信息,用户再次访问接口时会重新授权 ; isRemoveSession为true时删除Session -> 即强制用户退出if (!CollectionUtils.isEmpty(userList)) {for (SysUser user : userList) {ShiroUtils.deleteCache(user.getLoginName(), isRemoveSession);}}log.info("--------------- 动态修改用户权限成功! ---------------");}}

5. 新建shiro的配置文件ShiroConfig

package com.etouch.config.shiro;import java.util.ArrayList;
import java.util.List;import com.etouch.utils.CustomCredentialsMatcher;
import com.etouch.config.shiro.service.impl.ShiroServiceImpl;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.session.mgt.SessionManager;
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.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class ShiroConfig {private final String CACHE_KEY = "shiro:cache:";private final String SESSION_KEY = "shiro:session:";private final int EXPIRE = 1000*60*60*24*7; //TODO 单位毫秒/*** Redis配置*/@Value("${spring.redis.host}")private String host;@Value("${spring.redis.port}")private int port;@Value("${spring.redis.timeout}")private int timeout;@Value("${shiro.session.jsessionid}")private String jsessionid;@Beanpublic ShiroFilterFactoryBean shirFilter(SecurityManager securityManager, ShiroServiceImpl shiroService) {ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();shiroFilterFactoryBean.setSecurityManager(securityManager);/** 常用的过滤器如下:authc:所有已登陆用户可访问roles:有指定角色的用户可访问,通过[ ]指定具体角色,这里的角色名称与数据库中配置一致perms:有指定权限的用户可访问,通过[ ]指定具体权限,这里的权限名称与数据库中配置一致anon:所有用户可访问,通常作为指定页面的静态资源时使用* *///未登录提示shiroFilterFactoryBean.setLoginUrl("/sysUser/unLogin");//没有权限提示shiroFilterFactoryBean.setUnauthorizedUrl("/sysUser/unAuth");// 权限控制map.shiroFilterFactoryBean.setFilterChainDefinitionMap(shiroService.loadFilterChainDefinitionMap());return shiroFilterFactoryBean;}@Beanpublic CustomCredentialsMatcher credentialMatcher() {return new CustomCredentialsMatcher();}// 将自己的验证方式加入容器,注入自定义的realm@Beanpublic MyShiroRealm myShiroRealm() {MyShiroRealm myShiroRealm = new MyShiroRealm();//将密码比较器注入自定义realm域myShiroRealm.setCredentialsMatcher(credentialMatcher());myShiroRealm.setCacheManager(cacheManager());return myShiroRealm;}@Beanpublic PhoneRealm phoneRealm() {PhoneRealm phoneRealm = new PhoneRealm();phoneRealm.setCacheManager(cacheManager());return phoneRealm;}// 权限管理,配置主要是Realm的管理认证/*** 配置核心安全事务管理器** @param shiroRealm* @return*/@Bean(name = "securityManager")public SecurityManager securityManager(@Qualifier("myShiroRealm") MyShiroRealm shiroRealm, PhoneRealm phoneRealm) {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();// 设置realmsList<Realm> realms = new ArrayList<>();realms.add(shiroRealm);realms.add(phoneRealm);//设置自定义realm.securityManager.setRealms(realms);//配置记住我securityManager.setRememberMeManager(rememberMeManager());// 自定义session管理securityManager.setSessionManager(sessionManager());// 自定义Cache实现缓存管理securityManager.setCacheManager(cacheManager());return securityManager;}/*** cookie对象;会话Cookie模板 ,默认为: JSESSIONID 问题: 与SERVLET容器名冲突,重新定义为sid或rememberMe,自定义** @return*/@Beanpublic SimpleCookie rememberMeCookie() {//这个参数是cookie的名称,对应前端的checkbox的name = rememberMeSimpleCookie simpleCookie = new SimpleCookie("rememberMe");//setcookie的httponly属性如果设为true的话,会增加对xss防护的安全系数。它有以下特点://setcookie()的第七个参数//设为true后,只能通过http访问,javascript无法访问//防止xss读取cookiesimpleCookie.setHttpOnly(true);simpleCookie.setPath("/");//<!-- 记住我cookie生效时间30天 ,单位秒;-->simpleCookie.setMaxAge(2592000);return simpleCookie;}/*** cookie管理对象;记住我功能,rememberMe管理器** @return*/@Beanpublic CookieRememberMeManager rememberMeManager() {CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();cookieRememberMeManager.setCookie(rememberMeCookie());//rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)cookieRememberMeManager.setCipherKey(Base64.decode("4AvVhmFLUs0KTA3Kprsdag=="));return cookieRememberMeManager;}// 加入注解的使用,不加入这个注解不生效@Beanpublic AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);return authorizationAttributeSourceAdvisor;}/*** 配置Redis管理器:使用的是shiro-redis开源插件*/@Beanpublic RedisManager redisManager() {RedisManager redisManager = new RedisManager();redisManager.setHost(host);redisManager.setPort(port);redisManager.setTimeout(timeout);
//        redisManager.setPassword(password);return redisManager;}/*** 配置Cache管理器:用于往Redis存储权限和角色标识  (使用的是shiro-redis开源插件)*/@Beanpublic RedisCacheManager cacheManager() {RedisCacheManager redisCacheManager = new RedisCacheManager();redisCacheManager.setRedisManager(redisManager());redisCacheManager.setKeyPrefix(CACHE_KEY);// 配置缓存的话要求放在session里面的实体类必须有个id标识 注:这里id为用户表中的主键,否-> 报:User must has getter for field: xxredisCacheManager.setPrincipalIdFieldName("id");return redisCacheManager;}/*** SessionID生成器*/@Beanpublic ShiroSessionIdGenerator sessionIdGenerator() {return new ShiroSessionIdGenerator();}/*** 配置RedisSessionDAO (使用的是shiro-redis开源插件)*/@Beanpublic RedisSessionDAO redisSessionDAO() {RedisSessionDAO redisSessionDAO = new RedisSessionDAO();redisSessionDAO.setRedisManager(redisManager());redisSessionDAO.setSessionIdGenerator(sessionIdGenerator());redisSessionDAO.setKeyPrefix(SESSION_KEY);redisSessionDAO.setExpire(EXPIRE);return redisSessionDAO;}/*** 配置Session管理器*/@Beanpublic SessionManager sessionManager() {ShiroSessionManager shiroSessionManager = new ShiroSessionManager();shiroSessionManager.setGlobalSessionTimeout(EXPIRE);shiroSessionManager.setSessionDAO(redisSessionDAO());//修改默认session存储机制,将shiro中的session数据存储至redis数据库中shiroSessionManager.setSessionIdCookie(getSessionIdCookie());//设置sessionId的KeyshiroSessionManager.setDeleteInvalidSessions(true);//自动清空失效sessionreturn shiroSessionManager;}/*** 给shiro的sessionId默认的JSSESSIONID名字改掉* @return*/@Bean(name="sessionIdCookie")public SimpleCookie getSessionIdCookie(){SimpleCookie simpleCookie = new SimpleCookie(jsessionid);return simpleCookie;}/*** 该类如果不设置为static,@Value注解就无效,原因未知* @return*/@Beanpublic static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {return new LifecycleBeanPostProcessor();}}

自定义密码比较器

package com.etouch.utils;import com.etouch.corecenter.utils.MD5Util;
import com.etouch.utils.Encrypt;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.CredentialsMatcher;public class CustomCredentialsMatcher implements CredentialsMatcher {/**** @param token 用户输入的用户名和密码* @param info  数据库中的用户名和密码* @return  返回如果是false 则登录失败, 返回true 则登录成功*/@Overridepublic boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info)  {/*** 验证数据库的密码 和 用户输入的密码 是否一致** @param token 用户输入的用户名 密码* @param info  数据库中的用户名 密码* @return 返回值 比较密码是否正确 比较正确 返回true 表示登陆成功 返回false 表示登陆失败(同时抛出异常)** 明文密码: 不加密 原始密码* 密文密码: 经过一定的加密处理 , 数据更加安全* md5加密 : 不可逆的加密方式* 先将加密后的数据 以及加密前的数据 提前保存了 根据对应的数据找即可* 123456 ->  xxxxyyyyqqqq** 如何屏蔽这种情况* 123456 ->  xxxxyyyyqqqq* 123456000 ->  asdasdfg* 用户输入的密码 + 固定值(不变的值) =  特殊的密码* 用户输入的密码 + 可变化的固定值 =  特殊的密码* password:123456 + 用户的登录名 = 特殊的密码* 加密: 直接转换* 加盐加密: 在原有的密码基础上 加上一定的参数 针对该用户而言 这个盐(新加入的参数) 是不变的*/// System.out.println("密码比较器执行");
//        //加盐加密
//        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
//        //获取用户输入的数据
//        String email = upToken.getUsername();
//        String password = new String(upToken.getPassword());
//        //对密码进行加密处理  加盐加密
//        String md5Pwd = Encrypt.md5(password, email);
//        //获得数据库的密码
//        String dbPwd = (String) info.getCredentials();
//        return md5Pwd.equals(dbPwd);//不加盐加密UsernamePasswordToken upToken = (UsernamePasswordToken) token;//获得用户输入的密码String password = new String(upToken.getPassword());//加密后的密码String md5Pwd = MD5Util.string2MD5(password);//获取数据库的密码String dbPwd = (String) info.getCredentials();return md5Pwd.equals(dbPwd);}
}
### 6. 新建shiro的session管理器```javapackage com.etouch.config.shiro;import com.etouch.utils.constants.Constants;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;import static org.apache.shiro.web.servlet.ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE;/***  <p> 自定义获取Token </p>** @description :* @author : zhengqing* @date : 2019/8/23 15:55*/
public class ShiroSessionManager extends DefaultWebSessionManager {/*** 定义常量*/
//    private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";/*** 重写构造器*/public ShiroSessionManager() {super();this.setDeleteInvalidSessions(true);}/*** 重写方法实现从请求头获取Token便于接口统一* 每次请求进来,Shiro会去从请求头找REQUEST_HEADER这个key对应的Value(Token)*/@Overridepublic Serializable getSessionId(ServletRequest request, ServletResponse response) {String token = WebUtils.toHttp(request).getHeader(Constants.REQUEST_HEADER);// 如果请求头中存在token 则从请求头中获取tokenif ( StringUtils.isNotBlank( token ) ) {request.setAttribute(REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, token);request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);return token;} else {// 否则按默认规则从cookie取tokenreturn super.getSessionId(request, response);}}
}

7. 用户名密码登陆

  /*** 用户名密码登录** @param userName* @param passWord* @param rememberMe* @return*/@ApiOperation(value = "用户名密码登录", notes = "用户名密码登录")@PostMapping("/login")public ResultUtils<String> login(HttpServletRequest request,@ApiParam(value = "用户名", required = true) @RequestParam(name = "userName") String userName,@ApiParam(value = "密码", required = true) @RequestParam(name = "passWord") String passWord,@ApiParam("记住密码,true,false") @RequestParam(name = "rememberMe") boolean rememberMe,@ApiParam("验证码") @RequestParam(name = "VerificationCode") String VerificationCode) {if (StringUtils.isBlank(userName) || StringUtils.isBlank(passWord)) {return ResultUtils.error("用户名或密码不能为空");}if (checkVerify(request, VerificationCode).getCode() != 200) {return ResultUtils.errorParam("验证码错误,请重新输入");}QueryWrapper<SysUser> queryWrapper = new QueryWrapper();queryWrapper.eq("login_name", userName);SysUser sysUser = sysUserService.getOne(queryWrapper);if (sysUser == null) {return ResultUtils.error("用户名不存在");}String encodePwd = MD5Util.string2MD5(passWord);log.info("加密码后的密码为: " + encodePwd);if (!encodePwd.equals(sysUser.getPassword())) {return ResultUtils.error("用户名或密码错误");}//登录前,先清除掉之前的用户登录信息this.shiroCheck();UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(userName, passWord, rememberMe);//登录操作Subject subject = SecurityUtils.getSubject();LoginUser loginUser = new LoginUser();//存入用户id和tokenString uToken = null;try {//进行shiro登录验证subject.login(usernamePasswordToken);uToken = ShiroUtils.getSession().getId().toString();//subject.getSession().setAttribute("loginUser", sysUser);//userService.login(sysUser, rememberMe, uToken);SecurityUtils.getSubject().getSession().setTimeout(1000*60*60*24*7);return ResultUtils.success("登陆成功", uToken);} catch (AuthenticationException e) {e.printStackTrace();return ResultUtils.error("登录失败");} catch (InvalidSessionException e) {e.printStackTrace();return ResultUtils.error("登录失败");}}

8 如需增加手机号, 验证码登陆, 则需进行以下配置

8.1重写shiro的token获取类

package com.etouch.config.shiro;import org.apache.shiro.authc.HostAuthenticationToken;
import org.apache.shiro.authc.RememberMeAuthenticationToken;import java.io.Serializable;public class UserNamePasswordPhoneToken implements HostAuthenticationToken, RememberMeAuthenticationToken, Serializable {// 手机号码private String phone;private boolean rememberMe;private String host;/*** 重写getPrincipal方法*/public Object getPrincipal() {return phone;}/*** 重写getCredentials方法*/public Object getCredentials() {return phone;}public UserNamePasswordPhoneToken() {this.rememberMe = false;}public UserNamePasswordPhoneToken(String phone) {this(phone, false, null);}public UserNamePasswordPhoneToken(String phone, boolean rememberMe) {this(phone, rememberMe, null);}public UserNamePasswordPhoneToken(String phone, boolean rememberMe, String host) {this.phone = phone;this.rememberMe = rememberMe;this.host = host;}public String getPhone() {return phone;}public void setPhone(String phone) {this.phone = phone;}@Overridepublic String getHost() {return host;}@Overridepublic boolean isRememberMe() {return rememberMe;}
}

8.2 新建PhoneRealm 继承AuthorizingRealm

package com.etouch.config.shiro;import com.etouch.DTO.MenuDTO;
import com.etouch.DTO.RoleDTO;
import com.etouch.DTO.UserDTO;
import com.etouch.entity.SysUser;
import com.etouch.DTO.search.SearchUserArgs;
import com.etouch.service.UserService;
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.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;public class PhoneRealm extends AuthorizingRealm {@Autowiredprivate UserService userService;// 登录认证@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {UserNamePasswordPhoneToken token = null;// 如果是PhoneToken,则强转,获取phone;否则不处理。if (authenticationToken instanceof UserNamePasswordPhoneToken) {token = (UserNamePasswordPhoneToken) authenticationToken;} else {return null;}String phone = (String) token.getPrincipal();SearchUserArgs searchUserArgs = new SearchUserArgs();if (phone.contains("@")) {searchUserArgs.setEmail(phone);} else {searchUserArgs.setPhone(phone);}SysUser user = userService.findByArgs(searchUserArgs);UserDTO userDTO = new UserDTO();BeanUtils.copyProperties(user,userDTO);if (userDTO == null) {throw null;}return new SimpleAuthenticationInfo(userDTO, phone, this.getName());}/*** 权限校验* @param principalCollection* @return*/@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();UserDTO user = (UserDTO) SecurityUtils.getSubject().getPrincipal();UserDTO userDTO = userService.findUserByName(user.getLoginName());for (RoleDTO role : userDTO.getRoleList()) {authorizationInfo.addRole(role.getRoleName());for (MenuDTO menu : role.getMenuDTOList()) {authorizationInfo.addStringPermission(menu.getMenuUrl());}}return authorizationInfo;}@Overridepublic boolean supports(AuthenticationToken var1) {return var1 instanceof UserNamePasswordPhoneToken;}}

8.3 新增手机号验证码登陆

 @ApiOperation("手机号,验证码登录")@PostMapping("/loginByPhone")public ResultUtils<String> login(@ApiParam("手机号") @RequestParam(name = "phone") String phone,@ApiParam("验证码") @RequestParam(name = "code") String code, @ApiParam("记住密码") Boolean rememberMe) {//首先校验验证码Boolean b = userService.checkoutCode(phone, code);if (!b) {return ResultUtils.errorParam("验证码错误,请重新输入");}QueryWrapper<SysUser> queryWrapper = new QueryWrapper<>();//通过手机号或者与邮箱账号查询用户SearchUserArgs searchUserArgs = new SearchUserArgs();if (phone.contains("@")) {searchUserArgs.setEmail(phone);} else {searchUserArgs.setPhone(phone);}SysUser sysUser = userService.findByArgs(searchUserArgs);if (sysUser == null) {return ResultUtils.errorParam("查找不到用户信息");}this.shiroCheck();//清除掉这台客户端的上个用户的登录信息UserNamePasswordPhoneToken userNamePasswordPhoneToken = new UserNamePasswordPhoneToken(phone);//登录操作Subject subject = SecurityUtils.getSubject();LoginUser loginUser = new LoginUser();//存入用户id和tokenString uToken = null;try {//进行shiro登录验证uToken = ShiroUtils.getSession().getId().toString();subject.login(userNamePasswordPhoneToken);userService.login(sysUser, rememberMe, uToken);return ResultUtils.success("登陆成功", uToken);} catch (AuthenticationException e) {e.printStackTrace();return ResultUtils.error("登录失败");} catch (InvalidSessionException e) {e.printStackTrace();return ResultUtils.error("登录失败");}}

ShiroUtils工具类

package com.etouch.utils;import com.etouch.DTO.UserDTO;
import com.etouch.corecenter.enums.ExceptionEnum;
import com.etouch.corecenter.exception.ProjectExeption;
import com.etouch.entity.SysUser;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.Authenticator;
import org.apache.shiro.authc.LogoutAware;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.subject.support.DefaultSubjectContext;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.crazycake.shiro.RedisSessionDAO;import java.util.Collection;
import java.util.Objects;/*** <p> Shiro工具类 </p>*/
public class ShiroUtils {/*** 私有构造器**/private ShiroUtils() {}private static RedisSessionDAO redisSessionDAO = SpringUtil.getBean(RedisSessionDAO.class);//    public static final String LOGIN_USER_IN_SESSION = "loginUser";/*** 传入一个用户放到session中* @param user*/
//    public static void setUserInfo(User user){//        Session session = getSession();
//        // 将用户信息放到session中
//        session.setAttribute(LOGIN_USER_IN_SESSION, user);
//    }/*** 从session中获取一个用户的信息*/
//    public static User getUser(){//        Session session = getSession();
//        return (User) session.getAttribute(LOGIN_USER_IN_SESSION);
//    }// -------------------------------------------------------------/*** 获取当前用户Session** @Return SysUserEntity 用户信息*/public static Session getSession() {return SecurityUtils.getSubject().getSession();}/*** 用户登出*/public static void logout() {SecurityUtils.getSubject().logout();}/*** 获取当前用户信息** @Return SysUserEntity 用户信息*/public static UserDTO getUserInfo() {return (UserDTO) SecurityUtils.getSubject().getPrincipal();}/*** 获取当前登陆用户id** @Return*/public static String getLoginUserId() {UserDTO userDTO = (UserDTO) SecurityUtils.getSubject().getPrincipal();if (userDTO == null) {throw new ProjectExeption(ExceptionEnum.UNLOGIN);}return userDTO.getId();}/*** 删除用户缓存信息** @Param username  用户名称* @Param isRemoveSession 是否删除Session,删除后用户需重新登录*/public static void deleteCache(String username, boolean isRemoveSession) {//从缓存中获取SessionSession session = null;// 获取当前已登录的用户session列表Collection<Session> sessions = redisSessionDAO.getActiveSessions();UserDTO sysUserEntity;Object attribute = null;// 遍历Session,找到该用户名称对应的Sessionfor (Session sessionInfo : sessions) {attribute = sessionInfo.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);if (attribute == null) {continue;}sysUserEntity = (UserDTO) ((SimplePrincipalCollection) attribute).getPrimaryPrincipal();if (sysUserEntity == null) {continue;}if (Objects.equals(sysUserEntity.getLoginName(), username)) {session = sessionInfo;// 清除该用户以前登录时保存的session,强制退出  -> 单用户登录处理if (isRemoveSession) {redisSessionDAO.delete(session);}}}if (session == null || attribute == null) {return;}//删除sessionif (isRemoveSession) {redisSessionDAO.delete(session);}//删除Cache,再访问受限接口时会重新授权DefaultWebSecurityManager securityManager = (DefaultWebSecurityManager) SecurityUtils.getSecurityManager();Authenticator authc = securityManager.getAuthenticator();((LogoutAware) authc).onLogout((SimplePrincipalCollection) attribute);}/*** 从缓存中获取指定用户名的Session** @param username*/private static Session getSessionByUsername(String username) {// 获取当前已登录的用户session列表Collection<Session> sessions = redisSessionDAO.getActiveSessions();SysUser user;Object attribute;// 遍历Session,找到该用户名称对应的Sessionfor (Session session : sessions) {attribute = session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);if (attribute == null) {continue;}user = (SysUser) ((SimplePrincipalCollection) attribute).getPrimaryPrincipal();if (user == null) {continue;}if (Objects.equals(user.getLoginName(), username)) {return session;}}return null;}}

}

spring boot结合shiro实现用户-角色-权限的控制(包含用户名密码登陆和手机号验证码登陆)相关推荐

  1. spring oauth2 实现用户名密码登录、手机号验证码登录返回token

    文章目录 介绍 实现功能 用户名密码登录 步骤 编写成功处理器 配置成功处理器 手机号验证码登录 步骤 重写SmsCodeAuthenticationSecurityConfig 测试 用户名密码登录 ...

  2. Spring boot整合shiro权限管理

    apache shiro: https://shiro.apache.org/ Apache Shiro是一个强大且易用的Java安全框架,执行身份验证.授权.密码学和会话管理.使用Shiro的易于理 ...

  3. Spring Boot(十四):spring boot整合shiro-登录认证和权限管理

    Spring Boot(十四):spring boot整合shiro-登录认证和权限管理 使用Spring Boot集成Apache Shiro.安全应该是互联网公司的一道生命线,几乎任何的公司都会涉 ...

  4. (转)Spring Boot (十四): Spring Boot 整合 Shiro-登录认证和权限管理

    http://www.ityouknow.com/springboot/2017/06/26/spring-boot-shiro.html 这篇文章我们来学习如何使用 Spring Boot 集成 A ...

  5. 一篇搞定 SpringBoot+Mybatis+Shiro 实现多角色权限管理

    初衷:我在网上想找整合springboot+mybatis+shiro并且多角色认证的博客,发现找了好久也没有找到想到的,现在自己会了,就打算写个博客分享出去,希望能帮到你. 原创不易,请点赞支持! ...

  6. Spring Boot整合Shiro + Springboot +vue

    目录 02 Spring Boot整合Shiro p1.shiro概述 1 什么是Shiro 2 Shiro核心组件 p2.Shiro实现登录认证 AccountRealm.java QueryWra ...

  7. Spring Boot 整合 shiro 之盐值加密认证详解(六)

    Spring Boot 整合 shiro 之盐值加密认证详解 概述 不加盐认证 加入密码认证核心代码 修改 CustomRealm 新增获取密文的方法 修改 doGetAuthenticationIn ...

  8. 六、Spring Boot整合Shiro

    六.Spring Boot整合Shiro 6.1.整合思路 6.2.创建spring boot项目 6.3.引入shiro依赖 6.4.配置shiro环境 创建配置类ShiroConfig 1.配置: ...

  9. 有手就行的 Spring Boot 集成 Shiro

    前言   Apache Shiro 是 Java 的一个安全框架.目前,使用 Apache Shiro 的人越来越多,因为它相当简单,对比 Spring Security,可能没有 Spring Se ...

最新文章

  1. oracle修改c root,从新发现Oracle太美之root.sh
  2. 使用Python中的reduce()函数求积
  3. Mysql之alter用法汇总
  4. Oracle监听器Server端与Client端配置实例
  5. python安装sqlite3_Python安装sqlite3
  6. 每周一个 Python 模块 | hashlib
  7. 编译原理实验报告_任意给定一个正规式 r (包括连接、或、闭包运算),根据 Thompson算法设计一个程序,生成与该正规式等价的 NFA N 。
  8. 基于墨刀的轻音乐播放器——乐享APP原型设计
  9. audio隐藏下载按钮
  10. ztree在onCheck()方法中防止因触发联动关系导致页面多次渲染而卡死的问题
  11. html th width无效 解决方法
  12. java中的字符串String的不可变性
  13. matlab简单函数画图例题,Matlab(十) 简单的函数画图
  14. XJTUSE计算机图形学总结笔记
  15. 电脑桌面上没有计算机回收站,电脑桌面没有回收站,怎么处理
  16. 电子内窥镜的研究现状及发展趋势
  17. pigeon主题如何将顶部图片扩大
  18. vuejs出的手机app有哪些_vue.js点餐app手机触屏滑动分类菜单切换代码
  19. 小米手机比较 联通、移动、电信 3G 支持比较
  20. 5g网站服务器宽带,别装有线宽带了,5G以后,有线宽带将被淘汰

热门文章

  1. C++中换行endl和\n的区别
  2. 用request获取请求地址Ip
  3. ROS学习之路的整理
  4. 百度地图Javascript API 使用记录
  5. html+css设置背景图移动以及人物行走的动画效果
  6. [计算机网络] 实验 5 电子邮件
  7. RT-Thread柿饼控件(5)-- ProgressBar
  8. 弘辽科技:直通车成交率多少正常?如何提高成交率?
  9. IDEA2018版本相关配置
  10. 关于Android Studio单元测试中“Method d in android.util.Log not mocked.”问题的解决。