一、RBAC的基本概念
RBAC模型通过角色与权限的关联,实现用户与权限的逻辑分离,从而简化权限管理。在RBAC中,有三个核心要素:用户、角色和权限。
用户:需要访问系统资源的个体。每个用户都有不同且唯一的ID,用来进行识别,并被授予不同的角色。
角色:一组权限的集合,代表某种职责或功能。例如,在企业管理系统中,可以有“管理员”、“普通员工”、“财务人员”等角色。
权限:对特定资源的访问能力,如读取、写入、执行等。
二、RBAC的工作原理
RBAC通过将权限分配给角色,再将角色分配给用户,从而控制用户对系统资源的访问。这个过程可以概括为:判断“Who(用户)是否可以对What(资源)进行How(操作)的访问”,并判断这个逻辑表达式的值是否为True。
角色分配:将角色分配给用户的过程。用户可以通过被分配到一个或多个角色来获得相应的权限。
权限分配:将权限分配给角色的过程。每个角色都被赋予特定的权限,这些权限决定了用户在系统中可以执行哪些操作。
三. Shiro授权的基本概念
授权:也叫访问控制,即在应用中控制谁访问哪些资源(如访问页面、编辑数据、页面操作等)。
关键对象:
主体(Subject):访问应用的用户,在Shiro中使用Subject代表该用户。用户只有授权后才允许访问相应的资源。
资源(Resource):在应用中用户可以访问的URL,比如访问JSP页面、查看或编辑某些数据、访问某个业务方法、打印文本等都是资源。用户只有授权后才能访问。
权限(Permission):安全策略中的原子授权单位,通过权限可以表示在应用中用户有没有操作某个资源的权力,如访问用户列表页面、查看或新增、修改、删除用户数据等。Shiro支持粗粒度权限(如用户模块的所有权限)和细粒度权限(操作某个用户的权限,即实例级别的)。
角色(Role):权限的集合,一般情况下会赋予用户角色而不是权限,即这样用户可以拥有一组权限,赋予权限时比较方便。典型的如项目经理、技术总监、CTO、开发工程师等都是角色,不同的角色拥有一组不同的权限。
添加Shiro依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<classifier>jakarta</classifier>
<version>2.0.1</version>
<!-- 排除仍使用了javax.servlet的依赖shiro-core、shiro-web包 -->
<exclusions>
<exclusion>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
</exclusion>
</exclusions>
</dependency>
配置Shiro
@Configuration
public class ShiroConfig {
// 注入Redis参数,从application.yml获得
@Value("${spring.data.redis.host}")
private String host;
@Value("${spring.data.redis.port}")
private int port;
@Value("${spring.data.redis.password}")
private String password;
@Value("${spring.data.redis.timeout}")
private int timeout;
@Resource
private RoleService roleService;
@Bean(name = "shiroDialect")
public ShiroDialect shiroDialect(){
// thymeleaf页面上实用shiro标签
return new ShiroDialect();
}
/**
* 开启Shiro注解(如@RequiresRoles,@RequiresPermissions),
* 需借助SpringAop扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
* 配置一下两个Bean(DefaultAdvisorAutoProxyCreator和
AuthorizationAttributeSourceAdvisor)
*/
@Bean
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
/*
* 开启aop注解支持
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor=new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
@Bean
public MyShiroRealm myShiroRealm() { // 自定义Realm
MyShiroRealm shiroRealm = new MyShiroRealm();
// 设置启用缓存,并设置缓存名称
shiroRealm.setCachingEnabled(true);
return shiroRealm;
}
@Bean
public SecurityManager securityManager(){ // 安全管理器SecurityManager
DefaultWebSecurityManager defaultSecurityManager=new DefaultWebSecurityManager();
// 注入Realm
defaultSecurityManager.setRealm(myShiroRealm());
// 注入缓存管理器
defaultSecurityManager.setCacheManager(cacheManager());
// 注入会话管理器
defaultSecurityManager.setSessionManager(sessionManager());
SecurityUtils.setSecurityManager(defaultSecurityManager);
return defaultSecurityManager;
}
@Bean
public ShiroFilterFactoryBean shiroFilterFactory(SecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactory=new ShiroFilterFactoryBean();
// 注入SecurityManager
shiroFilterFactory.setSecurityManager(securityManager);
// 权限验证:使用Filter控制资源(URL)的访问
shiroFilterFactory.setLoginUrl("/index");
shiroFilterFactory.setSuccessUrl("/main");
shiroFilterFactory.setUnauthorizedUrl("/403"); // 没有权限跳转403页面
Map<String,String> filterChainDefinitionMap=new LinkedHashMap<String, String>(); // 必须使用LinkedHashMap(有序集合)
// 配置可以匿名访问的资源(URL):静态资源
filterChainDefinitionMap.put("/css/**","anon");
filterChainDefinitionMap.put("/fonts/**","anon");
filterChainDefinitionMap.put("/images/**","anon");
filterChainDefinitionMap.put("/js/**","anon");
filterChainDefinitionMap.put("/localcss/**","anon");
filterChainDefinitionMap.put("/localjs/**","anon");
filterChainDefinitionMap.put("/login","anon");
filterChainDefinitionMap.put("/logout","logout");
//配置需要特定权限才能访问的资源(URL)
//静态授权:包括全部需要特定权限才能访问的资源(URL)
filterChainDefinitionMap.put("/user/list","perms[用户列表]");
filterChainDefinitionMap.put("/user/add","perms[用户添加]");
filterChainDefinitionMap.put("/user/edit","perms[用户编辑]");
filterChainDefinitionMap.put("/user/del","perms[用户删除");
//配置认证访问:其他资源(URL)必须认证通过才能访问;
filterChainDefinitionMap.put("/**","authc"); //必须放在过滤器链的最后面
shiroFilterFactory.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactory;
}
}
创建一个自定义的Realm类,用于处理用户的认证和授权逻辑。例如:
public class MyShiroRealm extends AuthorizingRealm { // 安全数据源
@Resource
private UserService userService;
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("调用MyShiroRealm.doGetAuthenticationInfo获取身份信息!");
// 获得身份信息
UsernamePasswordToken token=(UsernamePasswordToken) authenticationToken;
String usrName=token.getUsername();
User user=userService.getUserByUsrName(usrName);
if (user==null){
throw new UnknownAccountException(); // 账号错误
}
if (user.getUsrFlag()==null||user.getUsrFlag().intValue()==0){
throw new LockedAccountException(); // 账号锁定
}
// user身份信息,user.getUsrPassword()密码,getName()realm名称
SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(user,user.getUsrPassword(),getName());
// 返回身份信息
return info;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("调用MyShiroRealm.doGetAuthorizationInfo获取权限信息!");
// 获取权限信息
User user=(User) principalCollection.getPrimaryPrincipal();
SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
// 静态授权:授予主题(用户)相应的角色和权限
info.addRole(user.getRole().getRoleName());
info.addStringPermission("用户列表"); // 所有用户拥有“用户列表”权限
if ("管理员".equals(user.getRole().getRoleName())){
info.addStringPermission("用户添加");
info.addStringPermission("用户编辑");
info.addStringPermission("用户删除");
}
return info;
}
}
在Spring Boot的控制器中,可以通过注解来实现权限控制。例如:
@Controller
public class indexController {
@Resource
private UserService userService;
@Resource
private RoleService roleService;
@RequestMapping(value = "/403")
public String unauthorized(){
return "403";
}
@RequestMapping("/login")
public String login(){
return "login";
}
@RequestMapping("/main")
public String toMain(){
return "main";
}
@RequestMapping("/logout")
public String logout(HttpSession session){
session.removeAttribute("loginUser");
session.invalidate();
SecurityUtils.getSubject().logout(); // Shiro注销
return "redirect:/login";
}
@PostMapping("/login")
public String doLogin(String usrName, String usrPassword, HttpServletRequest request,Model model,HttpSession session){
try {
UsernamePasswordToken token=new UsernamePasswordToken(usrName,usrPassword);
Subject subject= SecurityUtils.getSubject();
subject.login(token);
// 认证(登录)成功
User user=(User) subject.getPrincipal();
// 获取权限
Role role=user.getRole(); // 如果一对多关联不是立即加载,则需要通过用户获取校色
List<Right> rights=roleService.findRightsByRole(role);
role.getRights().addAll(rights);
session.setAttribute("loginUser",user);
return "redirect:/main";
}catch (UnknownAccountException | IncorrectCredentialsException e){
model.addAttribute("message","用户名或密码失败,登录失败!");
return "login";
}catch (LockedAccountException e){
model.addAttribute("message","用户被禁用,登录失败!");
return "login";
}catch (AuthenticationException e){
model.addAttribute("message","认证异常,登录失败!");
return "login";
}
}
}