前言
shiro是一个轻量级的权限框架,该篇我将会从0到1快速教大家搭建出一套包含角色,权限登录校验的项目。
就算你没了解过,也能学会。跟着我把代码敲一遍,这个项目就是属于你的。
该篇文章比较啰嗦,篇幅较长,如果不是入门的初学者大可不必从头开始阅读(我一般的教程都会以从零开始的方式,所以都比较啰嗦)。
正文
数据库的准备(Mysql):
在这是实践的案例里面,数据库表三张核心表,两张中间表(如果你表太多也可以自己调整,我这边是严格细分了):
数据库名:
my_system
核心表1 帐号表 , sys_user 表:
对应的mysql语句:
CREATE TABLE `sys_user` (
`userId` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户id 作为表主键 用于关联',
`userName` varchar(25) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户登录帐号',
`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '用户登录密码',
`userRemarks` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注,预留字段',
PRIMARY KEY (`userId`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 20002 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
核心表2 角色表,sys_role 表:
对应的mysql语句:
CREATE TABLE `sys_role` (
`roleId` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT ' 角色id 作为表主键 用于关联',
`roleName` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色名',
`roleRemarks` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注,预留字段',
PRIMARY KEY (`roleId`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
核心表3 权限表,sys_permissions 表:
对应的mysql语句:
CREATE TABLE `sys_permissions` (
`perId` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '权限表id 作为表主键 用于关联',
`permissionsName` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限名称',
`perRemarks` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注,预留字段',
PRIMARY KEY (`perId`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
中间表 1 帐号与角色的中间表,user_role 表:
对应的mysql语句:
CREATE TABLE `user_role` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '表主键id',
`userId` int(11) NULL DEFAULT NULL COMMENT '帐号表的主键id',
`roleId` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色表的主键id',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
中间表 2 角色与权限的中间表,role_per 表:
对应的mysql语句:
CREATE TABLE `role_per` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '表主键id',
`roleId` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色表的主键id',
`perId` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限表的主键id',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
对这些表做个简单描述:
首先是 帐号表 sys_user ,里面存放的就是 登录系统的帐号和密码;
角色表 sys_role ,里面存放的就是 角色信息;
权限表 sys_permissions ,里面存放的就是 权限信息;
中间表 user_role 和 role_per,分别是存放 哪个用户分配的哪个角色,多对多; 哪个角色对应哪个权限,多对多;
数据库方面的设计就到此,那么在开始代码实现前,我们还继续一下数据库方面的东西,就是数据模拟;
也就是说,这个项目实现后,我们的数据情况是这样的:
帐号表,注册了两个帐号进去(remark字段是个预留字段,可以无视):
sys_user
相关sql:
INSERT INTO `my_system`.`sys_user`(`userId`, `userName`, `password`, `userRemarks`) VALUES (10001, 'adminHong', '202cb962ac59075b964b07152d234b70', '小红');
INSERT INTO `my_system`.`sys_user`(`userId`, `userName`, `password`, `userRemarks`) VALUES (20001, 'jc', 'e165421110ba03099a1c0393373c5b43', 'JC');
然后这个系统暂时已经创建了2个角色,分别是管理员和普通用户:
sys_role
相关sql:
INSERT INTO `my_system`.`sys_role`(`roleId`, `roleName`, `roleRemarks`) VALUES ('100', 'admin', '系统管理员');
INSERT INTO `my_system`.`sys_role`(`roleId`, `roleName`, `roleRemarks`) VALUES ('200', 'common', '普通用户');
然后是这个系统具体的权限菜单或者说是权限按钮:
sys_permissions
相关sql:
INSERT INTO `my_system`.`sys_permissions`(`perId`, `permissionsName`, `perRemarks`) VALUES ('M01', 'resetPassword', '重置密码');
INSERT INTO `my_system`.`sys_permissions`(`perId`, `permissionsName`, `perRemarks`) VALUES ('M02', 'querySystemLog', '查看系统日志');
INSERT INTO `my_system`.`sys_permissions`(`perId`, `permissionsName`, `perRemarks`) VALUES ('M03', 'exportUserInfo', '导出用户信息');
INSERT INTO `my_system`.`sys_permissions`(`perId`, `permissionsName`, `perRemarks`) VALUES ('M204', 'queryMyUserInfo', '查看个人信息');
然后是我们给小红的帐号 adminHong分配角色 admin,然后给admin这个角色分配权限菜单M01,M02,M03,M204;
然后给JC的帐号jc分批角色 common,然后给common这个角色分批权限菜单M204;
所以两张关联表的数据就是以下情况:
role_per
相关sql:
INSERT INTO `my_system`.`role_per`(`id`, `roleId`, `perId`) VALUES (1, '100', 'M01');
INSERT INTO `my_system`.`role_per`(`id`, `roleId`, `perId`) VALUES (2, '100', 'M02');
INSERT INTO `my_system`.`role_per`(`id`, `roleId`, `perId`) VALUES (3, '100', 'M03');
INSERT INTO `my_system`.`role_per`(`id`, `roleId`, `perId`) VALUES (4, '200', 'M204');
INSERT INTO `my_system`.`role_per`(`id`, `roleId`, `perId`) VALUES (5, '100', 'M204');
user_role
相关sql:
INSERT INTO `my_system`.`user_role`(`id`, `userId`, `roleId`) VALUES (1, 10001, '100');
INSERT INTO `my_system`.`user_role`(`id`, `userId`, `roleId`) VALUES (2, 20001, '200');
其实看到这些表的模拟数据,看到这些关联关系,实现个权限系统,非常简单明了。
只需要根据帐号查询出来相关的数据,接下来怎么进行权限校验不是非常轻松的事情么。
例如我们执行一个这样的sql语句:
SELECT user.userId ,user.userName,role.roleName,role.roleId,per.permissionsName ,per.perId,per.perRemarks
FROM sys_user AS user,
sys_role AS role,
sys_permissions AS per,
role_per,
user_role
WHERE user.userName='adminHong'
AND user.userId=user_role.userId
AND user_role.roleId=role.roleId
AND role_per.roleId=role.roleId
AND role_per.perId=per.perId
查询出来的结果是:
根据用户登录帐号查询出这个帐号的角色身份,权限,结果非常清晰。
再查下另一个帐号,也是非常清晰:
ok,这么啰嗦地终于从数据库的设计 以及 模拟帐号注册,创建角色,创建权限,给帐号分批角色,给角色分批权限生成的数据
的层面场景,大致给这套系统呈现了个漂浮的使用场景。
代码实现
接下来就是看咱们怎么结合这个数据库去实现了。
先来看看我们的最后项目结构(里面的controller就是模拟的各个功能模块,日志功能,登录功能,导出功能):
创建一个springboot项目,
然后在pom.xml里,加入我们需要使用到的jar包:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.jc</groupId>
<artifactId>shiro</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>shiro</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--web项目-->
<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>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- druid数据源驱动 1.1.10解决springboot从1.0——2.0版本问题-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<!--mysql连接驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--lombok简化pojo代码-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.1</version>
</dependency>
<!--shiro权限框架-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
然后是application.yml文件:
server:
port: 8077
spring:
datasource:
druid:
username: root
password: root
url: jdbc:mysql://localhost:3306/my_system?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&zeroDateTimeBehavior=convertToNull
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
mybatis:
config-location: classpath:mybatis/mybatis-config.xml
mapper-locations: classpath:mybatis/mapper/*.xml
接下来是对照数据库表结构,创建三个pojo:
SysUser.java
import lombok.Data;
/**
* @Author : JCccc
* @CreateTime : 2020/4/24
* @Description :
**/
@Data
public class SysUser {
private Integer userId;
private String userName;
private String password;
private String userRemarks;
}
SysRole.class
import lombok.Data;
/**
* @Author : JCccc
* @CreateTime : 2020/4/24
* @Description :
**/
@Data
public class SysRole {
private String roleId;
private String roleName;
private String roleRemarks;
}
SysPermissions.java
import lombok.Data;
/**
* @Author : JCccc
* @CreateTime : 2020/4/24
* @Description :
**/
@Data
public class SysPermissions {
private Integer perId;
private String permissionsName;
private String perRemarks;
}
实体类创建完毕,先不用管那些操作表的增删改查。
核心使用环节, 创建ShiroConfig.java :
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
/**
* @Author : JCccc
* @CreateTime : 2020/4/24
* @Description :
**/
@Configuration
public class ShiroConfig {
@Bean
@ConditionalOnMissingBean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
defaultAAP.setProxyTargetClass(true);
return defaultAAP;
}
//将自己的验证方式加入容器
@Bean
public UserRealm myShiroRealm() {
UserRealm userRealm = new UserRealm();
return userRealm;
}
//权限管理,配置主要是Realm的管理认证
@Bean
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
return securityManager;
}
//对url的过滤筛选
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, String> map = new HashMap<>();
//登出
map.put("/logout", "logout");
//对所有用户认证
map.put("/**", "authc");
//登录
shiroFilterFactoryBean.setLoginUrl("/login");
//成功登录后跳转的url
//shiroFilterFactoryBean.setSuccessUrl("/xxxx");
//错误页面,认证不通过跳转
// shiroFilterFactoryBean.setUnauthorizedUrl("/error");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
return shiroFilterFactoryBean;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
创建自定义的权限审核处理类,UserRealm.java(涉及到数据库的查询文章后面有写):
import com.jc.shiro.pojo.SysUser;
import com.jc.shiro.service.LoginService;
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.factory.annotation.Autowired;
import java.util.List;
import java.util.Map;
/**
* @Author : JCccc
* @CreateTime : 2020/4/24
* @Description :
**/
public class UserRealm extends AuthorizingRealm {
@Autowired
private LoginService loginService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取登录用户名
String userName = (String) principalCollection.getPrimaryPrincipal();
//添加角色和权限
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
List<Map<String, Object>> powerList = loginService.getUserPower(userName);
System.out.println(powerList.toString());
for (Map<String, Object> powerMap : powerList) {
//添加角色
simpleAuthorizationInfo.addRole(String.valueOf(powerMap.get("roleName")));
//添加权限
simpleAuthorizationInfo.addStringPermission(String.valueOf(powerMap.get("permissionsName")));
}
return simpleAuthorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//加这一步的目的是在Post请求的时候会先进认证,然后在到请求
if (authenticationToken.getPrincipal() == null) {
return null;
}
//获取用户信息
String userName = authenticationToken.getPrincipal().toString();
//根据用户名去数据库查询用户信息
SysUser sysUser = loginService.queryUser(userName);
if (sysUser == null) {
//这里返回后会报出对应异常
return null;
} else {
//这里验证authenticationToken和simpleAuthenticationInfo的信息
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(userName, sysUser.getPassword().toString(), getName());
return simpleAuthenticationInfo;
}
}
}
然后是我们的登录接口,这里融合自己项目的帐号加密方法,LoginController.java:
import com.jc.shiro.service.LoginService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.util.DigestUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* @Author : JCccc
* @CreateTime : 2020/4/24
* @Description :
**/
@Controller
public class LoginController {
@Autowired
LoginService loginService;
@ResponseBody
@GetMapping("/login")
public String login(@RequestParam("userName") String userName, @RequestParam("password") String password) {
//添加用户认证信息
Subject subject = SecurityUtils.getSubject();
//自己系统的密码加密方式 ,这里简单示例一下MD5
String md5Password = DigestUtils.md5DigestAsHex(password.getBytes());
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(userName, md5Password);
try {
//进行验证,AuthenticationException可以catch到,但是AuthorizationException因为我们使用注解方式,是catch不到的,所以后面使用全局异常捕抓去获取
subject.login(usernamePasswordToken);
} catch (AuthenticationException e) {
e.printStackTrace();
return "账号或密码错误!";
} catch (AuthorizationException e) {
e.printStackTrace();
return "没有权限";
}
return "login success";
}
}
其中用到了一个根据用户登录帐号去查询对应的角色权限信息,也就是文章开头我们设计的查询。
mapper层:
LoginMapper.java
import com.jc.shiro.pojo.SysUser;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
import java.util.Map;
/**
* @Author : JCccc
* @CreateTime : 2020/4/24
* @Description :
**/
@Mapper
public interface LoginMapper {
SysUser queryUser(String userName );
List<Map<String,Object>> getUserPower(String userName);
}
loginMapper.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jc.shiro.mapper.LoginMapper">
<!--通过登录帐号查找用户信息-->
<select id="queryUser" resultType="com.jc.shiro.pojo.SysUser" parameterType="String">
SELECT *
FROM sys_user
WHERE userName=#{userName}
</select>
<!--通过登录帐号查找用户权限信息-->
<select id="getUserPower" resultType="java.util.HashMap" parameterType="String">
SELECT user.userId ,user.userName,role.roleName,role.roleId,per.permissionsName ,per.perId,per.perRemarks
FROM sys_user AS user,
sys_role AS role,
sys_permissions AS per,
role_per,
user_role
WHERE user.userName=#{userName}
AND user.userId=user_role.userId
AND user_role.roleId=role.roleId
AND role_per.roleId=role.roleId
AND role_per.perId=per.perId
</select>
</mapper>
然后是service层:
LoginService.java
import com.jc.shiro.pojo.SysUser;
import java.util.List;
import java.util.Map;
/**
* @Author : JCccc
* @CreateTime : 2020/4/24
* @Description :
**/
public interface LoginService {
SysUser queryUser(String userName );
List<Map<String,Object>> getUserPower(String userName );
}
LoginServiceImpl.java
import com.jc.shiro.mapper.LoginMapper;
import com.jc.shiro.pojo.SysUser;
import com.jc.shiro.service.LoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
/**
* @Author : JCccc
* @CreateTime : 2020/4/24
* @Description :
**/
@Service
public class LoginServiceImpl implements LoginService {
@Autowired
LoginMapper loginMapper;
@Override
public SysUser queryUser(String userName) {
return loginMapper.queryUser(userName);
}
@Override
public List<Map<String, Object>> getUserPower(String userName) {
return loginMapper.getUserPower(userName);
}
}
最后再补上一个异常全局控制器,MyExceptionHandler.java:
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.AuthorizationException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* @Author : JCccc
* @CreateTime : 2020/4/24
* @Description :
**/
@ControllerAdvice
@Slf4j
public class MyExceptionHandler {
@ExceptionHandler
@ResponseBody
public String ErrorHandler(AuthorizationException e) {
log.error("权限校验失败!", e);
return "您暂时没有权限,请联系管理员!";
}
}
到这里,已经整合完毕,接下来是我们的使用,后端的校验我们采取shiro提供的注解的方式去使用:
@RequiresRoles("xxx")
@RequiresPermissions("xxx")
我们简单模拟导出数据功能模块,ExportController.java:
暂时只提供了一个导出接口,而这个导出接口需要用户拥有 admin 角色 以及 exportUserInfo 权限,也就是代码里注解的参数
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author : JCccc
* @CreateTime : 2020/4/24
* @Description :
**/
@RestController
public class ExportController {
@RequiresRoles("admin")
@RequiresPermissions("exportUserInfo")
@ResponseBody
@RequestMapping("/export")
public String export() {
return "u can export !";
}
}
再来模拟日志功能模块,LogController.java:
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
/**
* @Author : JCccc
* @CreateTime : 2020/4/24
* @Description :
**/
@RestController
public class LogController {
//注解验角色和权限
@RequiresRoles("common")
@ResponseBody
@RequestMapping("/querySystemLog")
public String queryLog() {
return "u can queryLog !";
}
}
测试效果
好,接下来我们从登录开始,测试下整体的效果:
我们先用普通用户帐号 jc 登录看看,
登录成功后,我们去访问一下导出接口,因为导出接口, 可以看到因为jc是个普通用户,没有对应上角色和权限,所以无法正常访问:
那么访问下日志的查询接口,这个帐号是有权限的:
管理员帐号的登录测试也是跟上面一样,只要后端对接口添加的注解角色和权限对应上就可以访问,对应不上就会拦截。
快速模拟展示下,
登录:
访问导出数据接口,成功访问:
访问查询日志接口,没有权限,访问失败:
最后,其实管理员怎么能没有查询日志的权限呢? 数据库里,admin角色和普通用户角色都是拥有这个权限的:
原因出在我们在查询日志的接口上,配置的角色身份是:
那么我们想让管理员也能访问,我们不需要对管理员小红帐号再分配个普通用户的角色,
而是改动下接口的注解的使用,这两个shiro提供的注解传入的并不是单一参,是可以接收多个的:
下面的Logical 的 默认值是Logical.AND,是指这个接口的访问用户必须同时具备所有角色才可以访问,我们这边是想实现管理员或者普通用户都能访问。
所以对于这些多对个角色都能拥有的功能,只要数据库里的分配了对应的权限,我们在接口上使用注解传入对应的参数即可:
@RequiresRoles(value={"admin","common"},logical= Logical.OR)
ps:这样这个接口就是这个用户在数据库里面,只要角色是admin或者common都能访问。
重新启动项目,登录管理员小红的帐号adminHong,访问接口查询日志,可以看到能成功访问:
最后再对这两个注解说明一下,这两个角色校验注解和权限码校验注解,
可以单一的使用,也可以同时的使用,也可以不适用。根据业务场景,哪些接口只需要用户拥有角色即可,哪些接口需要具体到权限码,这些都是自己去做调整。
我个人建议的是,每个接口都具体到角色+权限码,这样比较严谨统一点,也不会乱套。
整合shiro框架的使用,除了它自带的那套密码验证流程外,对与权限,角色的分配和校验,核心就是 我们根据业务自定义的
UserRealm.java ,想更熟悉的伙伴,只需要在登录接口打断点,在UserRealm打断点,然后仿照我的测试流程,从登录到接口访问地去调接口,就可以更清晰地了解了。