Shiro 授权(Authorization)
术语简介
授权
授权,也叫访问控制,即在应用中控制谁能访问哪些资源(如访问页面/编辑数据/页面操作等)。在授权中需要了解的几个关键对象:主体(Subject)、资源(Resource)、权限(Permission)、角色(Role)。
主体
主体,即访问应用的用户,在 Shiro 中使用 Subject 代表该用户。用户只有授权后才允许访问相应的资源。
资源
在应用中用户可以访问的任何东西,比如访问 JSP 页面、查看/编辑某些数据、访问某个业务方法、打印文本等等都是资源。用户只有授权后才能访问。
权限
安全策略中的原子授权单位,通过权限我们可以表示在应用中用户有没有操作某个资源的权力。即权限表示在应用中用户能不能访问某个资源,如:访问用户列表页面、查看/新增/修改/删除用户数据(即很多时候都是 CRUD(增查改删)式权限控制)、打印文档等等。如上可以看出,权限代表了用户有没有操作某个资源的权力,即反映在某个资源上的操作允不允许,不反映谁去执行这个操作。所以后续还需要把权限赋予给用户,即定义哪个用户允许在某个资源上做什么操作(权限),Shiro不会去做这件事情,而是由实现人员提供。
Shiro支持粗粒度权限(如用户模块的所有权限)和细粒度权限(操作某个用户的权限,
即实例级别的)。
角色
角色代表了操作集合,可以理解为权限的集合,一般情况下我们会赋予用户角色而不是权限,即这样用户可以拥有一组权限,赋予权限时比较方便。典型的如:项目经理、技术总监、CTO、开发工程师等都是角色,不同的角色拥有一组不同的权限。
授权(Authorization)
基本操作
- 配置 shiro.ini
#对用户信息进行配置
[users]
#用户账号和密码
#配置规则: 用户账号=密码,角色1,角色2
hxy=123456,管理员
zhangsan=000000,项目经理
#对权限信息进行配置,基于角色配置
[roles]
#角色和权限
#配置规则:角色=权限1,权限2。权限字符串可使用通配符配置
#管理员能够对用户和角色进行所有操作
管理员=user:*,role:*
#客户经理只能对用户进行列表和详情的查看操作
客户经理=user:list,user:view
- 测试
@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 支持四种方式的授权验证(权限控制):
- 代码级别权限控制:通过写 if/else 授权代码块完成,前面测试类中即该方式
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole("管理员")){
//有权限
}else{
//无权限
}
- 页面标签权限控制:在页面通过相应的标签完成
<shiro:hasRole name="管理员">
<!-有权限->
</shiro:hasRole>
- 方法注解权限控制:通过在执行的 Java 方法上添加相应的注解完成
@RequiresRoles("管理员")
public String add(Model model){
//有权限
}
- 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 授权
静态授权
静态授权(将权限信息写死)
- 自定义 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;
}
}
- 配置 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 类 | 说明 |
---|---|---|
anon | AnonymousFilter | 匿名拦截,即不需要登录即可访间;一般用于静态资源过滤 |
authc | FormAuthenticationFilter | 基于表单的拦截,如果没有登录会跳到相应的登录页面登录 |
authcBasic | BasicHttpAuthenticationFilter | Basic HTTP 身份验证拦截 |
perms | PermissionsAuthorizationFilter | 权限授权拦截,验证用户是否拥有指定权限;例如:perms[用户列表] |
port | PortFilter | 端口拦截 |
rest | HttpMethodPermissionFilter | rest风格拦截,自动根据请求方法构建权限字符串 |
roles | RolesAuthorizationFilter | 角色授权拦截,验证用户是否拥有所有角色; |
ssl | SslFilter | sSL拦截,只有请求协议是 https才能通过: |
user | UserFilter | 用户拦截,用户已经身份验证/记住我登录的都可 |
logout | LogoutFilter | 退出拦截 |
页面标签权限控制
- pom.xml 添加依赖
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
- ShiroConfig.java 配置类中添加配置
@Bean(name = "shiroDialect")
public ShiroDialect shiroDialect(){//thymeleaf 页面上使用 shiro 标签
return new ShiroDialect ();
}
- 页面头部导入标签库
<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">
- 使用标签
<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;
}
}