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: -1
max-http-header-size: 4048576
spring:
profiles:
active: diagnose
application:
name: user-center
devtools:
restart:
enabled: true # 设置开启热部署
additional-paths: src/main/java # 重启目录
exclude: WEB-INF/**
freemarker:
cache: false # 页面不加载缓存,修改即时生效
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.1.199/zhzd?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 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: mysql
column-underline=true: # 默认驼峰
# 只打印sql日志和sql参数
logging:
file: logs/sys.log
level:
com:
etouch:
mapper: debug
# shiro自定义配置
shiro:
session:
jsessionid: x-auth-token
role:
superRole: superAdmin
---
# Redis数据源
spring:
profiles: diagnose
redis:
host: localhost
port: 6379
timeout: 6000
password: null
database: 0
jedis:
pool:
max-active: 1000 # 连接池最大连接数(使用负值表示没有限制)
max-wait: -1 # 连接池最大阻塞等待时间(使用负值表示没有限制)
max-idle: 10 # 连接池中的最大空闲连接
min-idle: 5 # 连接池中的最小空闲连接
eureka:
instance:
prefer-ip-address: true
instance-id: user-center
lease-expiration-duration-in-seconds: 15
lease-renewal-interval-in-seconds: 5
client:
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 {
@Autowired
private UserService userService;
/**
* 权限校验
*
* @param principals
* @return
*/
@Override
protected 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
*/
@Override
protected 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 {
@Autowired
private SysMenuMapper menuMapper;
@Autowired
private SysUserMapper userMapper;
@Autowired
@Override
public Map<String, String> loadFilterChainDefinitionMap() {
// 权限控制map
Map<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;
}
@Override
public 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);
}
}
}
@Override
public 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;
@Bean
public 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;
}
@Bean
public CustomCredentialsMatcher credentialMatcher() {
return new CustomCredentialsMatcher();
}
// 将自己的验证方式加入容器,注入自定义的realm
@Bean
public MyShiroRealm myShiroRealm() {
MyShiroRealm myShiroRealm = new MyShiroRealm();
//将密码比较器注入自定义realm域
myShiroRealm.setCredentialsMatcher(credentialMatcher());
myShiroRealm.setCacheManager(cacheManager());
return myShiroRealm;
}
@Bean
public 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();
// 设置realms
List<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
*/
@Bean
public SimpleCookie rememberMeCookie() {
//这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
//setcookie的httponly属性如果设为true的话,会增加对xss防护的安全系数。它有以下特点:
//setcookie()的第七个参数
//设为true后,只能通过http访问,javascript无法访问
//防止xss读取cookie
simpleCookie.setHttpOnly(true);
simpleCookie.setPath("/");
//<!-- 记住我cookie生效时间30天 ,单位秒;-->
simpleCookie.setMaxAge(2592000);
return simpleCookie;
}
/**
* cookie管理对象;记住我功能,rememberMe管理器
*
* @return
*/
@Bean
public CookieRememberMeManager rememberMeManager() {
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememberMeCookie());
//rememberMe cookie加密的密钥 建议每个项目都不一样 默认AES算法 密钥长度(128 256 512 位)
cookieRememberMeManager.setCipherKey(Base64.decode("4AvVhmFLUs0KTA3Kprsdag=="));
return cookieRememberMeManager;
}
// 加入注解的使用,不加入这个注解不生效
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* 配置Redis管理器:使用的是shiro-redis开源插件
*/
@Bean
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost(host);
redisManager.setPort(port);
redisManager.setTimeout(timeout);
// redisManager.setPassword(password);
return redisManager;
}
/**
* 配置Cache管理器:用于往Redis存储权限和角色标识 (使用的是shiro-redis开源插件)
*/
@Bean
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
redisCacheManager.setKeyPrefix(CACHE_KEY);
// 配置缓存的话要求放在session里面的实体类必须有个id标识 注:这里id为用户表中的主键,否-> 报:User must has getter for field: xx
redisCacheManager.setPrincipalIdFieldName("id");
return redisCacheManager;
}
/**
* SessionID生成器
*/
@Bean
public ShiroSessionIdGenerator sessionIdGenerator() {
return new ShiroSessionIdGenerator();
}
/**
* 配置RedisSessionDAO (使用的是shiro-redis开源插件)
*/
@Bean
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
redisSessionDAO.setSessionIdGenerator(sessionIdGenerator());
redisSessionDAO.setKeyPrefix(SESSION_KEY);
redisSessionDAO.setExpire(EXPIRE);
return redisSessionDAO;
}
/**
* 配置Session管理器
*/
@Bean
public SessionManager sessionManager() {
ShiroSessionManager shiroSessionManager = new ShiroSessionManager();
shiroSessionManager.setGlobalSessionTimeout(EXPIRE);
shiroSessionManager.setSessionDAO(redisSessionDAO());//修改默认session存储机制,将shiro中的session数据存储至redis数据库中
shiroSessionManager.setSessionIdCookie(getSessionIdCookie());//设置sessionId的Key
shiroSessionManager.setDeleteInvalidSessions(true);//自动清空失效session
return shiroSessionManager;
}
/**
* 给shiro的sessionId默认的JSSESSIONID名字改掉
* @return
*/
@Bean(name="sessionIdCookie")
public SimpleCookie getSessionIdCookie(){
SimpleCookie simpleCookie = new SimpleCookie(jsessionid);
return simpleCookie;
}
/**
* 该类如果不设置为static,@Value注解就无效,原因未知
* @return
*/
@Bean
public 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 则登录成功
*/
@Override
public 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管理器
```java
package 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)
*/
@Override
public Serializable getSessionId(ServletRequest request, ServletResponse response) {
String token = WebUtils.toHttp(request).getHeader(Constants.REQUEST_HEADER);
// 如果请求头中存在token 则从请求头中获取token
if ( 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取token
return 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和token
String 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;
}
@Override
public String getHost() {
return host;
}
@Override
public 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 {
@Autowired
private UserService userService;
// 登录认证
@Override
protected 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
*/
@Override
protected 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;
}
@Override
public 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和token
String 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) {
//从缓存中获取Session
Session session = null;
// 获取当前已登录的用户session列表
Collection<Session> sessions = redisSessionDAO.getActiveSessions();
UserDTO sysUserEntity;
Object attribute = null;
// 遍历Session,找到该用户名称对应的Session
for (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;
}
//删除session
if (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,找到该用户名称对应的Session
for (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;
}
}
}