前言:
簡述:權限管理分爲認證和授權兩大部分
認證即爲登錄認證,授權即爲訪問某API時是否有權限訪問
原理圖解
- Suject:前臺傳值過來的對象
- SecurityManager:權限管理核心對象
- Authenticator:權限認證對象
- Authorizer:權限授權對象
- SessionManager:session管理對象
- Realm:與數據庫連接的橋樑對象
- ShiroUtils:此工具類可以連接以上的對象
一、數據表建立
數據表:用戶表、用戶角色中間表、角色表、角色權限中間表、權限表
/*
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');
二 、項目搭建
1、項目目錄結構:
2、添加依賴,主要是shiro依賴,而swagger-ui用於測試接口
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<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>
<!-- mybatis-plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>2.3.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--shiro-->
<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>
<!--swagger2-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.7.0</version>
</dependency>
</dependencies>
3、配置yml文件
#配置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
4、首先定義一個統一返回json的包裝類
Result
package cn.zdxh.shirodemo.utils;
import lombok.Data;
@Data
public class Result<T> {
private Integer code;
private String msg;
private T data;
public static <T> Result success(T t){
Result<T> result = new Result();
result.setCode(1);
result.setMsg("成功");
result.setData(t);
return result;
}
public static <T> Result error(T t){
Result result = new Result();
result.setCode(0);
result.setMsg("失敗");
result.setData(t);
return result;
}
}
5、Shiro加密工具類
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();
}
}
6、定義實體類
- 這裏只給出一個SysUser實體類,其他實體類的同理。
- 需要注意的是本項目用的是mybatis-plus做持久層,要給實體類指定自增ID策略
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;
}
7、持久層Mapper
- 只給出SysUserMapper持久層相關,其他表的持久層同理
- 繼承BaseMapper即可完成簡單的crud
- 認證:需要查詢出用戶的信息
- 授權:需要查詢角色和權限的信息
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<SysUser> {
/**授權相關*/
//通過用戶ID查詢該用戶權限
List<SysPerms> findAllPermsByUserId(Integer id);
//通過用戶ID查詢該用戶角色
List<SysRole> findAllRolesByUserId(Integer id);
/**認證相關*/
//通過用戶名查詢該用戶信息
List<SysUser> findUserByUsername(String username);
}
在resources/mapper目錄下
SysUserMapper.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="cn.zdxh.shirodemo.mapper.SysUserMapper">
<!--授權相關-->
<!--通過用戶ID查詢該用戶權限-->
<select id="findAllPermsByUserId" parameterType="int" resultType="cn.zdxh.shirodemo.entity.SysPerms">
SELECT p.id,p.perms FROM sys_user_role ur
LEFT JOIN sys_role r
ON ur.role_id = r.id
LEFT JOIN sys_role_perms rp
ON r.id = rp.role_id
LEFT JOIN sys_perms p
ON rp.perms_id = p.id
WHERE ur.user_id= #{id}
</select>
<!--通過用戶ID查詢該用戶角色-->
<select id="findAllRolesByUserId" parameterType="int" resultType="cn.zdxh.shirodemo.entity.SysRole">
SELECT r.id,r.create_user_id,r.role_name,r.role_remark,r.create_time
FROM sys_user_role ur
LEFT JOIN sys_role r
ON ur.role_id = r.id
WHERE ur.user_id = #{id}
</select>
<!--通過用戶名查詢該用戶信息-->
<select id="findUserByUsername" parameterType="string" resultType="cn.zdxh.shirodemo.entity.SysUser">
SELECT * FROM sys_user WHERE username = #{username}
</select>
</mapper>
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<SysPerms> findAllPermsByUserId(Integer id);
//通過用戶ID查詢該用戶角色
List<SysRole> findAllRolesByUserId(Integer id);
/**認證相關*/
//通過用戶ID查詢用戶
List<SysUser> findUserByUsername(String username);
/**操作相關*/
//查詢所有用戶
List<SysUser> 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<SysPerms> findAllPermsByUserId(Integer id) {
return sysUserMapper.findAllPermsByUserId(id);
}
@Override
public List<SysRole> findAllRolesByUserId(Integer id) {
return sysUserMapper.findAllRolesByUserId(id);
}
@Override
public List<SysUser> findUserByUsername(String username) {
return sysUserMapper.findUserByUsername(username);
}
@Override
public List<SysUser> findAllUsers() {
return sysUserMapper.selectList(null);
}
@Override
public void addUser(SysUser sysUser) {
sysUserMapper.insert(sysUser);
}
}
9、自定義UserRealm,對接數據持久層(最重要的一步)
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> sysPerms = sysUserService.findAllPermsByUserId(sysUser.getId());
List<SysRole> sysRoles = sysUserService.findAllRolesByUserId(sysUser.getId());
Set<String> roles = new HashSet<>();
Set<String> 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<SysUser> 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:需要加鹽加密才需要重寫該方法,否則不用
加密策略:
- 密碼首先MD5加密一次
- 隨機生成一個salt值,與加密密碼組合再MD5加密一次
- 最後組成的加密密碼和鹽值存進數據庫
10、Controller層
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<String> 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<String> logout(){
//退出登錄操作
SecurityUtils.getSubject().logout();
return Result.error("退出登錄成功");
}
/**
* 沒有登錄的回調接口
* @return
*/
@GetMapping("/loginfail")
@ApiOperation("沒有登錄的回調接口")
public Result<String> loginfail(){
return Result.error("請登錄");
}
/**
* 沒有權限的回調接口
* @return
*/
@GetMapping("/authfail")
@ApiOperation("沒有權限的回調接口")
public Result<String> authfail(){
return Result.error("沒有操作權限");
}
}
SysUserController:用於測試用戶權限
- @RequiresPermissions() :說明該api需要相應權限才能訪問
- @RequiresRoles() : 說明該api需要相應的角色才能訪問
- 兩者存在一種即可,方法一的粒度更小,應用更加廣
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<String> list(){
//此方法和註解的方式一樣
//SecurityUtils.getSubject().isPermitted("sys:user:list");
List<SysUser> allUsers = sysUserService.findAllUsers();
return Result.success(allUsers);
}
/**
* 新增用戶
* @return
*/
@GetMapping("/add")
@ApiOperation("新增用戶")
@RequiresPermissions("sys:user:add")
//@RequiresRoles("admin")
public Result<String> 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("新增用戶成功");
}
}
11、配置類
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<String, String> 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;
}
}
12、統一異常管理
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<String> authorizationException(AuthorizationException e){
return Result.error("沒有操作權限");
}
/**
* 其他的一些未知名異常
* @param e
* @return
*/
@ResponseBody
@ExceptionHandler(Exception.class)
public Result<String> exception(Exception e){
return Result.error(e.getMessage());
}
}
三、測試結果
訪問swagger-ui路徑:http://localhost:8080/swagger-ui.html#/
1、沒有登錄時訪問
2、登錄
3、登錄成功,並有相應權限
4、登錄成功,但是沒有相應權限
5、退出登錄
四、總結
- Shiro權限管理系統是基於session實現的,這裏暫未滿足移動端的token認證方式,有興趣的可以在這方面探究一下。
- 項目github地址:https://github.com/ningxiaojian31/shirodemo
- 想要了解shiro的執行原理的可以翻看博主的另外一篇文章