springboot+shiro 权限管理
一、为什么要了解权限框架
权限管理框架属于系统安全的范畴,权限管理实现对用户访问系统的控制,按照安全规则用户可以访问而且只能访问自己被授权的资源。
目前常见的权限框架有Shiro和Spring Security,本篇文章记录springboot整合shiro,实现简单的权限控制。
二、shiro介绍
Shiro全称是Apache Shiro,是一款灵活、强大的安全框架。方便简洁的处理身份认证、授权、加密等。
shiro的三个组件:
Subject 【主体】:指代当前用户【人、爬虫等】
SecurityManager【安全管理器】:管理所有的Subject、具体安全操作的真正执行者。
Reamls:本质是一个安全的DTO,用于进行权限、认证信息;
通过Subject来进行认证和授权,而Subject又委托给SecurityManager; 需要给Shrio的SecurityManager注入Realm,从而让SecurityManager能得到合法的用户及其权限进行判断。
三、环境准备
模拟场景【基于权限】:
1、可以跳转到add页面,说明拥有add权限。
2、可以跳转到update页面,说明拥有update权限。
3、拥有add权限只展示add的链接、拥有update权限只展示update的链接;
模拟场景【基于角色】:
1、拥有admin身份进入add、update,select页面,
2、拥有user身份只可以进入select页面。
实现效果:
环境:
jdk 17
Maven 3.8.6
Mysql 8.x
IDEA2021
springboot 2.7.0
3.1 数据库表以及相关数据
一共五张表:用户表、角色表、权限表、用户角色表、角色权限表。初始化SQL脚本在最后的传送门。
当前数据库数据:【用户->身份->权限】
张三,角色是admin 拥有权限有add
李四,角色是user,拥有权限有update
3.2、shiro环境准备
1、导入必要依赖、导入springboot-shiro的整合相关依赖依赖
<!-- boot-shiro整合依赖-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.10.0</version>
</dependency>
<!--shiro缓存-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.7.1</version>
</dependency>
<!-- mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<!-- mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- hutool工具包-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.11</version>
</dependency>
<!--thymeleaf模板引擎-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<version>2.6.4</version>
</dependency>
<!-- thymeleaf-shiro整合依赖 -->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</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>
2、配置文件的一一些必要参数
2、配置文件的一一些必要参数
server:
port: 8080
spring:
thymeleaf:
mode: HTML
cache: false
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3308/boot_mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&AllowPublicKeyRetrieval=True
username: root
password: root
debug: true
mybatis:
mapperLocations: mapper/*.xml
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #日志输出
map-underscore-to-camel-case: true #开启驼峰映射
3、 页面资源
准备页面资源:index.html、login.html。启动项目来到首页、不用登录登录情况下可以访问任意页。
项目目录结构:
四、自定义登录
第一步:需要先完成一些简单的配置:
1、新建UserReam类,继承AuthorizingRealm ,并重写他的认证和授权方法、实现自定义授权认证。
/** * 自定义UserRealm,用户认证授权登录 */ public class UserRealm extends AuthorizingRealm { @Autowired private UserService userService; @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token ) throws AuthenticationException { System.err.println("执行了+==========>认证AuthenticationInfo"); // 用户名密码数据库里取 UsernamePasswordToken userToken =(UsernamePasswordToken) token; IUser queryUser = new IUser(); queryUser.setUserName(userToken.getUsername()); List<IUser> userList = userService.selectUser(queryUser); if(CollectionUtils.isEmpty(userList)){ return null; }else { IUser user = userList.get(0); System.err.println("user:"+user); // 密码认证 简单的equals比较 return new SimpleAuthenticationInfo(user.getUserName(), user.getPassWord(),""); } } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { System.err.println("执行了+==========>授权doGetAuthenticationInfo"); String username = (String)principals.getPrimaryPrincipal(); System.err.println("username"+username); IUser queryUser = new IUser(); queryUser.setUserName(username); // 根据用户名获取身份、再由身份获取权限 List<IRole> roles = userService.selectRolesByUser(queryUser); if(CollectionUtils.isEmpty(roles)){ return null; }else { SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); roles.forEach(role -> { simpleAuthorizationInfo.addRole(role.getName()); //权限信息 List<IPermission> perms = userService.selectPermsByRole(role); if (!CollectionUtils.isEmpty(perms)) { perms.forEach(permission -> { simpleAuthorizationInfo.addStringPermission(permission.getPermission()); }); } }); return simpleAuthorizationInfo; } } }
2、新建ShiroConfiguration配置类,
配置类里创建了工厂对象、安全对象、自定Ream等bean对象、 shiro内置了五个过滤器,可对资源、请求接口等进行拦截
/**
* shiro配置类
*/
@Configuration
public class ShiroConfiguration {
/**
* 工厂对象3
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//给filter设置安全管理
bean.setSecurityManager(defaultWebSecurityManager);
/**
*
* 基于路径拦截资源
* anon 无需认证
* authc 必须认证
* user 记住我功能
* perms 拥有对某个资源
* roles 用某个角色权限
*/
Map<String,String> map = new HashMap<>();
map.put("/index","authc");
map.put("/toLogin","anon");
map.put("/","authc");
map.put("/toAdd","perms[add]");
map.put("/toUpdate","perms[update]");
map.put("/toSelect", "roles[admin]");
//更改默认的登录请求路径
bean.setLoginUrl("/toLogin");
//未授权请求路径
bean.setUnauthorizedUrl("/unauthorized");
bean.setFilterChainDefinitionMap(map);
return bean;
}
/**
* 安全对象2
*/
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 管理realm
securityManager.setRealm(userRealm);
return securityManager;
}
/**
* 创建realm对象,先创建,再接管1
*/
@Bean(name = "userRealm")
public UserRealm userRealm(){
return new UserRealm();
}
/**
* 页面的Shiro标签生效
* @return
*/
@Bean
public ShiroDialect shiroDialect(){
return new ShiroDialect();
}
}
shiro登录过程
传统登录功能:
shiro拿到用户信息后。不是直接调用业务逻辑层的方法。现由SecurityUtils拿到登录对象、由对象取执行login方法,由于UserRealm 继承了AuthorizingRealm、所以登录操作被拦截、完成认证操作。login方法会抛出需要认证失败的异常。根据异常信息可以给前端对应的提示。
第二步: 自定义登录方法
/**
* 自定义UserRealm,用户认证授权登录
*/
public class UserRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token ) throws AuthenticationException {
System.err.println("执行了+==========>认证AuthenticationInfo");
SimpleAuthenticationInfo info =null;
String username = token.getPrincipal().toString();
IUser queryUser = new IUser();
queryUser.setUserName(username);
List<IUser> dbUserList = userService.selectUser(queryUser);
if(CollectionUtils.isNotEmpty(dbUserList)){
IUser dbUser = dbUserList.get(0);
// 将注册时保存的随机盐构造ByteSource对象
info = new SimpleAuthenticationInfo(dbUser.getUserName(),dbUser.getPassWord(),this.getName());
// info = new SimpleAuthenticationInfo(dbUser.getUserName(),dbUser.getPassWord(),new SimpleByteSource(dbUser.getSalt()),this.getName());
}
return info;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.err.println("执行了+==========>授权doGetAuthenticationInfo");
String username = (String)principals.getPrimaryPrincipal();
System.err.println("username"+username);
IUser queryUser = new IUser();
queryUser.setUserName(username);
// 根据用户名获取身份、再由身份获取权限
List<IRole> roles = userService.selectRolesByUser(queryUser);
if(CollectionUtils.isEmpty(roles)){
return null;
}else {
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
roles.forEach(role -> {
simpleAuthorizationInfo.addRole(role.getName());
//权限信息
List<IPermission> perms = userService.selectPermsByRole(role);
if (!CollectionUtils.isEmpty(perms)) {
perms.forEach(permission -> {
simpleAuthorizationInfo.addStringPermission(permission.getPermission());
});
}
});
return simpleAuthorizationInfo;
}
}
}
完成了简单资源授权,为了前端展示,把对应的数据存到model
IUser user = new IUser();
user.setUserName(username);
List<IRole> roles = userService.selectRolesByUser(user);
List<IPermission> perms = userService.selectPermsByRole(roles.get(0));
model.addAttribute("role",roles.get(0).getName());
model.addAttribute("perms",perms);
效果如下: