共计 30533 个字符,预计需要花费 77 分钟才能阅读完成。
起源:http://dwz.date/bRkG
作者:Sans_
一. 阐明
Shiro 是一个平安框架, 我的项目中次要用它做认证, 受权, 加密, 以及用户的会话治理, 尽管 Shiro 没有 SpringSecurity 性能更丰盛, 然而它轻量, 简略, 在我的项目中通常业务需要 Shiro 也都能胜任.
二. 我的项目环境
- MyBatis-Plus 版本: 3.1.0
- SpringBoot 版本:2.1.5
- JDK 版本:1.8
- Shiro 版本:1.4
- Shiro-redis 插件版本:3.1.0
数据表(SQL 文件在我的项目中): 数据库中测试号的明码进行了加密, 明码皆为 123456
数据表名 | 中文表名 | 备注阐明 |
---|---|---|
sys_user | 零碎用户表 | 根底表 |
sys_menu | 权限表 | 根底表 |
sys_role | 角色表 | 根底表 |
sys_role_menu | 角色与权限关系表 | 两头表 |
sys_user_role | 用户与角色关系表 | 两头表 |
Maven 依赖如下:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- AOP 依赖, 肯定要加, 否则权限拦挡验证不失效 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- lombok 插件 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<!-- mybatisPlus 外围库 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.0</version>
</dependency>
<!-- 引入阿里数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.6</version>
</dependency>
<!-- Shiro 外围依赖 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<!-- Shiro-redis 插件 -->
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.1.0</version>
</dependency>
<!-- StringUtilS 工具 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.5</version>
</dependency>
</dependencies>
配置如下:
# 配置端口
server:
port: 8764
spring:
# 配置数据源
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/my_shiro?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false
username: root
password: root
type: com.alibaba.druid.pool.DruidDataSource
# Redis 数据源
redis:
host: localhost
port: 6379
timeout: 6000
password: 123456
jedis:
pool:
max-active: 1000 # 连接池最大连接数(应用负值示意没有限度)max-wait: -1 # 连接池最大阻塞等待时间(应用负值示意没有限度)max-idle: 10 # 连接池中的最大闲暇连贯
min-idle: 5 # 连接池中的最小闲暇连贯
# mybatis-plus 相干配置
mybatis-plus:
# xml 扫描,多个目录用逗号或者分号分隔(通知 Mapper 所对应的 XML 文件地位)mapper-locations: classpath:mapper/*.xml
# 以下配置均有默认值, 能够不设置
global-config:
db-config:
#主键类型 AUTO:"数据库 ID 自增" INPUT:"用户输出 ID",ID_WORKER:"全局惟一 ID (数字类型惟一 ID)", UUID:"全局惟一 ID UUID";
id-type: auto
#字段策略 IGNORED:"疏忽判断" NOT_NULL:"非 NULL 判断") NOT_EMPTY:"非空判断"
field-strategy: NOT_EMPTY
#数据库类型
db-type: MYSQL
configuration:
# 是否开启主动驼峰命名规定映射: 从数据库列名到 Java 属性驼峰命名的相似映射
map-underscore-to-camel-case: true
# 返回 map 时 true: 当查问数据为空时字段返回为 null,false: 不加这个查问数据为空时,字段将被暗藏
call-setters-on-nulls: true
# 这个配置会将执行的 sql 打印进去,在开发或测试的时候能够用
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
二. 编写我的项目根底类
用户实体,Dao,Service 等在这里省略, 请参考源码
编写 Exception 类来解决 Shiro 权限拦挡异样
/**
* @Description 自定义异样
* @Author Sans
* @CreateTime 2019/6/15 22:56
*/
@ControllerAdvice
public class MyShiroException {
/**
* 解决 Shiro 权限拦挡异样
* 如果返回 JSON 数据格式请加上 @ResponseBody 注解
* @Author Sans
* @CreateTime 2019/6/15 13:35
* @Return Map<Object> 返回后果集
*/
@ResponseBody
@ExceptionHandler(value = AuthorizationException.class)
public Map<String,Object> defaultErrorHandler(){Map<String,Object> map = new HashMap<>();
map.put("403","权限有余");
return map;
}
}
创立 SHA256Util 加密工具
/**
* @Description Sha-256 加密工具
* @Author Sans
* @CreateTime 2019/6/12 9:27
*/
public class SHA256Util {
/** 公有结构器 **/
private SHA256Util(){};
/** 加密算法 **/
public final static String HASH_ALGORITHM_NAME = "SHA-256";
/** 循环次数 **/
public final static int HASH_ITERATIONS = 15;
/** 执行加密 - 采纳 SHA256 和盐值加密 **/
public static String sha256(String password, String salt) {return new SimpleHash(HASH_ALGORITHM_NAME, password, salt, HASH_ITERATIONS).toString();}
}
创立 Spring 工具
/**
* @Description Spring 上下文工具类
* @Author Sans
* @CreateTime 2019/6/17 13:40
*/
@Component
public class SpringUtil implements ApplicationContextAware {
private static ApplicationContext context;
/**
* Spring 在 bean 初始化后会判断是不是 ApplicationContextAware 的子类
* 如果该类是,setApplicationContext()办法, 会将容器中 ApplicationContext 作为参数传入进去
* @Author Sans
* @CreateTime 2019/6/17 16:58
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {context = applicationContext;}
/**
* 通过 Name 返回指定的 Bean
* @Author Sans
* @CreateTime 2019/6/17 16:03
*/
public static <T> T getBean(Class<T> beanClass) {return context.getBean(beanClass);
}
}
创立 Shiro 工具
/**
* @Description Shiro 工具类
* @Author Sans
* @CreateTime 2019/6/15 16:11
*/
public class ShiroUtils {
/** 公有结构器 **/
private ShiroUtils(){}
private static RedisSessionDAO redisSessionDAO = SpringUtil.getBean(RedisSessionDAO.class);
/**
* 获取以后用户 Session
* @Author Sans
* @CreateTime 2019/6/17 17:03
* @Return SysUserEntity 用户信息
*/
public static Session getSession() {return SecurityUtils.getSubject().getSession();}
/**
* 用户登出
* @Author Sans
* @CreateTime 2019/6/17 17:23
*/
public static void logout() {SecurityUtils.getSubject().logout();}
/**
* 获取以后用户信息
* @Author Sans
* @CreateTime 2019/6/17 17:03
* @Return SysUserEntity 用户信息
*/
public static SysUserEntity getUserInfo() {return (SysUserEntity) SecurityUtils.getSubject().getPrincipal();
}
/**
* 删除用户缓存信息
* @Author Sans
* @CreateTime 2019/6/17 13:57
* @Param username 用户名称
* @Param isRemoveSession 是否删除 Session
* @Return void
*/
public static void deleteCache(String username, boolean isRemoveSession){
// 从缓存中获取 Session
Session session = null;
Collection<Session> sessions = redisSessionDAO.getActiveSessions();
SysUserEntity sysUserEntity;
Object attribute = null;
for(Session sessionInfo : sessions){
// 遍历 Session, 找到该用户名称对应的 Session
attribute = sessionInfo.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
if (attribute == null) {continue;}
sysUserEntity = (SysUserEntity) ((SimplePrincipalCollection) attribute).getPrimaryPrincipal();
if (sysUserEntity == null) {continue;}
if (Objects.equals(sysUserEntity.getUsername(), username)) {
session=sessionInfo;
break;
}
}
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);
}
}
创立 Shiro 的 SessionId 生成器
/**
* @Description 自定义 SessionId 生成器
* @Author Sans
* @CreateTime 2019/6/11 11:48
*/
public class ShiroSessionIdGenerator implements SessionIdGenerator {
/**
* 实现 SessionId 生成
* @Author Sans
* @CreateTime 2019/6/11 11:54
*/
@Override
public Serializable generateId(Session session) {Serializable sessionId = new JavaUuidSessionIdGenerator().generateId(session);
return String.format("login_token_%s", sessionId);
}
}
三. 编写 Shiro 外围类
创立 Realm 用于受权和认证
/**
* @Description Shiro 权限匹配和账号密码匹配
* @Author Sans
* @CreateTime 2019/6/15 11:27
*/
public class ShiroRealm extends AuthorizingRealm {
@Autowired
private SysUserService sysUserService;
@Autowired
private SysRoleService sysRoleService;
@Autowired
private SysMenuService sysMenuService;
/**
* 受权权限
* 用户进行权限验证时候 Shiro 会去缓存中找, 如果查不到数据, 会执行这个办法去查权限, 并放入缓存中
* @Author Sans
* @CreateTime 2019/6/12 11:44
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
SysUserEntity sysUserEntity = (SysUserEntity) principalCollection.getPrimaryPrincipal();
// 获取用户 ID
Long userId =sysUserEntity.getUserId();
// 这里能够进行受权和解决
Set<String> rolesSet = new HashSet<>();
Set<String> permsSet = new HashSet<>();
// 查问角色和权限(这里依据业务自行查问)
List<SysRoleEntity> sysRoleEntityList = sysRoleService.selectSysRoleByUserId(userId);
for (SysRoleEntity sysRoleEntity:sysRoleEntityList) {rolesSet.add(sysRoleEntity.getRoleName());
List<SysMenuEntity> sysMenuEntityList = sysMenuService.selectSysMenuByRoleId(sysRoleEntity.getRoleId());
for (SysMenuEntity sysMenuEntity :sysMenuEntityList) {permsSet.add(sysMenuEntity.getPerms());
}
}
// 将查到的权限和角色别离传入 authorizationInfo 中
authorizationInfo.setStringPermissions(permsSet);
authorizationInfo.setRoles(rolesSet);
return authorizationInfo;
}
/**
* 身份认证
* @Author Sans
* @CreateTime 2019/6/12 12:36
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// 获取用户的输出的账号.
String username = (String) authenticationToken.getPrincipal();
// 通过 username 从数据库中查找 User 对象,如果找到进行验证
// 理论我的项目中, 这里能够依据理论状况做缓存, 如果不做,Shiro 本人也是有工夫距离机制,2 分钟内不会反复执行该办法
SysUserEntity user = sysUserService.selectUserByName(username);
// 判断账号是否存在
if (user == null) {throw new AuthenticationException();
}
// 判断账号是否被解冻
if (user.getState()==null||user.getState().equals("PROHIBIT")){throw new LockedAccountException();
}
// 进行验证
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
user, // 用户名
user.getPassword(), // 明码
ByteSource.Util.bytes(user.getSalt()), // 设置盐值
getName());
// 验证胜利开始踢人(革除缓存和 Session)
ShiroUtils.deleteCache(username,true);
return authenticationInfo;
}
}
创立 SessionManager 类
/**
* @Description 自定义获取 Token
* @Author Sans
* @CreateTime 2019/6/13 8:34
*/
public class ShiroSessionManager extends DefaultWebSessionManager {
// 定义常量
private static final String AUTHORIZATION = "Authorization";
private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
// 重写结构器
public ShiroSessionManager() {super();
this.setDeleteInvalidSessions(true);
}
/**
* 重写办法实现从申请头获取 Token 便于接口对立
* 每次申请进来,Shiro 会去从申请头找 Authorization 这个 key 对应的 Value(Token)
* @Author Sans
* @CreateTime 2019/6/13 8:47
*/
@Override
public Serializable getSessionId(ServletRequest request, ServletResponse response) {String token = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
// 如果申请头中存在 token 则从申请头中获取 token
if (!StringUtils.isEmpty(token)) {request.setAttribute(ShiroHttpServletRequest.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 获取形式
// 按默认规定从 Cookie 取 Token
// return super.getSessionId(request, response);
return null;
}
}
}
创立 ShiroConfig 配置类
/**
* @Description Shiro 配置类
* @Author Sans
* @CreateTime 2019/6/10 17:42
*/
@Configuration
public class ShiroConfig {
private final String CACHE_KEY = "shiro:cache:";
private final String SESSION_KEY = "shiro:session:";
//Redis 配置
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.timeout}")
private int timeout;
@Value("${spring.redis.password}")
private String password;
/**
* 开启 Shiro-aop 注解反对
* @Attention 应用代理形式所以须要开启代码反对
* @Author Sans
* @CreateTime 2019/6/12 8:38
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* Shiro 根底配置
* @Author Sans
* @CreateTime 2019/6/12 8:42
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactory(SecurityManager securityManager){ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// 留神过滤器配置程序不能颠倒
// 配置过滤: 不会被拦挡的链接
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/userLogin/**", "anon");
filterChainDefinitionMap.put("/**", "authc");
// 配置 shiro 默认登录界面地址,前后端拆散中登录界面跳转应由前端路由管制,后盾仅返回 json 数据
shiroFilterFactoryBean.setLoginUrl("/userLogin/unauth");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* 平安管理器
* @Author Sans
* @CreateTime 2019/6/12 10:34
*/
@Bean
public SecurityManager securityManager() {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 自定义 Ssession 治理
securityManager.setSessionManager(sessionManager());
// 自定义 Cache 实现
securityManager.setCacheManager(cacheManager());
// 自定义 Realm 验证
securityManager.setRealm(shiroRealm());
return securityManager;
}
/**
* 身份验证器
* @Author Sans
* @CreateTime 2019/6/12 10:37
*/
@Bean
public ShiroRealm shiroRealm() {ShiroRealm shiroRealm = new ShiroRealm();
shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return shiroRealm;
}
/**
* 凭证匹配器
* 将明码校验交给 Shiro 的 SimpleAuthenticationInfo 进行解决, 在这里做匹配配置
* @Author Sans
* @CreateTime 2019/6/12 10:48
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {HashedCredentialsMatcher shaCredentialsMatcher = new HashedCredentialsMatcher();
// 散列算法: 这里应用 SHA256 算法;
shaCredentialsMatcher.setHashAlgorithmName(SHA256Util.HASH_ALGORITHM_NAME);
// 散列的次数,比方散列两次,相当于 md5(md5(""));
shaCredentialsMatcher.setHashIterations(SHA256Util.HASH_ITERATIONS);
return shaCredentialsMatcher;
}
/**
* 配置 Redis 管理器
* @Attention 应用的是 shiro-redis 开源插件
* @Author Sans
* @CreateTime 2019/6/12 11:06
*/
@Bean
public RedisManager redisManager() {RedisManager redisManager = new RedisManager();
redisManager.setHost(host);
redisManager.setPort(port);
redisManager.setTimeout(timeout);
redisManager.setPassword(password);
return redisManager;
}
/**
* 配置 Cache 管理器
* 用于往 Redis 存储权限和角色标识
* @Attention 应用的是 shiro-redis 开源插件
* @Author Sans
* @CreateTime 2019/6/12 12:37
*/
@Bean
public RedisCacheManager cacheManager() {RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
redisCacheManager.setKeyPrefix(CACHE_KEY);
// 配置缓存的话要求放在 session 外面的实体类必须有个 id 标识
redisCacheManager.setPrincipalIdFieldName("userId");
return redisCacheManager;
}
/**
* SessionID 生成器
* @Author Sans
* @CreateTime 2019/6/12 13:12
*/
@Bean
public ShiroSessionIdGenerator sessionIdGenerator(){return new ShiroSessionIdGenerator();
}
/**
* 配置 RedisSessionDAO
* @Attention 应用的是 shiro-redis 开源插件
* @Author Sans
* @CreateTime 2019/6/12 13:44
*/
@Bean
public RedisSessionDAO redisSessionDAO() {RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
redisSessionDAO.setSessionIdGenerator(sessionIdGenerator());
redisSessionDAO.setKeyPrefix(SESSION_KEY);
redisSessionDAO.setExpire(timeout);
return redisSessionDAO;
}
/**
* 配置 Session 管理器
* @Author Sans
* @CreateTime 2019/6/12 14:25
*/
@Bean
public SessionManager sessionManager() {ShiroSessionManager shiroSessionManager = new ShiroSessionManager();
shiroSessionManager.setSessionDAO(redisSessionDAO());
return shiroSessionManager;
}
}
四. 实现权限管制
Shiro 能够用代码或者注解来管制权限, 通常咱们应用注解管制, 不仅简略不便, 而且更加灵便.Shiro 注解一共有五个:
注解名称 | 阐明 |
---|---|
RequiresAuthentication | 应用该注解标注的类, 办法等在拜访时, 以后 Subject 必须在以后 session 中曾经过认证. |
RequiresGuest | 应用该注解标注的类, 办法等在拜访时, 以后 Subject 能够是“gust”身份, 不须要通过认证或者在原先的 session 中存在记录. |
RequiresUser | 验证用户是否被记忆, 有两种含意: 一种是胜利登录的 (subject.isAuthenticated() 后果为 true); 另外一种是被记忆的 (subject.isRemembered() 后果为 true). |
RequiresPermissions | 以后 Subject 须要领有某些特定的权限时, 能力执行被该注解标注的办法. 如果没有权限, 则办法不会执行还会抛出 AuthorizationException 异样. |
RequiresRoles | 以后 Subject 必须领有所有指定的角色时, 能力拜访被该注解标注的办法. 如果没有角色, 则办法不会执行还会抛出 AuthorizationException 异样. |
个别状况下咱们在我的项目中做权限管制, 应用最多的是 RequiresPermissions 和 RequiresRoles, 容许存在多个角色和权限, 默认逻辑是 AND, 也就是同时领有这些才能够拜访办法, 能够在注解中以参数的模式设置成 OR
示例
// 领有一个角色就能够拜访
@RequiresRoles(value={"ADMIN","USER"},logical = Logical.OR)
// 领有所有权限才能够拜访
@RequiresPermissions(value={"sys:user:info","sys:role:info"},logical = Logical.AND)
应用程序:Shiro 注解是存在程序的, 当多个注解在一个办法上的时候, 会一一查看, 晓得全副通过为止, 默认拦挡程序是:
RequiresRoles->RequiresPermissions->RequiresAuthentication->RequiresUser->RequiresGuest
示例
// 领有 ADMIN 角色同时还要有 sys:role:info 权限
@RequiresRoles(value={"ADMIN")
@RequiresPermissions("sys:role:info")
创立 UserRoleController 角色拦挡测试类
/**
* @Description 角色测试
* @Author Sans
* @CreateTime 2019/6/19 11:38
*/
@RestController
@RequestMapping("/role")
public class UserRoleController {
@Autowired
private SysUserService sysUserService;
@Autowired
private SysRoleService sysRoleService;
@Autowired
private SysMenuService sysMenuService;
@Autowired
private SysRoleMenuService sysRoleMenuService;
/**
* 管理员角色测试接口
* @Author Sans
* @CreateTime 2019/6/19 10:38
* @Return Map<String,Object> 返回后果
*/
@RequestMapping("/getAdminInfo")
@RequiresRoles("ADMIN")
public Map<String,Object> getAdminInfo(){Map<String,Object> map = new HashMap<>();
map.put("code",200);
map.put("msg","这里是只有管理员角色能拜访的接口");
return map;
}
/**
* 用户角色测试接口
* @Author Sans
* @CreateTime 2019/6/19 10:38
* @Return Map<String,Object> 返回后果
*/
@RequestMapping("/getUserInfo")
@RequiresRoles("USER")
public Map<String,Object> getUserInfo(){Map<String,Object> map = new HashMap<>();
map.put("code",200);
map.put("msg","这里是只有用户角色能拜访的接口");
return map;
}
/**
* 角色测试接口
* @Author Sans
* @CreateTime 2019/6/19 10:38
* @Return Map<String,Object> 返回后果
*/
@RequestMapping("/getRoleInfo")
@RequiresRoles(value={"ADMIN","USER"},logical = Logical.OR)
@RequiresUser
public Map<String,Object> getRoleInfo(){Map<String,Object> map = new HashMap<>();
map.put("code",200);
map.put("msg","这里是只有有 ADMIN 或者 USER 角色能拜访的接口");
return map;
}
/**
* 登出(测试登出)
* @Author Sans
* @CreateTime 2019/6/19 10:38
* @Return Map<String,Object> 返回后果
*/
@RequestMapping("/getLogout")
@RequiresUser
public Map<String,Object> getLogout(){ShiroUtils.logout();
Map<String,Object> map = new HashMap<>();
map.put("code",200);
map.put("msg","登出");
return map;
}
}
创立 UserMenuController 权限拦挡测试类
/**
* @Description 权限测试
* @Author Sans
* @CreateTime 2019/6/19 11:38
*/
@RestController
@RequestMapping("/menu")
public class UserMenuController {
@Autowired
private SysUserService sysUserService;
@Autowired
private SysRoleService sysRoleService;
@Autowired
private SysMenuService sysMenuService;
@Autowired
private SysRoleMenuService sysRoleMenuService;
/**
* 获取用户信息汇合
* @Author Sans
* @CreateTime 2019/6/19 10:36
* @Return Map<String,Object> 返回后果
*/
@RequestMapping("/getUserInfoList")
@RequiresPermissions("sys:user:info")
public Map<String,Object> getUserInfoList(){Map<String,Object> map = new HashMap<>();
List<SysUserEntity> sysUserEntityList = sysUserService.list();
map.put("sysUserEntityList",sysUserEntityList);
return map;
}
/**
* 获取角色信息汇合
* @Author Sans
* @CreateTime 2019/6/19 10:37
* @Return Map<String,Object> 返回后果
*/
@RequestMapping("/getRoleInfoList")
@RequiresPermissions("sys:role:info")
public Map<String,Object> getRoleInfoList(){Map<String,Object> map = new HashMap<>();
List<SysRoleEntity> sysRoleEntityList = sysRoleService.list();
map.put("sysRoleEntityList",sysRoleEntityList);
return map;
}
/**
* 获取权限信息汇合
* @Author Sans
* @CreateTime 2019/6/19 10:38
* @Return Map<String,Object> 返回后果
*/
@RequestMapping("/getMenuInfoList")
@RequiresPermissions("sys:menu:info")
public Map<String,Object> getMenuInfoList(){Map<String,Object> map = new HashMap<>();
List<SysMenuEntity> sysMenuEntityList = sysMenuService.list();
map.put("sysMenuEntityList",sysMenuEntityList);
return map;
}
/**
* 获取所有数据
* @Author Sans
* @CreateTime 2019/6/19 10:38
* @Return Map<String,Object> 返回后果
*/
@RequestMapping("/getInfoAll")
@RequiresPermissions("sys:info:all")
public Map<String,Object> getInfoAll(){Map<String,Object> map = new HashMap<>();
List<SysUserEntity> sysUserEntityList = sysUserService.list();
map.put("sysUserEntityList",sysUserEntityList);
List<SysRoleEntity> sysRoleEntityList = sysRoleService.list();
map.put("sysRoleEntityList",sysRoleEntityList);
List<SysMenuEntity> sysMenuEntityList = sysMenuService.list();
map.put("sysMenuEntityList",sysMenuEntityList);
return map;
}
/**
* 增加管理员角色权限(测试动静权限更新)
* @Author Sans
* @CreateTime 2019/6/19 10:39
* @Param username 用户 ID
* @Return Map<String,Object> 返回后果
*/
@RequestMapping("/addMenu")
public Map<String,Object> addMenu(){
// 增加管理员角色权限
SysRoleMenuEntity sysRoleMenuEntity = new SysRoleMenuEntity();
sysRoleMenuEntity.setMenuId(4L);
sysRoleMenuEntity.setRoleId(1L);
sysRoleMenuService.save(sysRoleMenuEntity);
// 革除缓存
String username = "admin";
ShiroUtils.deleteCache(username,false);
Map<String,Object> map = new HashMap<>();
map.put("code",200);
map.put("msg","权限增加胜利");
return map;
}
}
创立 UserLoginController 登录类
/**
* @Description 用户登录
* @Author Sans
* @CreateTime 2019/6/17 15:21
*/
@RestController
@RequestMapping("/userLogin")
public class UserLoginController {
/**
* 登录
* @Author Sans
* @CreateTime 2019/6/20 9:21
*/
@RequestMapping("/login")
public Map<String,Object> login(@RequestBody SysUserEntity sysUserEntity){Map<String,Object> map = new HashMap<>();
// 进行身份验证
try{
// 验证身份和登陆
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(sysUserEntity.getUsername(), sysUserEntity.getPassword());
// 验证胜利进行登录操作
subject.login(token);
}catch (IncorrectCredentialsException e) {map.put("code",500);
map.put("msg","用户不存在或者明码谬误");
return map;
} catch (LockedAccountException e) {map.put("code",500);
map.put("msg","登录失败,该用户已被解冻");
return map;
} catch (AuthenticationException e) {map.put("code",500);
map.put("msg","该用户不存在");
return map;
} catch (Exception e) {map.put("code",500);
map.put("msg","未知异样");
return map;
}
map.put("code",0);
map.put("msg","登录胜利");
map.put("token",ShiroUtils.getSession().getId().toString());
return map;
}
/**
* 未登录
* @Author Sans
* @CreateTime 2019/6/20 9:22
*/
@RequestMapping("/unauth")
public Map<String,Object> unauth(){Map<String,Object> map = new HashMap<>();
map.put("code",500);
map.put("msg","未登录");
return map;
}
/**
* 增加一个用户演示接口
* 这里仅作为演示不加任何权限和反复查问校验
* @Author Sans
* @CreateTime 2020/1/6 9:22
*/
@RequestMapping("/testAddUser")
public Map<String,Object> testAddUser(){
// 设置根底参数
SysUserEntity sysUser = new SysUserEntity();
sysUser.setUsername("user1");
sysUser.setState("NORMAL");
// 随机生成盐值
String salt = RandomStringUtils.randomAlphanumeric(20);
sysUser.setSalt(salt);
// 进行加密
String password ="123456";
sysUser.setPassword(SHA256Util.sha256(password, sysUser.getSalt()));
// 保留用户
sysUserService.save(sysUser);
// 保留角色
SysUserRoleEntity sysUserRoleEntity = new SysUserRoleEntity();
sysUserRoleEntity.setUserId(sysUser.getUserId()); // 保留用户完之后会把 ID 返回给用户实体
sysUserRoleService.save(sysUserRoleEntity);
// 返回后果
Map<String,Object> map = new HashMap<>();
map.put("code",0);
map.put("msg","增加胜利");
return map;
}
}
五.POSTMAN 测试
登录胜利后会返回 TOKEN, 因为是单点登录, 再次登陆的话会返回新的 TOKEN, 之前 Redis 的 TOKEN 就会生效了
当第一次拜访接口后咱们能够看到缓存中曾经有权限数据了, 在次访问接口的时候,Shiro 会间接去缓存中拿取权限, 留神拜访接口时候要设置申请头.
ADMIN 这个号当初没有 sys:info:all 这个权限的, 所以无法访问 getInfoAll 接口, 咱们要动态分配权限后, 要清掉缓存, 在拜访接口时候,Shiro 会去从新执行受权办法, 之后再次把权限和角色数据放入缓存中
拜访增加权限测试接口, 因为是测试, 我把减少权限的用户 ADMIN 写死在外面了, 权限增加后, 调用工具类清掉缓存, 咱们能够发现,Redis 中曾经没有缓存了
再次拜访 getInfoAll 接口, 因为缓存中没有数据,Shiro 会从新受权查问权限, 拦挡通过
六. 我的项目源码
码云: gitee.com/liselotte/s…
GitHub: github.com/xuyulong201…
谢谢大家浏览, 如果喜爱, 请珍藏点赞, 多给些 star, 文章不足之处, 也请给出宝贵意见.
举荐
- Netflix 微服务架构设计解析
- 为什么阿里规定须要在事务注解 @Transactional 中指定 rollbackFor?
- 下一代构建工具 Gradle,比 Maven 强在哪里!
- 如何让你的 Nginx 晋升 10 倍性能?
- 面试必须要明确的 Redis 分布式锁实现原理!
学习材料分享
12 套 微服务、Spring Boot、Spring Cloud 核心技术材料,这是局部材料目录:
- Spring Security 认证与受权
- Spring Boot 我的项目实战(中小型互联网公司后盾服务架构与运维架构)
- Spring Boot 我的项目实战(企业权限治理我的项目))
- Spring Cloud 微服务架构我的项目实战(分布式事务解决方案)
- …
公众号后盾回复
arch028
获取材料::