美文网首页
shiro springboot 授权

shiro springboot 授权

作者: 杨健kimyeung | 来源:发表于2020-08-25 13:19 被阅读0次

一、概要

授权也叫访问控制,即在应用中控制谁能访问哪些资源(如访问页面/编辑数据/页面操作等)。在授权中需了解的几个关键对象:主体(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、判断当前用户是否拥有角色

  1. 常用方法

    Subject方法 描述
    hasRole(String roleName) 当用户拥有指定角色时,返回true
    hasRoles(List<String> roleNames) 按照列表顺序返回相应的一个boolean值数组
    hasAllRoles(Collection<String> roleNames) 如果用户拥有所有指定角色时,返回true
  2. 示例代码

    if (currentUser.hasRole("admin")) {
      log.info("角色:" + "admin");
    }
    

2、判断当前用户是否拥有权限

  1. 常用API

    Subject方法 描述
    checkRole(String roleName) 断言用户是否拥有指定角色
    checkRoles(Collection<String> roleNames) 断言用户是否拥有所有指定角色
    checkRoles(String... roleNames) 对上一方法的方法重载
  2. 示例代码

    //判断用户是否有权限
    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

相关文章

网友评论

      本文标题:shiro springboot 授权

      本文链接:https://www.haomeiwen.com/subject/jszajktx.html