文章目录
概要
提示:这里可以添加技术概要
例如:
Apache Shiro 是一个强大且易于使用的 Java 安全框架,提供了身份验证、授权、加密、会话管理等功能。Shiro 使用了责任链模式来处理安全操作,其中的过滤器链(Filter Chain)就是一个典型的责任链模式的应用。
自定义配置:在 Shiro 中,过滤器链由一系列的过滤器(Filter)组成,用于对请求进行身份验证、权限控制等操作。每个过滤器负责处理特定的安全操作,比如身份验证、权限检查等。在请求到达时,Shiro 会按照配置的顺序依次调用每个过滤器来完成相应的安全操作。
在shiro中考虑到在链中多个处理器会使得系统消耗过多的资源,shiro便内置了需求更大的一系列内置的常用过滤器用于处理常见的权限管理操作,比如身份验证、权限检查等。但有时候系统需要实现一些复杂的安全操作,这时可以通过自定义过滤器来扩展过滤器链。只需实现 org.apache.shiro.web.filter.AccessControlFilter 或 org.apache.shiro.web.filter.PathMatchingFilter 接口,并配置到 Shiro 的安全管理器中即可。
过滤器链配置:Shiro 允许通过配置文件或编程方式来配置过滤器链,可以动态地添加、删除或修改过滤器的顺序。这使得系统更加灵活,能够根据需求动态地调整过滤器链的组合和顺序。
过滤器链解析器:Shiro 提供了过滤器链解析器(Filter Chain Resolver)用于根据请求的 URL 匹配相应的过滤器链。通过自定义过滤器链解析器,可以实现更加灵活和精细的过滤器链配置,从而满足不同的安全需求。
过滤器链管理器:Shiro 提供了过滤器链管理器(Filter Chain Manager)用于管理和维护过滤器链。通过自定义过滤器链管理器,可以实现对过滤器链的动态管理和监控,包括过滤器的添加、删除、修改等操作。
整体架构流程
提示:这里可以添加技术整体架构
采用了mysql+springboot+shiro
代码
提示:这里可以添加技术细节
RBAC登录模型
项目结构
本文将从上到下一一展示代码哦,如果没有粘贴到的就是不重要的东西咯!!
返回结果集
package com.demo.common;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {
//状态码
private Integer code ;
//消息提示
private String message ;
//数据 List , Map, String , Book , Student
private T data ;
//操作成功,没有数据返回
public static<T> Result<T> success(){
return new Result<>(20000, "操作成功" ,null);
}
//操作成功,返回数据
public static<T> Result<T> success(T data){
return new Result<>(20000, "操作成功" ,data);
}
public static<T> Result<T> success(String message){
return new Result<>(20000, message ,null);
}
public static<T> Result<T> success(String message, T data){
return new Result<>(20000, message ,data);
}
public static<T> Result<T> success(Integer code , String message, T data){
return new Result<>(code, message ,data);
}
public static<T> Result<T> fail(){
return new Result<>(60204, "操作失败" ,null);
}
public static<T> Result<T> fail(String message){
return new Result<>(60204, message ,null);
}
public static<T> Result<T> fail(Integer code , String message){
return new Result<>(code , message , null);
}
public static<T> Result<T> fail(Integer code , String message, T data){
return new Result<>(code , message , data);
}
}
shiro配置方面
realm(可以理解为将你的权限方面的数据读取到shiro中)方面配置
package com.demo.config;
import com.demo.entity.ShiroPermission;
import com.demo.entity.ShiroRole;
import com.demo.entity.ShiroUser;
import com.demo.entity.req.ShiroAuthRequest;
import com.demo.service.ShiroPermissionService;
import com.demo.service.ShiroUserService;
import com.demo.utils.ShiroUtil;
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 org.springframework.util.StringUtils;
import javax.annotation.Resource;
public class CustomRealm extends AuthorizingRealm {
@Resource
private ShiroUtil shiroUtil;
@Override
//进行授权操作
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取登录用户名
String userName = (String) principalCollection.getPrimaryPrincipal();
ShiroAuthRequest shiroAuthRequest = shiroUtil.getRoleAndPerssions(userName);
//添加角色和权限
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.addRole(shiroAuthRequest.getRoleName());
for (String permissonName : shiroAuthRequest.getPermissionName()) {
simpleAuthorizationInfo.addStringPermission(permissonName);
}
return simpleAuthorizationInfo;
}
@Override
//进行认证操作
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
if (StringUtils.isEmpty((String) authenticationToken.getPrincipal())) {
return null;
}
//获取用户信息
String name = authenticationToken.getPrincipal().toString();
ShiroAuthRequest shiroAuthRequest = shiroUtil.getRoleAndPerssions(name);
if (shiroAuthRequest.getPassword() == null) {
//这里返回后会报出对应异常
return null;
} else {
//这里验证authenticationToken和simpleAuthenticationInfo的信息
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(name, shiroAuthRequest.getPassword().toString(), getName());
return simpleAuthenticationInfo;
}
}
}
shiroconfig(就是配置权限拦截等操作的地方)
package com.demo.config;
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.boot.autoconfigure.condition.ConditionalOnMissingBean;
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
@ConditionalOnMissingBean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
defaultAAP.setProxyTargetClass(true);
return defaultAAP;
}
//将自己的Realm(Realm相当于是集成mysql时的数据源,其中有授权和认证功能)加入容器
@Bean
public CustomRealm myShiroRealm() {
//拿到创建的 com.demo.config下的CustomRelam
CustomRealm customRealm = new CustomRealm();
return customRealm;
}
//权限管理,配置主要是Realm的管理认证
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//将自己定义的realm放到安全管理器中
securityManager.setRealm(myShiroRealm());
return securityManager;
}
//Filter工厂,设置对应的过滤条件和跳转条件
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 设置登录页面
shiroFilterFactoryBean.setLoginUrl("/login");
// 设置未授权页面
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
// 配置系统受限资源及权限规则
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
// 配置静态资源不拦截
filterChainDefinitionMap.put("/static/**", "anon");
//
filterChainDefinitionMap.put("/login", "anon");
// 配置退出过滤器,其中的具体实现代码需要根据您的项目来实现
filterChainDefinitionMap.put("/logout", "logout");
// 配置需要认证的URL
filterChainDefinitionMap.put("/admin/**", "authc");
// 上面配置的意思是,访问/admin/**下的URL需要认证(登录),未登录时会跳转到登录页面
// 配置需要特定角色才能访问的URL
filterChainDefinitionMap.put("/user/**", "roles[user]");
filterChainDefinitionMap.put("/teacher/delete", "roles[teacher]");
// 上面配置的意思是,访问/user/**下的URL需要拥有"user"角色,否则会被拦截
// 配置需要特定权限才能访问的URL
filterChainDefinitionMap.put("/article/create", "perms[article:create]");
// 上面配置的意思是,访问/article/create URL需要拥有"article:create"权限,否则会被拦截
// 配置匿名访问的URL,这些URL不需要登录即可访问
filterChainDefinitionMap.put("/public/**", "anon");
// 其余URL均需要认证,未登录的用户访问会被重定向到登录页面
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
//通过配置AuthorizationAttributeSourceAdvisor Bean,可以启用基于注解的权限控制,并确保安全管理器能够正确地解析和处理这些注解。
// 这样就可以更方便地在代码中实现细粒度的权限控制,而无需显式地编写大量的授权逻辑。
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
LoginController
package com.demo.controller;
import com.demo.common.Result;
import com.demo.entity.ShiroUser;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.subject.Subject;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
@RestController
@Slf4j
public class LoginController {
//http://localhost:9091/login?userName=zhangsan&password=123456
@PostMapping("/login")
public Result login(@RequestBody ShiroUser user) {
if (StringUtils.isEmpty(user.getUsername()) || StringUtils.isEmpty(user.getPassword())) {
return Result.fail("请输入用户名和密码");
}
//用户认证信息
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(
user.getUsername(),
user.getPassword()
);
try {
//进行验证,这里可以捕获异常,然后返回对应信息
subject.login(usernamePasswordToken);
//可以检查登陆的用户是否具有admin角色
// subject.checkRole("admin");
//检查是否有query和add权限
// subject.checkPermissions("query", "add");
System.out.println("登录成功");
} catch (UnknownAccountException e) {
log.error("用户名不存在!", e);
return Result.fail("用户名不存在");
} catch (AuthenticationException e) {
log.error("账号或密码错误!", e);
return Result.fail("账号或密码错误");
} catch (AuthorizationException e) {
log.error("没有权限!", e);
return Result.fail("没有权限");
}
return Result.success(20000,"登陆成功",user);
}
@RequiresRoles("teacher")
@GetMapping("/teacher/delete")
public String admin() {
return "这是只有老师能干的删除方法";
}
@RequiresPermissions("query")
@GetMapping("/index")
public Result index() {
return Result.success("成功");
}
@RequiresPermissions("add")
@GetMapping("/add")
public String add() {
return "add success!";
}
@GetMapping("/403")
public Result error() {
return Result.fail("没有权限的");
}
}
Dao层代码和mapper映射文件和service层等代码是通过easycode自动生成。可以根据自己的需求,在数据库中设计生成自己的权限管理代码,这里就不展示咯(太长了),如果有需要的宝子们可以私信我找我要喔!
实体类
package com.demo.entity.req;
import lombok.Data;
@Data
public class RoleReq {
private String roleName;
private Integer roleId;
}
package com.demo.entity.req;
import com.demo.entity.ShiroRole;
import com.demo.entity.ShiroUser;
import lombok.Data;
import java.security.Permission;
import java.util.List;
@Data
public class ShiroAuthRequest {
private String userName;
private String password;
private String roleName;
private List<String> permissionName;
}
package com.demo.entity.req;
import com.demo.entity.ShiroPermission;
import com.demo.entity.ShiroRole;
import com.demo.entity.ShiroUser;
import lombok.Data;
@Data
public class UserRequest extends ShiroUser {
private ShiroRole shiroRole;
private ShiroPermission shiroPermission;
}
package com.demo.entity.req;
import com.demo.entity.ShiroRole;
import com.demo.entity.ShiroUser;
import lombok.Data;
import java.util.List;
@Data
public class UserRoleReq{
private RoleReq roleReq;
private ShiroUser shiroUser;
private List<String> permissionName;
}
package com.demo.entity;
import java.io.Serializable;
/**
* (ShiroPermission)实体类
*
* @author makejava
* @since 2024-04-01 16:59:21
*/
public class ShiroPermission implements Serializable {
private static final long serialVersionUID = 809854245653804484L;
private Integer id;
private String permissionname;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getPermissionname() {
return permissionname;
}
public void setPermissionname(String permissionname) {
this.permissionname = permissionname;
}
}
package com.demo.entity;
import java.io.Serializable;
/**
* (ShiroRole)实体类
*
* @author makejava
* @since 2024-04-01 16:59:22
*/
public class ShiroRole implements Serializable {
private static final long serialVersionUID = 375737695740115429L;
private Integer id;
private String rolename;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getRolename() {
return rolename;
}
public void setRolename(String rolename) {
this.rolename = rolename;
}
}
package com.demo.entity;
import java.io.Serializable;
/**
* (ShiroRolePermission)实体类
*
* @author makejava
* @since 2024-04-01 16:59:22
*/
public class ShiroRolePermission implements Serializable {
private static final long serialVersionUID = 566977603081784919L;
private Integer id;
private Integer roleid;
private Integer permissionid;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getRoleid() {
return roleid;
}
public void setRoleid(Integer roleid) {
this.roleid = roleid;
}
public Integer getPermissionid() {
return permissionid;
}
public void setPermissionid(Integer permissionid) {
this.permissionid = permissionid;
}
}
package com.demo.entity;
import java.io.Serializable;
/**
* (ShiroUser)实体类
*
* @author makejava
* @since 2024-04-01 16:59:22
*/
public class ShiroUser implements Serializable {
private static final long serialVersionUID = -95988511545390278L;
private Integer id;
private String username;
private String password;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
package com.demo.entity;
import java.io.Serializable;
/**
* (ShiroUserRole)实体类
*
* @author makejava
* @since 2024-04-01 16:59:22
*/
public class ShiroUserRole implements Serializable {
private static final long serialVersionUID = -16668937319264282L;
private Integer id;
private Integer userid;
private Integer roleid;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getUserid() {
return userid;
}
public void setUserid(Integer userid) {
this.userid = userid;
}
public Integer getRoleid() {
return roleid;
}
public void setRoleid(Integer roleid) {
this.roleid = roleid;
}
}
utils
package com.demo.utils;
import com.demo.entity.ShiroPermission;
import com.demo.entity.ShiroRole;
import com.demo.entity.ShiroUser;
import com.demo.entity.ShiroUserRole;
import com.demo.entity.req.ShiroAuthRequest;
import com.demo.entity.req.UserRoleReq;
import com.demo.service.ShiroPermissionService;
import com.demo.service.ShiroRoleService;
import com.demo.service.ShiroUserRoleService;
import com.demo.service.ShiroUserService;
import com.google.common.base.Preconditions;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
@Slf4j
@Component
public class ShiroUtil {
@Resource
private ShiroUserService shiroUserService;
public ShiroAuthRequest getRoleAndPerssions(String userName){
ShiroAuthRequest shiroAuthRequest = new ShiroAuthRequest();
//查询用户名称
UserRoleReq userRoleByName = shiroUserService.getUserRoleByName(userName);
List<String> permissionName = userRoleByName.getPermissionName();
shiroAuthRequest.setPassword(userRoleByName.getShiroUser().getPassword());
Preconditions.checkNotNull(shiroAuthRequest.getPassword(),"用户密码不能为空");
shiroAuthRequest.setUserName(userName);
Preconditions.checkNotNull(shiroAuthRequest.getUserName(),"用户名不能为空");
shiroAuthRequest.setRoleName(userRoleByName.getRoleReq().getRoleName());
Preconditions.checkNotNull(shiroAuthRequest.getRoleName(),"用户角色不能为空");
shiroAuthRequest.setPermissionName(permissionName);
log.info("用户 {} 的权限列表:{}", userName, permissionName);
return shiroAuthRequest;
}
}
配置类
server:
port: 9091
Spring:
application:
name: MyDemo01
datasource:
druid:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/MyDemoRoleChain?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: 1234
mybatis-plus:
configuration:
#在映射实体或者属性时,将数据库中表名和字段名中的下划线去掉,按照驼峰命名法映射
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
banner: false
mapper-locations: classpath:mapper/*.xml
pom文件
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>MyAuthRoleChain</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>Demo01</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.26</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>19.0</version>
</dependency>
</dependencies>
</project>
运行截图
小结
提示:这里可以添加总结
在Shiro中,责任链模式的运用体现在处理HTTP请求的过滤器链上。每个过滤器都负责一项特定的安全任务,比如身份认证、权限授权或会话管理。当一个HTTP请求到达时,它会按顺序经过配置的过滤器链。每个过滤器依次检查请求,执行其任务,然后决定是否要将请求传递给链中的下一个过滤器。如果某个过滤器确定请求不符合安全管理器要求,它可以阻止请求继续传递,并直接响应请求,例如重定向到登录页面或返回一个错误消息。
通过这种方式,Shiro允许开发者灵活地配置安全策略,根据需要添加、移除或重新排序过滤器。这种模式使得Shiro的安全机制非常灵活和可扩展,开发者可以根据应用程序的具体需求定制过滤器链,实现细粒度的安全控制。