前言
以前的项目代码,整理记录一下。
一、什么是shiro
官方:Shiro是一个功能强大且易于使用的Java安全框架,可以运行在JavaSE和JavaEE项目中,可执行身份验证、授权、加密和会话管理。
二、Shiro核心组件
1、UsernamePasswordToken,shiro用来封装用户登录信息,使用用户登录信息来创建令牌Token
2、SecurityManager,Shiro的核心部分,负责安全认证和授权
3、Subject,Shiro的一个抽象概念,代表一个用户实体或单独的个体对象
4、Realm,开发者自定义模块,根据业务逻辑进行认证和授权的逻辑设计
5、AuthenticationInfo,用户的角色信息集合,认证时使用
6、AuthorzationInfo,角色的权限信息集合,授权时使用
7、DefaultWebSecurityManager,安全管理器,开发者自定义的Realm模块需要注入到DefaultWebSecurityManager进行管理才能生效
8、ShiroFilterFactoryBean,过滤器工厂,Shiro的基本运行机制是开发者定制规则,Shiro去执行,具体的执行操作就是ShiroFilterFactoryBean创建的一个个filter对象来完成
三、Springboot集成Shiro
-
引入Shiro依赖
完整pom.xml依赖文件,参考最后的项目地址
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>2.0.0-alpha-3</version>
</dependency>
-
创建一个ShiroConfig配置类
将自定义Realm注入 securityManager安全管理器;
过滤器工厂拦截请求;
开启对shior注解的支持
package com.rocky.springbootshiro.config;
import com.rocky.springbootshiro.shiro.CustomRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;
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.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
//将自己的验证方式加入容器,开发者自定义模块
@Bean
public CustomRealm myShiroRealm() {
CustomRealm customRealm = new CustomRealm();
customRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return customRealm;
}
/**
* 将自定义Realm注入 securityManager安全管理器
*/
@Bean(value = "securityManager")
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置自定义 realm.
securityManager.setRealm(myShiroRealm());
return securityManager;
}
/**
* 过滤器工厂拦截请求
* 使用Qualifier根据方法名获取实例
* @param securityManager
* @return
*/
@Bean
public ShiroFilterFactoryBean factory(@Qualifier("securityManager") SecurityManager securityManager) {
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
factoryBean.setSecurityManager(securityManager);
//必须使用LinkedHashMap有序集合,shiro根据配置的规则进行拦截认证时,是根据容器中的存储顺序决定的
Map<String, String> filterRuleMap = new LinkedHashMap<>();
filterRuleMap.put("/loginValidateCode","anon");
filterRuleMap.put("/loginValidateCode*","anon");
filterRuleMap.put("/listUsers","anon");
filterRuleMap.put("/user/logout","anon");
filterRuleMap.put("/login","anon");// 所有请求通过我们自己的JWT Filter
filterRuleMap.put("/unauthorized/**", "anon");// 访问 /unauthorized/** 不通过JWTFilter
filterRuleMap.put("/**", "authc");//其余接口一律拦截,主要这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截
factoryBean.setFilterChainDefinitionMap(filterRuleMap);
return factoryBean;
}
/**
* 开启对shior注解的支持
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
//设置加密算法
hashedCredentialsMatcher.setHashAlgorithmName("SHA1");
//设置加密的次数
// hashedCredentialsMatcher.setHashIterations(1);
//true加密采用hex编码,false加密采用base64编码
// hashedCredentialsMatcher.setStoredCredentialsHexEncoded(false);
return hashedCredentialsMatcher;
}
}
-
编写自定义的Realm类
编写自己的Realm类继承AuthorizingRealm,实现doGetAuthenticationInfo(认证)和doGetAuthorizationInfo(授权)方法
package com.rocky.springbootshiro.shiro;
import com.rocky.springbootshiro.bean.Permissions;
import com.rocky.springbootshiro.bean.Role;
import com.rocky.springbootshiro.bean.User;
import com.rocky.springbootshiro.common.SignUtil;
import com.rocky.springbootshiro.pojo.Users;
import com.rocky.springbootshiro.service.LoginService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.SecureRandomNumberGenerator;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.lang.util.ByteSource;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class CustomRealm extends AuthorizingRealm {
@Autowired
private LoginService loginService;
/**
* 进行认证
* @param authenticationToken
* @return
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken){
System.out.println("======认证=====");
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
System.out.println(new String(usernamePasswordToken.getPassword()));
Users u = loginService.selectUserByUserName(usernamePasswordToken.getUsername());
if (u!=null){
//加盐值
return new SimpleAuthenticationInfo(u,u.getPassword(),ByteSource.Util.bytes(SignUtil.SALT_STR),getName());
}
return null;
}
/**
* @MethodName doGetAuthorizationInfo
* @Description 授权配置
* 只有当需要检测用户权限的时候才会调用此方法,例如checkRole,checkPermission,@RequiresPermissions,@RequiresRoles之类的,有几个校验方法就调用几次
*/
@Override
public AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("========授权处理=======");
//获取登录用户名
Subject subject = SecurityUtils.getSubject();
Users u = (Users) subject.getPrincipal();
//添加角色和权限
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//超级管理员
if("admin".equals(u.getUsername())) {
simpleAuthorizationInfo.addRole("admin");
simpleAuthorizationInfo.addStringPermission("*:*:*");
return simpleAuthorizationInfo;
}
//授权用户角色和权限
User user = loginService.getUserByName(u.getUsername());
for (Role role : user.getRoles()) {
//添加角色
simpleAuthorizationInfo.addRole(role.getRoleCode());
//添加权限
for (Permissions permissions : role.getPermissions()) {
simpleAuthorizationInfo.addStringPermission(permissions.getPermissinsCode());
}
}
return simpleAuthorizationInfo;
}
}
-
授权逻辑
这里涉及4张表:用户表,角色表,角色权限关联表,权限表。一个用户可能多个角色,一个角色可能绑定多个权限,都是一对多的关系,User类实体包含一个Role实体Set集合,Role类包含Permissions实体Set集合,授权时通过主外键关联查询出对应的角色和权限
==用户表==
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
`lid` int NOT NULL AUTO_INCREMENT,
`username` varchar(255) NOT NULL,
`rolelid` varchar(500) NULL DEFAULT NULL,
`name` varchar(255) NOT NULL,
`password` varchar(64) NOT NULL,
`mobilephone` varchar(15) NULL DEFAULT NULL,
`email` varchar(60) NULL DEFAULT NULL,
`isuse` int NOT NULL DEFAULT 1,
`isdelete` int NOT NULL DEFAULT 0,
`operatorid` int NULL DEFAULT NULL,
`address` varchar(255) NULL DEFAULT NULL,
`operatorname` varchar(20) NULL DEFAULT NULL,
`operatortime` varchar(20) NULL DEFAULT NULL,
`sex` int NULL DEFAULT NULL,
`rolename` varchar(500) NULL DEFAULT NULL,
`telephone` varchar(15) NULL DEFAULT NULL,
`createname` varchar(60) NULL DEFAULT NULL,
`createtime` varchar(20) NULL DEFAULT NULL,
`age` int NULL DEFAULT NULL,
PRIMARY KEY (`lid`) USING BTREE,
UNIQUE INDEX `unique_user_username`(`username` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 30 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = DYNAMIC;
==角色权限关联表==
DROP TABLE IF EXISTS `sys_role_permission`;
CREATE TABLE `sys_role_permission` (
`id` int NOT NULL AUTO_INCREMENT,
`role_id` int NULL DEFAULT NULL COMMENT '角色id',
`permission_id` int NULL DEFAULT NULL COMMENT '权限id',
`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`delete_status` varchar(1) NULL DEFAULT '1' COMMENT '是否有效 1有效 2无效',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 20 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '角色-权限关联表' ROW_FORMAT = DYNAMIC;
==角色表==
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
`id` int NOT NULL AUTO_INCREMENT,
`role_name` varchar(20) NULL DEFAULT NULL COMMENT '角色名',
`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`delete_status` varchar(1) NULL DEFAULT '1' COMMENT '是否有效 1有效 2无效',
`role_code` varchar(255) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '后台角色表' ROW_FORMAT = COMPACT;
==权限表==
DROP TABLE IF EXISTS `sys_permission`;
CREATE TABLE `sys_permission` (
`id` int NOT NULL DEFAULT 0 COMMENT '自定id,主要供前端展示权限列表分类排序使用.',
`menu_code` varchar(255) NULL DEFAULT '' COMMENT '归属菜单,前端判断并展示菜单使用,',
`menu_name` varchar(255) NULL DEFAULT '' COMMENT '菜单的中文释义',
`permission_code` varchar(255) NULL DEFAULT '' COMMENT '权限的代码/通配符,对应代码中@RequiresPermissions 的value',
`permission_name` varchar(255) NULL DEFAULT '' COMMENT '本权限的中文释义',
`required_permission` tinyint(1) NULL DEFAULT 2 COMMENT '是否本菜单必选权限, 1.必选 2非必选 通常是\"列表\"权限是必选',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci COMMENT = '后台权限表' ROW_FORMAT = COMPACT;
-
登录校验
使用Subject对象调用login方法进行登录校验
@PostMapping("/login")
public ResultMap login(HttpServletRequest request, HttpServletResponse resp, User user) {
ResultMap resultMap = new ResultMap();
if (StringUtils.isEmpty(user.getUserName()) || StringUtils.isEmpty(user.getPassWord())) {
return resultMap.fail().code(500).message("请输入用户名和密码!");
}
//用户认证信息
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(
user.getUserName(),
user.getPassWord()
);
//进行登录验证,shiro默认登录过期时间是30分钟
subject.login(usernamePasswordToken);
Users u = loginService.selectUserByUserName(user.getUserName());
return resultMap.success().code(200).ObjData("data", JSONObject.toJSON(u)).message("");
}
-
方法权限绑定
这里使用注解式来控制权限,也可以使用编程式来判断权限,获取Subject subject = SecurityUtils.getSubject(),
里面有hasRole() hasRoles() checkPermission()等方法来检查权限,没有权限会报错,可使用一个全局的异常类来捕获Shiro的报错,然后自定义返回异常信息给到前端
/**
* 拥有 user或admin 角色的用户可以访问下面的页面
*/
@RequiresRoles(logical = Logical.OR, value = {"user", "admin"})
@GetMapping("/getMessage")
public ResultMap getMessage() {
Users u = (Users) SecurityUtils.getSubject().getPrincipal();
System.out.println(u.getUsername());
return resultMap.success().code(200).message("admin角色 和 user:list权限!");
}
/**
* 拥有vip权限且,是user或admin角色
*/
@GetMapping("/getVipMessage")
@RequiresRoles(logical = Logical.OR, value = {"user","admin"})
@RequiresPermissions("vip")
public ResultMap getVipMessage() {
return resultMap.success().code(200).message("user角色或者admin角色,和vip权限!");
}
-
其它权限注解
/**
* @RequiresAuthentication
* 已经通过subject登录认证
*
* @RequiresUser
* subject已经身份验证或者通过记住我登录
*
* @RequiresRoles(logical = Logical.OR, value = {"user","admin"})
* 拥有user角色或者admin角色
*
* @RequiresPermissions("user:list")
* 拥有user:list权限
*
*@GetMapping("/getAuthMessage")
* 已经通过subject登录认证
*/
- 定义全局异常类ExceptionController
@RestControllerAdvice注解将作用在所有注解了@RequestMapping的控制器的方法上
package com.rocky.springbootshiro.controller;
import com.rocky.springbootshiro.bean.ResultMap;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authz.AuthorizationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
/**
* @Author Mr.HPC
* @Description 定义全局的异常处理类
* @Date 23:46 2021/4/7
*/
@RestControllerAdvice
public class ExceptionController {
private final ResultMap resultMap;
@Autowired
public ExceptionController(ResultMap resultMap) {
this.resultMap = resultMap;
}
//没有权限
@ExceptionHandler(AuthorizationException.class)
public ResultMap handle401_1(Throwable ex) {
// ex.printStackTrace();
return resultMap.fail().code(401).message("ExceptionController:您没有权限访问!");
}
//账号密码错误
@ExceptionHandler(AuthenticationException.class)
public ResultMap handle401_2(Throwable ex){
return resultMap.fail().code(401).message("ExceptionController:用户名或密码错误!");
}
//账号不存在
@ExceptionHandler(UnknownAccountException.class)
public ResultMap handle401_3(Throwable ex){
return resultMap.fail().code(401).message("ExceptionController:账号不存在!");
}
// 捕捉其他所有异常
@ExceptionHandler(Exception.class)
public ResultMap globalException(HttpServletRequest request, Throwable ex) {
// ex.printStackTrace();
return resultMap.fail()
.code(getStatus(request).value())
.message("ExceptionController:访问出错,无法访问: " + ex.getMessage());
}
private HttpStatus getStatus(HttpServletRequest request) {
Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
if (statusCode == null) {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
return HttpStatus.valueOf(statusCode);
}
}
四、Postman测试
登录成功
有权限
没权限
登录失败
五、实例项目代码地址
码云:https://gitee.com/nevertouch/springboot-shiro.git