spring boot结合shiro实现用户-角色-权限的控制(包含用户名密码登陆和手机号验证码登陆)
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实现用户-角色-权限的控制(包含用户名密码登陆和手机号验证码登陆)相关推荐
- spring oauth2 实现用户名密码登录、手机号验证码登录返回token
文章目录 介绍 实现功能 用户名密码登录 步骤 编写成功处理器 配置成功处理器 手机号验证码登录 步骤 重写SmsCodeAuthenticationSecurityConfig 测试 用户名密码登录 ...
- Spring boot整合shiro权限管理
apache shiro: https://shiro.apache.org/ Apache Shiro是一个强大且易用的Java安全框架,执行身份验证.授权.密码学和会话管理.使用Shiro的易于理 ...
- Spring Boot(十四):spring boot整合shiro-登录认证和权限管理
Spring Boot(十四):spring boot整合shiro-登录认证和权限管理 使用Spring Boot集成Apache Shiro.安全应该是互联网公司的一道生命线,几乎任何的公司都会涉 ...
- (转)Spring Boot (十四): Spring Boot 整合 Shiro-登录认证和权限管理
http://www.ityouknow.com/springboot/2017/06/26/spring-boot-shiro.html 这篇文章我们来学习如何使用 Spring Boot 集成 A ...
- 一篇搞定 SpringBoot+Mybatis+Shiro 实现多角色权限管理
初衷:我在网上想找整合springboot+mybatis+shiro并且多角色认证的博客,发现找了好久也没有找到想到的,现在自己会了,就打算写个博客分享出去,希望能帮到你. 原创不易,请点赞支持! ...
- Spring Boot整合Shiro + Springboot +vue
目录 02 Spring Boot整合Shiro p1.shiro概述 1 什么是Shiro 2 Shiro核心组件 p2.Shiro实现登录认证 AccountRealm.java QueryWra ...
- Spring Boot 整合 shiro 之盐值加密认证详解(六)
Spring Boot 整合 shiro 之盐值加密认证详解 概述 不加盐认证 加入密码认证核心代码 修改 CustomRealm 新增获取密文的方法 修改 doGetAuthenticationIn ...
- 六、Spring Boot整合Shiro
六.Spring Boot整合Shiro 6.1.整合思路 6.2.创建spring boot项目 6.3.引入shiro依赖 6.4.配置shiro环境 创建配置类ShiroConfig 1.配置: ...
- 有手就行的 Spring Boot 集成 Shiro
前言 Apache Shiro 是 Java 的一个安全框架.目前,使用 Apache Shiro 的人越来越多,因为它相当简单,对比 Spring Security,可能没有 Spring Se ...
最新文章
- oracle修改c root,从新发现Oracle太美之root.sh
- 使用Python中的reduce()函数求积
- Mysql之alter用法汇总
- Oracle监听器Server端与Client端配置实例
- python安装sqlite3_Python安装sqlite3
- 每周一个 Python 模块 | hashlib
- 编译原理实验报告_任意给定一个正规式 r (包括连接、或、闭包运算),根据 Thompson算法设计一个程序,生成与该正规式等价的 NFA N 。
- 基于墨刀的轻音乐播放器——乐享APP原型设计
- audio隐藏下载按钮
- ztree在onCheck()方法中防止因触发联动关系导致页面多次渲染而卡死的问题
- html th width无效 解决方法
- java中的字符串String的不可变性
- matlab简单函数画图例题,Matlab(十) 简单的函数画图
- XJTUSE计算机图形学总结笔记
- 电脑桌面上没有计算机回收站,电脑桌面没有回收站,怎么处理
- 电子内窥镜的研究现状及发展趋势
- pigeon主题如何将顶部图片扩大
- vuejs出的手机app有哪些_vue.js点餐app手机触屏滑动分类菜单切换代码
- 小米手机比较 联通、移动、电信 3G 支持比较
- 5g网站服务器宽带,别装有线宽带了,5G以后,有线宽带将被淘汰
热门文章
- C++中换行endl和\n的区别
- 用request获取请求地址Ip
- ROS学习之路的整理
- 百度地图Javascript API 使用记录
- html+css设置背景图移动以及人物行走的动画效果
- [计算机网络] 实验 5 电子邮件
- RT-Thread柿饼控件(5)-- ProgressBar
- 弘辽科技:直通车成交率多少正常?如何提高成交率?
- IDEA2018版本相关配置
- 关于Android Studio单元测试中“Method d in android.util.Log not mocked.”问题的解决。