一、概要
授权也叫访问控制,即在应用中控制谁能访问哪些资源(如访问页面/编辑数据/页面操作等)。在授权中需了解的几个关键对象:主体(Subject)、资源(Resource)、权限(Permission)、角色(Role)。
- 主体:即访问应用的用户,在Shiro中使用Subject代表该用户。用户只有授权后才允许访问相应的资源。
- 资源:在应用中用户可以访问的任何东西,比如访问界面、查看/编辑某些数据、访问某个业务方法、打印文本等等都是资源。用户只要授权后才能访问。
-
权限:安全策略中的原子授权单位,通过权限我们可以表示在应用中用户有没有操作某个资源的权力。即权限表示在应用中用户能不能访问某个资源,如:
访问用户列表页面
查看/新增/修改/删除用户数据(即很多时候都是CRUD(增查改删)式权限控制)。打印文档等等
如上可以看出,权限代表了用户有没有操作某个资源的权利,即反映在某个资源上的操作允不允许,不反映谁去执行这个操作。所以后续还需要把权限赋予给用户,即定义哪个用户允许在某个资源上做什么操作(权限),Shiro不会去做这件事情,而是由实现人员提供。
Shiro支持粗粒度权限(如用户模块的所有权限)和细粒度权限(操作某个用户的权限,即实例级别的) - 角色:角色代表了操作集合,可以理解为权限的集合,一般情况下我们会赋予用户角色而不是权限,即这样用户可以拥有一组权限,赋予权限时比较方便。典型的如:项目经理、技术总监、CTO、开发工程师等都是角色,不同的角色拥有一组不同的权限。
- 隐式角色:即直接通过角色来验证用户有没有操作权限,如在应用中CTO、技术总监、开发工程师可以使用打印机,假设某天不允许开发工程师使用打印机,此时需要从应用中删除相应代码;再如在应用中CTO、技术总监可以查看用户、查看权限;突然有一天不允许技术总监查看用户、查看权限了,需要在相关代码中把技术总监角色从判断逻辑中删除掉;即粒度是以角色为单位进行访问控制的,粒度较粗;如果进行修改可能造成多处代码修改。
- 显示角色:在程序中通过权限控制谁能访问某个资源,角色聚合一组权限集合;这样假设哪个角色不能访问某个资源,只需要从角色代表的权限集合中移除即可;无须修改多处代码;即粒度是以资源/实例为单位的;粒度较细。
二、Shiro 的授权方式
Shiro支持三种方式的授权:编程式、注解式、JSP/GSP标签
注解方式 (常用)
通过在执行的Java方法上放置相应的注解完成,没有权限将抛出相应的异常;
@RequiresRoles("admin")
public void index() {
//有权限
}
编程方式(了解)
通过hasRole写 if/else 授权代码块完成
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole(“admin”)) {
//有权限
} else {
//无权限
}
// 或者
if (subject.isPermitted("user:create")) {
log.info(subject.getPrincipal()+ ":" + "可以创建用户");
}
JSP/GSP 标签(了解)
在 JSP/GSP页面中使用相应的标签
<shiro:hasRole name="admin">
<!— 有权限 —>
</shiro:hasRole>
三、注解控制鉴权授权
注解 | 功能 |
---|---|
@RequiresGuest | 只有游客可以访问 |
@RequiresAuthentication | 需要登录才能访问 |
@RequiresUser | 已登录的用户或“记住我”的用户能访问 |
@RequiresRoles | 已登录的用户需具有指定的角色才能访问 |
@RequiresPermissions | 已登录的用户需具有指定的权限才能访问 |
四、步骤
1、配置shiro注解
需要注意的时候如果使用RequiresPermissions等注解 访问的时候出现404
@Bean
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
/**
* setUsePrefix(false)用于解决一个奇怪的bug。在引入spring aop的情况下。
* 在@Controller注解的类的方法中加入@RequiresRole等shiro注解,导致返回404。
*/
defaultAdvisorAutoProxyCreator.setUsePrefix(true);
return defaultAdvisorAutoProxyCreator;
}
/**
* Shiro生命周期处理器
*/
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
过滤器
@Bean
public ShiroFilterChainDefinition filterChainDefinition() {
//登录接口不需要认证就能访问
DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition();
// 表示不需要授权 (anon 表示访问登录不需要认证)
definition.addPathDefinition("/login", "anon");
definition.addPathDefinition("/register", "anon");
definition.addPathDefinition("/druid/**", "anon");
definition.addPathDefinition("/index", "anon");
// 用户登出的操作
definition.addPathDefinition("/logout", "logout");
// anonc 表示所有的api开头的url地址必须登录 403
definition.addPathDefinition("/api/**", "anonc");
definition.addPathDefinition("/**", "anonc");
definition.addPathDefinition("/admin/**", "anonc");
return definition;
}
2、自定义授权
public class UserRealm extends AuthorizingRealm {
@Resource
RolePermissionMapper rolePermissionMapper;
/**
* 授权
* 权限表的设计
* 使用权限
* 注解
*
*
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取的是通过认证 simpleAuthenticationInfo对象的第一个参数 如果第一个参数是对象 则可以墙砖成对象,如果是String类型
User user = (User) principalCollection.getPrimaryPrincipal();
// 通过用户信息查询角色表 权限表
Integer uid = user.getUid();
List<RoleDto> roleList = rolePermissionMapper.selectRolesByUserId(uid);
// 角色集合
Set<String> roles = new HashSet<>();
// 权限集合
List<String> permissions = new ArrayList<>();
for (RoleDto role : roleList) {
roles.add(role.getRoleName());
for (PermissionDto permission : role.getPermissions()) {
permissions.add(permission.getPerName());
}
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setRoles(roles);
info.addStringPermissions(permissions);
return info;
}
}
在控制层中
@RestController
public class TestRoleController {
@GetMapping("/")
public String index() {
return "不需要热河权限";
}
// 只有admin角色才能访问的该接口
@GetMapping("/role/admin")
@RequiresRoles(value = {"admin", "user"}, logical = Logical.OR)
public String testAdmin() {
return "只有admin角色才能访问";
}
@GetMapping("/per/create")
@RequiresPermissions("user:create")
public String testPermission() {
return "创建用户信息的权限";
}
}
数据库表
create table sys_permission
(
per_id int auto_increment comment '主键'
primary key,
per_name varchar(128) not null comment '权限名称',
description varchar(255) null comment '权限说明',
is_del int default 0 null comment '是否启用',
constraint per_name
unique (per_name)
)
comment '权限表';
create table sys_role
(
role_id int auto_increment comment '主键'
primary key,
role_name varchar(128) not null comment '角色的名称',
description varchar(255) null comment '角色说明',
is_del int default 0 null comment '是否启用',
constraint role_name
unique (role_name)
)
comment '权限表';
create table sys_role_permission
(
rpid int auto_increment
primary key,
per_id int not null comment '权限ID',
role_id int not null comment '角色ID'
)
comment '角色权限表';
create table sys_user
(
uid int auto_increment
primary key,
username varchar(20) not null,
password varchar(128) not null,
status int default 0 null,
constraint username
unique (username)
)
comment '用户表';
create table sys_user_role
(
id int auto_increment
primary key,
uid int not null comment '用户ID',
role_id int not null comment '角色ID'
)
comment '用户角色表';
工具类
public class ShiroUtils {
/**
* 循环次数
*/
public final static int HASH_ITERATIONS = 1024;
public static String sha256(String password, String salt) {
return new SimpleHash(Sha256Hash.ALGORITHM_NAME, password, salt, HASH_ITERATIONS).toString();
}
// 获取一个测试账号 admin
public static void main(String[] args) {
// 3743a4c09a17e6f2829febd09ca54e627810001cf255ddcae9dabd288a949c4a
System.out.println(sha256("admin", "123"));
}
/**
* 获取会话
*/
public static Session getSession() {
return SecurityUtils.getSubject().getSession();
}
/**
* Subject:主体,代表了当前“用户”
*/
public static Subject getSubject() {
return SecurityUtils.getSubject();
}
public static User getUser() {
return (User) SecurityUtils.getSubject().getPrincipal();
}
public static Integer getUserId() {
return getUser().getUid();
}
public static void setSessionAttribute(Object key, Object value) {
getSession().setAttribute(key, value);
}
public static Object getSessionAttribute(Object key) {
return getSession().getAttribute(key);
}
public static boolean isLogin() {
return SecurityUtils.getSubject().getPrincipal() != null;
}
public static void logout() {
SecurityUtils.getSubject().logout();
}
}
3、自定义权限异常提示
@RestControllerAdvice
public class ShiroException {
@ExceptionHandler(AuthorizationException.class)
public String authorizationException (){
return "抱歉您没有权限访问该内容!";
}
@ExceptionHandler(Exception.class)
public String handleException(Exception e){
return "系统异常!";
}
}
附、常见的API
1、判断当前用户是否拥有角色
-
常用方法
Subject方法 描述 hasRole(String roleName) 当用户拥有指定角色时,返回true hasRoles(List<String> roleNames) 按照列表顺序返回相应的一个boolean值数组 hasAllRoles(Collection<String> roleNames) 如果用户拥有所有指定角色时,返回true -
示例代码
if (currentUser.hasRole("admin")) { log.info("角色:" + "admin"); }
2、判断当前用户是否拥有权限
-
常用API
Subject方法 描述 checkRole(String roleName) 断言用户是否拥有指定角色 checkRoles(Collection<String> roleNames) 断言用户是否拥有所有指定角色 checkRoles(String... roleNames) 对上一方法的方法重载 -
示例代码
//判断用户是否有权限 if (currentUser.isPermitted("user:create")) { log.info(currentUser.getPrincipal()+ ":" + "可以创建用户"); }
3、过滤器
Filter | 说明 | 对应的烂机器 |
---|---|---|
anon(常用) | 无参,开放权限,可以理解为匿名用户或游客 | org.apache.shiro.web.filter.authc.AnonymousFilter |
authc(常用) | 无参,需要认证 | org.apache.shiro.web.filter.authc.FormAuthenticationFilter |
logout(固定) | 无参,注销,执行后会直接跳转到shiroFilterFactoryBean.setLoginUrl(); 设置的 url |
org.apache.shiro.web.filter.authz.PortFilter |
authcBasic | 无参,表示 httpBasic 认证 | org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter |
user | 无参,表示必须存在用户,当登入操作时不做检查 | org.apache.shiro.web.filter.authc.UserFilter |
ssl | 无参,表示安全的URL请求,协议为 https | org.apache.shiro.web.filter.authz.SslFilter |
perms[user] | 参数可写多个,表示需要某个或某些权限才能通过,多个参数时写 perms[“user, admin”],当有多个参数时必须每个参数都通过才算通过 | org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter |
roles[admin] | 参数可写多个,表示是某个或某些角色才能通过,多个参数时写 roles[“admin,user”],当有多个参数时必须每个参数都通过才算通过 | org.apache.shiro.web.filter.authz.RolesAuthorizationFilter |
rest[user] | 根据请求的方法,相当于 perms[user:method],其中 method 为 post,get,delete 等 | org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter |
port[8081] | 当请求的URL端口不是8081时,跳转到schemal://serverName:8081?queryString 其中 schmal 是协议 http 或 https 等等,serverName 是你访问的 Host,8081 是 Port 端口,queryString 是你访问的 URL 里的 ? 后面的参数 | org.apache.shiro.web.filter.authz.PortFilter |
网友评论