简述:权限管理分为认证和授权两大部分
认证即为登录认证,授权即为访问某API时是否有权限访问
原理图解
数据表:用户表、用户角色中间表、角色表、角色权限中间表、权限表
/*
Navicat MySQL Data Transfer
Source Server : localhost
Source Server Version : 50721
Source Host : localhost:3306
Source Database : shiro
Target Server Type : MYSQL
Target Server Version : 50721
File Encoding : 65001
Date: 2019-07-15 20:26:21
*/
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for `sys_perms`
-- ----------------------------
DROP TABLE IF EXISTS `sys_perms`;
CREATE TABLE `sys_perms` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`perms` varchar(255) DEFAULT NULL COMMENT '权限',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='权限表';
-- ----------------------------
-- Records of sys_perms
-- ----------------------------
INSERT INTO `sys_perms` VALUES ('1', 'sys:user:list,sys:user:add');
-- ----------------------------
-- Table structure for `sys_role`
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`role_name` varchar(64) DEFAULT NULL COMMENT '角色',
`role_remark` varchar(64) DEFAULT NULL COMMENT '角色备注',
`create_user_id` int(11) DEFAULT NULL COMMENT '创建人ID',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='角色表';
-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES ('1', 'admin', '系统管理员', '1', '2019-07-14 23:37:51');
-- ----------------------------
-- Table structure for `sys_role_perms`
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_perms`;
CREATE TABLE `sys_role_perms` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`role_id` int(11) NOT NULL COMMENT '角色表外键',
`perms_id` int(11) NOT NULL COMMENT '权限表外键',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='角色和权限中间表';
-- ----------------------------
-- Records of sys_role_perms
-- ----------------------------
INSERT INTO `sys_role_perms` VALUES ('1', '1', '1');
-- ----------------------------
-- Table structure for `sys_user`
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`username` varchar(64) DEFAULT NULL COMMENT '用户名',
`password` varchar(255) DEFAULT NULL COMMENT '密码(密文)',
`salt` varchar(255) DEFAULT NULL COMMENT '盐',
`create_user_id` int(11) DEFAULT NULL COMMENT '创建人ID',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES ('1', 'admin', 'a7c525e97cb55128257469230e990f23', 'f6281f61-ae3d-4b4c-8650-d73f4bf01208', '1', '2019-07-10 23:41:27');
INSERT INTO `sys_user` VALUES ('2', 'justin', '2e7446f43341d2f077beafdd02641771', '47b3aec6-14af-4bd4-ae7a-45168c1a40ff', null, '2019-07-15 09:36:46');
-- ----------------------------
-- Table structure for `sys_user_role`
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` int(11) NOT NULL COMMENT '用户表外键',
`role_id` int(11) NOT NULL COMMENT '角色表外键',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT='用户和角色中间表';
-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES ('1', '1', '1');
org.springframework.boot
spring-boot-starter-data-redis
org.springframework.boot
spring-boot-starter-web
mysql
mysql-connector-java
runtime
com.baomidou
mybatis-plus-boot-starter
2.3.3
org.projectlombok
lombok
true
org.springframework.boot
spring-boot-starter-test
test
org.apache.shiro
shiro-core
1.4.0
org.apache.shiro
shiro-spring
1.4.0
io.springfox
springfox-swagger2
2.7.0
io.springfox
springfox-swagger-ui
2.7.0
#配置mysql
spring:
datasource:
url: jdbc:mysql://localhost:3306/shiro?serverTimezone=GMT
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
#配置mybatis-plus
mybatis-plus:
mapper-locations: classpath:/mybatis/mapper/*.xml
#显示sql语句
logging:
level:
cn.zdxh.shirodemo.mapper: DEBUG
Result
package cn.zdxh.shirodemo.utils;
import lombok.Data;
@Data
public class Result {
private Integer code;
private String msg;
private T data;
public static Result success(T t){
Result result = new Result();
result.setCode(1);
result.setMsg("成功");
result.setData(t);
return result;
}
public static Result error(T t){
Result result = new Result();
result.setCode(0);
result.setMsg("失败");
result.setData(t);
return result;
}
}
ShiroUtils
package cn.zdxh.shirodemo.utils;
import org.apache.shiro.crypto.hash.Md5Hash;
public class ShiroUtils {
//密码加密
public static String encryptMD5(String password){
return new Md5Hash(password).toString();
}
//通过MD5加盐加密
public static String encryptMD5(String password,String salt){
return new Md5Hash(password,salt).toString();
}
}
SysUser
package cn.zdxh.shirodemo.entity;
import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.enums.IdType;
import lombok.Data;
import java.util.Date;
/**
* 用户实体
*/
@Data
public class SysUser {
//ID自动增长策略
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
private String username;
private String password;
private String salt;
private Integer createUserId;
private Date createTime;
}
SysUserMapper
package cn.zdxh.shirodemo.mapper;
import cn.zdxh.shirodemo.entity.SysPerms;
import cn.zdxh.shirodemo.entity.SysRole;
import cn.zdxh.shirodemo.entity.SysUser;
import com.baomidou.mybatisplus.mapper.BaseMapper;
import java.util.List;
public interface SysUserMapper extends BaseMapper {
/**授权相关*/
//通过用户ID查询该用户权限
List findAllPermsByUserId(Integer id);
//通过用户ID查询该用户角色
List findAllRolesByUserId(Integer id);
/**认证相关*/
//通过用户名查询该用户信息
List findUserByUsername(String username);
}
在resources/mapper目录下
SysUserMapper.xml
8、服务层
SysUserService
package cn.zdxh.shirodemo.service;
import cn.zdxh.shirodemo.entity.SysPerms;
import cn.zdxh.shirodemo.entity.SysRole;
import cn.zdxh.shirodemo.entity.SysUser;
import java.util.List;
public interface SysUserService {
/**授权相关*/
//通过用户ID查询该用户权限
List findAllPermsByUserId(Integer id);
//通过用户ID查询该用户角色
List findAllRolesByUserId(Integer id);
/**认证相关*/
//通过用户ID查询用户
List findUserByUsername(String username);
/**操作相关*/
//查询所有用户
List findAllUsers();
//新增用户
void addUser(SysUser sysUser);
}
SysUserServiceImpl
package cn.zdxh.shirodemo.service.impl;
import cn.zdxh.shirodemo.entity.SysPerms;
import cn.zdxh.shirodemo.entity.SysRole;
import cn.zdxh.shirodemo.entity.SysUser;
import cn.zdxh.shirodemo.mapper.SysUserMapper;
import cn.zdxh.shirodemo.service.SysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class SysUserServiceImpl implements SysUserService {
@Autowired
private SysUserMapper sysUserMapper;
@Override
public List findAllPermsByUserId(Integer id) {
return sysUserMapper.findAllPermsByUserId(id);
}
@Override
public List findAllRolesByUserId(Integer id) {
return sysUserMapper.findAllRolesByUserId(id);
}
@Override
public List findUserByUsername(String username) {
return sysUserMapper.findUserByUsername(username);
}
@Override
public List findAllUsers() {
return sysUserMapper.selectList(null);
}
@Override
public void addUser(SysUser sysUser) {
sysUserMapper.insert(sysUser);
}
}
UserRealm
package cn.zdxh.shirodemo.shiro;
import cn.zdxh.shirodemo.entity.SysPerms;
import cn.zdxh.shirodemo.entity.SysRole;
import cn.zdxh.shirodemo.entity.SysUser;
import cn.zdxh.shirodemo.service.SysUserService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.*;
/**
* 自定义Realm
*/
@Component
public class UserRealm extends AuthorizingRealm {
@Autowired
private SysUserService sysUserService;
/**
* 授权,需要授权才会调用该方法
* @param principalCollection
* @return
* @throws AuthenticationException
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//参数是从认证方法参数一传过来的
SysUser sysUser = (SysUser) principalCollection.getPrimaryPrincipal();
//根据用户ID去数据库查询角色和权限,并封装角色名和权限名
List sysPerms = sysUserService.findAllPermsByUserId(sysUser.getId());
List sysRoles = sysUserService.findAllRolesByUserId(sysUser.getId());
Set roles = new HashSet<>();
Set perms = new HashSet<>();
//权限
if (sysPerms != null && sysPerms.size() > 0){
for (SysPerms sysPerm : sysPerms){
//分割出每个权限
String[] split = sysPerm.getPerms().split(",");
perms.addAll(Arrays.asList(split));
}
}
//角色
if (sysRoles != null && sysRoles.size() > 0){
for (SysRole sysRole : sysRoles){
roles.add(sysRole.getRoleName());
}
}
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
//设置拥有的角色
authorizationInfo.setRoles(roles);
//设置拥有的权限
authorizationInfo.setStringPermissions(perms);
return authorizationInfo;
}
/**
* 登录认证,即在subject.login(token)后调用
* @param authenticationToken
* @return
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//login方法调用之后传过来的token
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken)authenticationToken;
//根据用户名去数据库查询该用户
List sysUsers = sysUserService.findUserByUsername(usernamePasswordToken.getUsername());
SysUser sysUser = new SysUser();
sysUser = sysUsers.get(0);//只能一个用户,其实这里用户名是唯一的
/**
* 参数一:传给doGetAuthorizationInfo授权作为参数的
* 参数二:密码,交给shiro自动判断密码是否正确
* 参数三:盐,给密码加盐后再判断
* 参数四:自定义Realm名称
*/
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(sysUser, sysUser.getPassword(),
ByteSource.Util.bytes(sysUser.getSalt()),getName());
return authenticationInfo;
}
/**
* 因为了我们使用到了MD5算法,所以得告知凭证匹配器利用该算法去匹配。
* 真正匹配的方法:assertCredentialsMatcher
*/
@Override
public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("MD5");//指定加密算法,默认只加密一次,可以选择多次加密。这里的加密要对应新建用户时加密存储的密码
super.setCredentialsMatcher(hashedCredentialsMatcher);
}
// public static void main(String[] args) {
// try {
// String password = new Md5Hash("123456").toString();
// Md5Hash md5Hash = new Md5Hash("e10adc3949ba59abbe56e057f20f883e","f6281f61-ae3d-4b4c-8650-d73f4bf01208");
// System.out.println(password);//e780cbb524f6f54e2734b3114978820f
// System.out.println(md5Hash.toString());
// } catch (Exception e) {
// e.printStackTrace();
// }
// }
}
doGetAuthorizationInfo:授权回调方法。去数据库查询该用户的角色和权限
doGetAuthenticationInfo:认证回调方法。去数据库查询该用户的相关信息
setCredentialsMatcher:需要加盐加密才需要重写该方法,否则不用
加密策略:
LoginController:用于登录和登出
package cn.zdxh.shirodemo.controller;
import cn.zdxh.shirodemo.entity.SysUser;
import cn.zdxh.shirodemo.utils.Result;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/login")
@Api(tags = "登录相关接口")
public class LoginController {
/**
* 登录接口
* @param sysUser
* @return
*/
@PostMapping("/login")
@ApiOperation("登录接口")
public Result login(SysUser sysUser){
//这里先将密码进行MD5,实际上这个步骤是前端做的
sysUser.setPassword(new Md5Hash(sysUser.getPassword()).toString());
//获取到主体
Subject subject = SecurityUtils.getSubject();
//此对象目的是包装用户名和密码
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(sysUser.getUsername(), sysUser.getPassword());
//登录
subject.login(usernamePasswordToken);
return Result.success("登录成功");
}
/**
* 退出登录接口
* @return
*/
@GetMapping("/logout")
@ApiOperation("退出登录接口")
public Result logout(){
//退出登录操作
SecurityUtils.getSubject().logout();
return Result.error("退出登录成功");
}
/**
* 没有登录的回调接口
* @return
*/
@GetMapping("/loginfail")
@ApiOperation("没有登录的回调接口")
public Result loginfail(){
return Result.error("请登录");
}
/**
* 没有权限的回调接口
* @return
*/
@GetMapping("/authfail")
@ApiOperation("没有权限的回调接口")
public Result authfail(){
return Result.error("没有操作权限");
}
}
SysUserController:用于测试用户权限
package cn.zdxh.shirodemo.controller;
import cn.zdxh.shirodemo.entity.SysUser;
import cn.zdxh.shirodemo.service.SysUserService;
import cn.zdxh.shirodemo.utils.Result;
import cn.zdxh.shirodemo.utils.ShiroUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
import java.util.List;
import java.util.UUID;
/**
* 用户接口
*/
@Api(tags = "用户接口相关")
@RestController
@RequestMapping("/user")
public class SysUserController {
@Autowired
private SysUserService sysUserService;
/**
* 查询所有的用户
* @return
*/
@GetMapping("/list")
@ApiOperation("查询所有的用户")
@RequiresPermissions("sys:user:list")
public Result list(){
//此方法和注解的方式一样
//SecurityUtils.getSubject().isPermitted("sys:user:list");
List allUsers = sysUserService.findAllUsers();
return Result.success(allUsers);
}
/**
* 新增用户
* @return
*/
@GetMapping("/add")
@ApiOperation("新增用户")
@RequiresPermissions("sys:user:add")
//@RequiresRoles("admin")
public Result add(SysUser sysUser){
//此方法和注解的方式一样
//SecurityUtils.getSubject().isPermitted("sys:user:list");
try{
//密码首先加一次密
String password = ShiroUtils.encryptMD5(sysUser.getPassword());
//加盐之后再进行加密
String salt = UUID.randomUUID().toString();
password = ShiroUtils.encryptMD5(password,salt);
//设置并初始化值
sysUser.setPassword(password);
sysUser.setSalt(salt);
sysUser.setCreateTime(new Date());
sysUserService.addUser(sysUser);
}catch (Exception e){
e.printStackTrace();
return Result.error("新增用户出现异常啦");
}
return Result.success("新增用户成功");
}
}
MyWebMvcConfigure : 配置swagger-ui
package cn.zdxh.shirodemo.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
@Configuration
public class MyWebMvcConfigure extends WebMvcConfigurationSupport {
/**
* 映射器,解决swagger-ui 404问题
* @param registry
*/
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/statics/**").addResourceLocations("classpath:/statics/");
// 解决 SWAGGER 404报错
registry.addResourceHandler("/swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
ShiroConfig:Shiro的相关配置
package cn.zdxh.shirodemo.config;
import cn.zdxh.shirodemo.shiro.UserRealm;
import org.apache.shiro.mgt.SecurityManager;
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.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
/**
* 配置SecurityManager对象
* @param userRealm 自定义的Realm对象
* @return
*/
@Bean("securityManager")
public SecurityManager securityManager(UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm);
securityManager.setRememberMeManager(null);
return securityManager;
}
/**
* 配置过滤器,指定拦截哪些请求
* @param securityManager
* @return
*/
@Bean("shiroFilter")
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
/**设置securityManager*/
shiroFilter.setSecurityManager(securityManager);
/**设置没有登录的回调方法*/
shiroFilter.setLoginUrl("/login/loginfail");
//设置没有权限的回调方法,这里有可能不会生效,因为权限范围问题,另一种解决方法为统一异常管理器
//shiroFilter.setUnauthorizedUrl("/login/authfail");
/**设置过滤规则,anon:放行,authc:拦截*/
Map filterMap = new LinkedHashMap<>();
filterMap.put("/swagger/**", "anon");
filterMap.put("/v2/api-docs", "anon");
filterMap.put("/swagger-ui.html", "anon");
filterMap.put("/webjars/**", "anon");
filterMap.put("/swagger-resources/**", "anon");
filterMap.put("/login/login","anon");
filterMap.put("/login/logout","anon");
filterMap.put("/login/fail","anon");
filterMap.put("/**", "authc");
shiroFilter.setFilterChainDefinitionMap(filterMap);
return shiroFilter;
}
/**
* 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions)
* 配置以下两个bean(DefaultAdvisorAutoProxyCreator和AuthorizationAttributeSourceAdvisor)即可实现此功能
* @return
*/
@Bean
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
MyExceptionHandler:因为在没权限的时候,在拦截器那里跳转页面没有生效,所以只能在这里拦截没权限异常
package cn.zdxh.shirodemo.exception;
import cn.zdxh.shirodemo.utils.Result;
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;
/**
* 统一异常管理
*/
@ControllerAdvice
public class MyExceptionHandler {
/**
* 没有权限的异常
* @param e
* @return
*/
@ResponseBody
@ExceptionHandler(AuthorizationException.class)
public Result authorizationException(AuthorizationException e){
return Result.error("没有操作权限");
}
/**
* 其他的一些未知名异常
* @param e
* @return
*/
@ResponseBody
@ExceptionHandler(Exception.class)
public Result exception(Exception e){
return Result.error(e.getMessage());
}
}
访问swagger-ui路径:http://localhost:8080/swagger-ui.html#/