Shiro 授权(Authorization)

术语简介

授权

授权,也叫访问控制,即在应用中控制谁能访问哪些资源(如访问页面/编辑数据/页面操作等)。在授权中需要了解的几个关键对象:主体(Subject)、资源(Resource)、权限(Permission)、角色(Role)。

主体

主体,即访问应用的用户,在 Shiro 中使用 Subject 代表该用户。用户只有授权后才允许访问相应的资源。

资源

在应用中用户可以访问的任何东西,比如访问 JSP 页面、查看/编辑某些数据、访问某个业务方法、打印文本等等都是资源。用户只有授权后才能访问。

权限

安全策略中的原子授权单位,通过权限我们可以表示在应用中用户有没有操作某个资源的权力。即权限表示在应用中用户能不能访问某个资源,如:访问用户列表页面、查看/新增/修改/删除用户数据(即很多时候都是 CRUD(增查改删)式权限控制)、打印文档等等。如上可以看出,权限代表了用户有没有操作某个资源的权力,即反映在某个资源上的操作允不允许,不反映谁去执行这个操作。所以后续还需要把权限赋予给用户,即定义哪个用户允许在某个资源上做什么操作(权限),Shiro不会去做这件事情,而是由实现人员提供。
Shiro支持粗粒度权限(如用户模块的所有权限)和细粒度权限(操作某个用户的权限,
即实例级别的)。

角色

角色代表了操作集合,可以理解为权限的集合,一般情况下我们会赋予用户角色而不是权限,即这样用户可以拥有一组权限,赋予权限时比较方便。典型的如:项目经理、技术总监、CTO、开发工程师等都是角色,不同的角色拥有一组不同的权限。

授权(Authorization)

基本操作

  1. 配置 shiro.ini
#对用户信息进行配置
[users]
#用户账号和密码
#配置规则: 用户账号=密码,角色1,角色2
hxy=123456,管理员
zhangsan=000000,项目经理

#对权限信息进行配置,基于角色配置
[roles]
#角色和权限
#配置规则:角色=权限1,权限2。权限字符串可使用通配符配置
#管理员能够对用户和角色进行所有操作
管理员=user:*,role:*
#客户经理只能对用户进行列表和详情的查看操作
客户经理=user:list,user:view
  1. 测试
@Test
public void testShiro() {
    //1.创建Realm(安全数据源)
    //通过shiro.ini配置文件创建Realm
    IniRealm realm = new IniRealm("classpath:shiro.ini");
    //2.配置SecurityManager
    DefaultSecurityManager securityManager = new DefaultSecurityManager();

    //注入创建的Realm(安全数据源)
    securityManager.setRealm(realm);
    SecurityUtils.setSecurityManager(securityManager);
    //3.操作Subject,进行认证
    Subject subject = SecurityUtils.getSubject();
    //封装一个令牌
    UsernamePasswordToken token = new UsernamePasswordToken("hxy", "123456");
    try {
        subject.login(token);
    } catch (AuthenticationException e) {
        System.out.println("认证异常:");
        e.printStackTrace();
    }
    System.out.println("是否通过认证:" + subject.isAuthenticated());
    System.out.println("身份信息:" + subject.getPrincipal());

    //认证通过后进行权限验证
    System.out.println("是否为管理员角色:"+subject.hasRole("管理员"));//判断是否为某角色
    System.out.println("是否能操作用户查看功能:"+subject.isPermitted("user:view"));//判断是否拥有某权限
    //也可以使用check方法判断是否拥有某权限,但是失败的情况下会抛出UnauthorizedException异常
    //subject.checkPermission("user:view");
}

执行结果:
在这里插入图片描述

授权流程

在这里插入图片描述
4. 首先调用 Subject.isPermitted*/hasRole* 接口,其会委托给 SecurityManager,而
SecurityManager 接着会委托给 Authorizer ;
5. Authorizer 是真正的授权者,如果我们调用如 isPermitted(“user:view”),其首先会通
过 PermissionResolver 把字符串转换成相应的 Permission 实例;
6. 在进行授权之前,其会调用相应的 Realm 获取 Subject 相应的角色/权限用于匹配传
入的角色/权限;
7. Authorizer 会判断 Realm 的角色/权限是否和传入的匹配,如果有多个 Realm,会委托
给 ModularRealmAuthorizer 进行循环判断,isPermitted*/hasRole* 如果匹配会返回
true ,否则返回 false 表示授权失败。

授权方式

Shiro 支持四种方式的授权验证(权限控制):

  1. 代码级别权限控制:通过写 if/else 授权代码块完成,前面测试类中即该方式
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole("管理员")){
	//有权限
}else{
	//无权限
}
  1. 页面标签权限控制:在页面通过相应的标签完成
<shiro:hasRole name="管理员">
	<!-有权限->
</shiro:hasRole>
  1. 方法注解权限控制:通过在执行的 Java 方法上添加相应的注解完成
@RequiresRoles("管理员")
public String add(Model model){	
	//有权限
}
  1. URL 拦截器权限控制:在 ShiroFilterFactoryBean 对象中配置 URL 拦截器规则完成
filterChainDefinitionMap.put("/login","anon");

filterChainDefinitionMap.put("/user/add","perms[用户添加]");
filterChainDefinitionMap.put("/user/modify","perms[用户编辑]");
filterChainDefinitionMap.put("/user/del","perms[用户删除]");*/

SpringBoot(API)+Shiro 授权

静态授权

静态授权(将权限信息写死)
在这里插入图片描述

  1. 自定义 Realm 授予权限
import com.hxy.crm.pojo.Right;
import com.hxy.crm.pojo.Role;
import com.hxy.crm.pojo.User;
import com.hxy.crm.service.UserService;
import org.apache.shiro.authc.*;
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 javax.annotation.Resource;
import java.util.Set;

public class MyShiroRealm extends AuthorizingRealm {//安全数据源

    @Resource
    private UserService userService;

    @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;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("调用MyShiroRealm.doGetAuthenticationInfo获取身份信息!");
        //获得身份信息
        UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
        String userName = token.getUsername();
        User user = userService.getUserByUserName(userName);
        if(user==null){
            throw new UnknownAccountException();//账号错误
        }
        if(user.getUserFlag()==null||user.getUserFlag().intValue()==0){
            throw new LockedAccountException();//账号锁定
        }
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(
                user,//身份(根据用户名查询数据库获得的用户)
                user.getUserPassword(),//凭证(查询数据库获得密码)
                getName()//Realm对象的名称
        );
        //返回身份信息
        return info;

    }

}
  1. 配置 ShiroConfig 拦截 URL
import com.hxy.crm.pojo.Right;
import com.hxy.crm.service.RoleService;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

@Configuration
public class ShiroConfig {
    @Bean
    public MyShiroRealm myShiroRealm(){//自定义Realm
        MyShiroRealm shiroRealm = new MyShiroRealm();
        return shiroRealm;
    }

    @Bean
    public SecurityManager securityManager(){//安全管理器SecurityManager
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //注入Realm
        securityManager.setRealm(myShiroRealm());
        return securityManager;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactory(SecurityManager securityManager){//Shiro过滤器:权限验证
        ShiroFilterFactoryBean shiroFilterFactory = new ShiroFilterFactoryBean();
        //注入SecurityManager
        shiroFilterFactory.setSecurityManager(securityManager);
        //权限验证:使用Filter控制资源(URL)的访问
        shiroFilterFactory.setLoginUrl("/login");
        shiroFilterFactory.setSuccessUrl("/main");
        shiroFilterFactory.setUnauthorizedUrl("/403");
        Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();//必须使用LinkedHashMap(有序集合)
        //配置可以匿名访问的资源(URL): 静态资源
        filterChainDefinitionMap.put("/statics/css/**","anon");
        filterChainDefinitionMap.put("/statics/fonts/**","anon");
        filterChainDefinitionMap.put("/statics/images/**","anon");
        filterChainDefinitionMap.put("/statics/js/**","anon");
        filterChainDefinitionMap.put("/statics/localcss/**","anon");
        filterChainDefinitionMap.put("/statics/lcaljs/**","anon");

        filterChainDefinitionMap.put("/login","anon");
        filterChainDefinitionMap.put("/logout","logout");//注销过滤器,自动注销

        //配置需要特定权限才能访问的资源(URL)
        //静态授权:包括全部需要特定权限才能访问的资源(URL)
        filterChainDefinitionMap.put("/user/list","perms[用户列表]");
        filterChainDefinitionMap.put("/user/add","perms[用户添加]");
        filterChainDefinitionMap.put("/user/modify","perms[用户编辑]");
        filterChainDefinitionMap.put("/user/del","perms[用户删除]");

        //配置认证访问,其他资源(URL)必须认证通过才能访问
        filterChainDefinitionMap.put("/**","authc");//必须放在过滤器链的后面

        shiroFilterFactory.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactory;
    }

}

URL 拦截权限控制

过滤器简称对应的 java 类说明
anonAnonymousFilter匿名拦截,即不需要登录即可访间;一般用于静态资源过滤
authcFormAuthenticationFilter基于表单的拦截,如果没有登录会跳到相应的登录页面登录
authcBasicBasicHttpAuthenticationFilterBasic HTTP 身份验证拦截
permsPermissionsAuthorizationFilter权限授权拦截,验证用户是否拥有指定权限;例如:perms[用户列表]
portPortFilter端口拦截
restHttpMethodPermissionFilterrest风格拦截,自动根据请求方法构建权限字符串
rolesRolesAuthorizationFilter角色授权拦截,验证用户是否拥有所有角色;
sslSslFiltersSL拦截,只有请求协议是 https才能通过:
userUserFilter用户拦截,用户已经身份验证/记住我登录的都可
logoutLogoutFilter退出拦截

页面标签权限控制

  1. pom.xml 添加依赖
<dependency>
    <groupId>com.github.theborakompanioni</groupId>
    <artifactId>thymeleaf-extras-shiro</artifactId>
    <version>2.0.0</version>
</dependency>
  1. ShiroConfig.java 配置类中添加配置
@Bean(name = "shiroDialect")
public ShiroDialect shiroDialect(){//thymeleaf 页面上使用 shiro 标签
	return new ShiroDialect ();
}
  1. 页面头部导入标签库
<html lang="en" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"
	xmlns:th="http://www.thymeleaf.org"
	xmlns:layout="http://www.ultraq.net.nz/web/thymeleaf/layout"
	layout:decorate="main">
  1. 使用标签
<a href="/user/add" th:href="@{/user/add}" shiro:hasPermission="用户添加">新增</a>

表示用户拥有 “用户添加” 权限则显示 “新增” 按钮

动态授权

在这里插入图片描述

在这里插入图片描述

#权限表
CREATE TABLE `sys_right` (
  `right_code` varchar(50) COLLATE utf8_unicode_ci NOT NULL,
  `right_parent_code` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
  `right_type` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL,
  `right_text` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
  `right_url` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL,
  `right_tip` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`right_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

#角色权限关联表
CREATE TABLE `sys_role_right` (
  `rf_id` bigint(20) NOT NULL AUTO_INCREMENT,
  `rf_role_id` bigint(20) DEFAULT NULL,
  `rf_right_code` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`rf_id`),
  KEY `sys_role_right_ibfk_1` (`rf_right_code`),
  KEY `FK_sys_role_right` (`rf_role_id`),
  CONSTRAINT `FK_sys_role_right` FOREIGN KEY (`rf_role_id`) REFERENCES `sys_role` (`role_id`),
  CONSTRAINT `sys_role_right_ibfk_1` FOREIGN KEY (`rf_right_code`) REFERENCES `sys_right` (`right_code`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

Right.java 实体类

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import javax.persistence.*;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;

@Entity
@Table(name="sys_right")
@JsonIgnoreProperties(value = {"hibernateLazyInitializer","handler"})
public class Right implements Serializable {
	@Id
	@Column(name = "right_code")
	private String rightCode;
	@Column(name = "right_parent_code")
	private String rightParentCode;
	@Column(name = "right_type")
	private String rightType;
	@Column(name = "right_text")
	private String rightText;
	@Column(name = "right_url")
	private String rightUrl;
	@Column(name = "right_tip")
	private String rightTip;
	
	@ManyToMany(targetEntity = Role.class,mappedBy = "rights")
	@JsonIgnore
	private Set<Role> roles = new HashSet<Role>(0);
	}

修改 Role.java 实体类

@Entity
@Table(name = "sys_role")
@JsonIgnoreProperties(value = {"hibernateLazyInitializer","handler"})
public class Role implements Serializable {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "role_id")
	private Long roleId;
	@Column(name = "role_name")
	private String roleName;
	@Column(name = "role_desc")
	private String roleDesc;
	@Column(name = "role_flag")
	private Integer roleFlag;
	@OneToMany(targetEntity = User.class, fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.REMOVE}, mappedBy = "role")
	private Set<User> users = new HashSet<User>();
	
	//多对多
	@ManyToMany(targetEntity = Right.class,fetch = FetchType.EAGER)
	@JoinTable(name = "sys_role_right",joinColumns = {@JoinColumn(name = "rf_role_id")},inverseJoinColumns = {@JoinColumn(name = "rf_right_code")})
	@OrderBy(value = "rightCode")
	private Set<Right> rights = new HashSet<Right>(0);
	}

开发RightRepository

public interface RightRepository extends JpaRepository<Right,String> {
    public List<Right> findRightsByRolesOrderByRightCode(Role role);
}

调整 MyShiroRealm

public class MyShiroRealm extends AuthorizingRealm {//安全数据源

    @Resource
    private UserService userService;

    @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("用户删除");
        }*/

        //动态授权
        Role role = user.getRole();
        if(role!=null){
            info.addRole(user.getRole().getRoleName());
            Set<Right> rights = role.getRights();
            if(rights!=null && rights.size()>0){
                for (Right right :rights){
                    info.addStringPermission(right.getRightCode());
                }
            }
        }
        return info;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("调用MyShiroRealm.doGetAuthenticationInfo获取身份信息!");
        //获得身份信息
        UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
        String userName = token.getUsername();
        User user = userService.getUserByUserName(userName);
        if(user==null){
            throw new UnknownAccountException();//账号错误
        }
        if(user.getUserFlag()==null||user.getUserFlag().intValue()==0){
            throw new LockedAccountException();//账号锁定
        }
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(
                user,//身份(根据用户名查询数据库获得的用户)
                user.getUserPassword(),//凭证(查询数据库获得密码)
                getName()//Realm对象的名称
        );
        //返回身份信息
        return info;

    }

}

修改ShiroConfig

public class ShiroConfig {

    @Resource
    private RoleService roleService;

    @Bean
    public MyShiroRealm myShiroRealm(){//自定义Realm
        MyShiroRealm shiroRealm = new MyShiroRealm();
        return shiroRealm;
    }

    @Bean
    public SecurityManager securityManager(){//安全管理器SecurityManager
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //注入Realm
        securityManager.setRealm(myShiroRealm());
        return securityManager;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactory(SecurityManager securityManager){//Shiro过滤器:权限验证
        ShiroFilterFactoryBean shiroFilterFactory = new ShiroFilterFactoryBean();
        //注入SecurityManager
        shiroFilterFactory.setSecurityManager(securityManager);
        //权限验证:使用Filter控制资源(URL)的访问
        shiroFilterFactory.setLoginUrl("/login");
        shiroFilterFactory.setSuccessUrl("/main");
        shiroFilterFactory.setUnauthorizedUrl("/403");
        Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();//必须使用LinkedHashMap(有序集合)
        //配置可以匿名访问的资源(URL): 静态资源
        filterChainDefinitionMap.put("/statics/css/**","anon");
        filterChainDefinitionMap.put("/statics/fonts/**","anon");
        filterChainDefinitionMap.put("/statics/images/**","anon");
        filterChainDefinitionMap.put("/statics/js/**","anon");
        filterChainDefinitionMap.put("/statics/localcss/**","anon");
        filterChainDefinitionMap.put("/statics/lcaljs/**","anon");

        filterChainDefinitionMap.put("/login","anon");
        filterChainDefinitionMap.put("/logout","logout");//注销过滤器,自动注销

        //配置需要特定权限才能访问的资源(URL)
        //静态授权:包括全部需要特定权限才能访问的资源(URL)
        /*filterChainDefinitionMap.put("/lists","perms[用户列表]");
        filterChainDefinitionMap.put("/adds","perms[用户添加]");
        filterChainDefinitionMap.put("/saves","perms[用户添加]");
        filterChainDefinitionMap.put("/modifys/**","perms[用户编辑]");
        filterChainDefinitionMap.put("/modifyss","perms[用户编辑]");
        filterChainDefinitionMap.put("/del","perms[用户删除]");*/

        //动态授权
        List<Right> rights = roleService.findAllRights();
        for (Right right : rights){
            if(right.getRightUrl()!=null && !right.getRightUrl().trim().equals("")){
                filterChainDefinitionMap.put(right.getRightUrl(),"perms["+right.getRightCode()+"]");
            }
        }

        //配置认证访问,其他资源(URL)必须认证通过才能访问
        filterChainDefinitionMap.put("/**","authc");//必须放在过滤器链的后面

        shiroFilterFactory.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactory;
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值