shiro框架三:基于URL粗粒度的用户授权

上篇播客我们写到了用户登录后才能进入其他页面,这篇播客我们要自定义realm实现用户授权功能。

实现功能:将test1.html、test2.html、test3.html分别赋予不同的角色和权限,只有拥有相应的角色权限的用户才有权访问该页面否则无权访问,跳到权限不足页面unauthorized.html

紧接着上一篇,直接上代码
shiro配置文件中添加这3行配置

   /admin/sys/shiro/test2.html* = perms[test1]  <!--拥有test1权限的人才能访问-->
   /admin/sys/shiro/test3.html* = perms[test2]  <!--拥有test2权限的人才能访问-->
   /admin/sys/shiro/test1.html* = roles[base]   <!--拥有base角色的人才可访问-->

shiro全文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                            http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
                            http://www.springframework.org/schema/context
                            http://www.springframework.org/schema/context/spring-context-4.0.xsd
                            http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
                         ">

   <!--<bean id="myRealm" class="com.jgs.shirourl.realm.MyRealm"></bean>-->
    <!--安全管理器-->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm"  ref="myRealm"></property>
    </bean>
    <!-- 配置Shiro核心Filter  -->
    <bean id="shiroFilter"
          class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <!-- 安全管理器 -->
        <property name="securityManager" ref="securityManager" />
        <!-- 未认证,跳转到哪个页面  -->
        <property name="loginUrl" value="/login.html" />
        <!-- 登录页面页面 -->
        <property name="successUrl" value="/index.html" />
        <!-- 认证后,没有权限跳转页面 -->
        <property name="unauthorizedUrl" value="/unauthorized.html" />
        <!-- shiro URL控制过滤器规则  -->
        <!--这里配置过后,所有的请求都经过shiro,然后也只有一下配置了,不登录可以问的才能访问,其他(/**)的都需要登录后才能访问-->
        <property name="filterChainDefinitions">
            <value>
                /login.html* = anon   <!--*表示访问页面的同时,允许传递参数-->
                /login.js = anon
                <!--这里要释放css、和js-->
                /admin/sys/libs/** = anon    <!--两个**表示该文件夹以及该文件夹下的所有子文件夹 -->
                <!--还要释放,登录的接口-->
                /admin/main/user/login.do* = anon
                /admin/sys/shiro/test2.html* = perms[test1]  <!--拥有test1权限的人才能访问-->
                /admin/sys/shiro/test3.html* = perms[test2]  <!--拥有test2权限的人才能访问-->
                /admin/sys/shiro/test1.html* = roles[base]   <!--拥有base角色的人才可访问-->
                /** = authc    <!--登录后才能访问-->
            </value>
            <!--
            anon  未认证可以访问
            authc  认证后可以访问
            perms 需要特定权限才能访问
            roles 需要特定角色才能访问
            user 需要特定用户才能访问
            port  需要特定端口才能访问
            reset  根据指定 HTTP 请求访问才能访问
            -->
        </property>
    </bean>

    <!--shiro后处理器-->
    <bean id="lifecycleBeanPostProcessor"
          class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

</beans>

项目页面的目录结构
这里写图片描述

配置权限不足的页面unauthorized.html,就一句话

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>权限不足</h1>
</body>
</html>

spring-shiro.xml

 <!-- 认证后,没有权限跳转到该页面 -->
        <property name="unauthorizedUrl" value="/unauthorized.html" />

在用户登录成功后跳到的index.html页面中添加一下代码,就是两个链接(这两个链接的有相应的权限和角色才能访问),让用户点击跳到该页面,但是用户没有权限,所以此时会跳到权限不足的页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
 我是首页面
 <a href="/admin/sys/shiro/test1.html">test1</a>
 <!--这里的测试页面的路径,要说一下,我们的项目一启动是直接访问login.html这个页面的,当然是我shiro配置好的
    未登录就跳到该页面,注意浏览器的访问路径
    http://localhost:9600/login.html;
    该页面是直接在webapp目录下的,
    它前面没有任何的路径,所以我们在访问其他页面的时候,直接以webapp为根,例如访问test1.html
     <a href="/admin/sys/shiro/test1.html">test1</a>
    这里还要说一下,我web.xml文件中的springmvc配置了/admin/main的访问路径,也就是说,它只拦截
    springmvc的请求,即,我们在发送.do请求的时候是要加上/admin/main的
 -->
 <a href="/admin/sys/shiro/test2.html">test2</a>
 <a href="/admin/sys/shiro/test3.html">test2</a>
</body>
</html>

运行结果
这里写图片描述

这里写图片描述
其实这里就是登陆之后,我们再访问test1.html和test2.html的时候,它就会进入到我们自定义的realm里去指定授权的方法
///
这里没有问题了,接下来就是给用户授权了。

其实大致的思路是比价简单的,代码也很少。

1、用户登录,进入到我们自定义的realm里,然后执行doGetAuthenticationInfo的认证管理的方法
2、如果用户登录成功,我们在doGetAuthenticationInfo方法中返回一个return new SimpleAuthenticationInfo(user,user.getPassword(),getName());对象,该对象的第一个参数,就是登录成功的用户的信息
3、然后,程序会调用我们自定义的realm类中的doGetAuthorizationInfo方法,进行授权
4、授权的方式
        4.1、在doGetAuthenticationInfo(认证)方法中:然后我们的认证方法返回了一个SimpleAuthenticationInfo对象,该对象会将登录成功的用户信息再放到subject中。于是我们通过SecurityUtils.getSubject();的到subject,再通过subject.getPrincipal得到用户的信息。
        4.2、通过用户的信息去查询数据中用户的所有角色,然后添加到角色里
        4.3、通过用户的信息去查询数据中用户的所有权限,然后添加到权限里
        4.4、返回AuthorizationInfo
        4.5、如果用户有权限访问页面,则跳到相应的页面,否则提示权限不足

要给用户授权首先的先要有数据表
我这里创建了5张表

t_permission  // 权限表
t_role        // 角色表
t_role_permission  // 角色权限表 :角色与权限为多对多的关系
t_user        // 用户表
t_user_role   // 用户角色表  这里的用户与角色也是一对多的关系,就比如超级管理员就同时用户管理员的角色权限   

各个表的构造
注:权限表和角色表中的keyword即为角色和权限的关键字,也就是在配置文件中配置的角色和权限的名称
t_user
user表

t_role
这里写图片描述

t_user_role
这里写图片描述

t_permission
这里写图片描述

t_role_permission
这里写图片描述

sql语句

// 根据用户id查询出权限:右外连接查询
// 注1:如果这里你的左表是t_role 而右表是t_user_role ,那么不管你的ur.user_id是否存在,它都会将t_role表中的所有结果查询出来
// 原因:左向外联接的结果集包括  LEFT OUTER子句中指定的左表的所有行,而不仅仅是联接列所匹配的行。如果左表的某行在右表中没有匹配行,则在相关联的结果集行中右表的所有选择列表列均为空值。
// 注2:mybatis中返回的结果如果是list集合,那么返回值类型为集合中的元素的类型
 <select id="selectPermissionByUserId" parameterType="java.lang.Integer"  resultType="com.jgs.shirourl.entity.TPermission">
    SELECT * FROM t_permission p WHERE p.id
    IN(SELECT rp.permission_id FROM t_role_permission rp WHERE rp.permission_id
    IN(SELECT r.id FROM t_role r RIGHT JOIN t_user_role ur ON ur.user_id =  #{userId,jdbcType=BIGINT}))
  </select>
// 也可以用内查询
SELECT * FROM t_permission p WHERE p.id
    IN(SELECT rp.permission_id FROM t_role_permission rp WHERE rp.permission_id
    IN(SELECT r.id FROM t_role r,t_user_role ur WHERE ur.user_id =  #{userId,jdbcType=BIGINT}))
// 根据用户id查询出角色
 <select id="selectRoleByUerId"  parameterType="java.lang.Integer" resultType="com.jgs.shirourl.entity.TRole">
    SELECT * FROM t_role r WHERE r.id IN(SELECT ur.role_id FROM t_user_role ur ,t_user u WHERE ur.user_id = #{userId,jdbcType=BIGINT})
  </select>

service层

 public List<TRole> selectRoleByUser(TUser user){

        List<TRole> roleList = roleMapper.selectRoleByUerId(user.getId());
        return roleList;
 }

 public List<TPermission> selectPermissionByUserId(Integer userId) {
        List<TPermission> list = permissionMapper.selectPermissionByUserId(userId);
        return list;
    }

自定义realm类的授权方法

package com.jgs.shirourl.realm;

import com.jgs.shirourl.entity.ShiroUser;
import com.jgs.shirourl.entity.TPermission;
import com.jgs.shirourl.entity.TRole;
import com.jgs.shirourl.entity.TUser;
import com.jgs.shirourl.service.impl.*;
import org.apache.shiro.SecurityUtils;
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 org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import java.util.List;

@Component("myRealm") // 这里起的名称在配置文件中要用到
public class MyRealm  extends AuthorizingRealm{


    // 自定义realm 实现 安全数据 连接

    @Autowired
    public TUserServiceImpl userService;

    @Autowired
    public TuserroleServiceImpl userRoleService;

    @Autowired
    public PermissionServiceImpl permissionService;


    // 授权
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("shiro 授权管理...");

        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        // 根据当前用户查询角色和权限
        Subject subject = SecurityUtils.getSubject();
        TUser user = (TUser) subject.getPrincipal();// 得到从数据库查到的用户,即认证管理中返回的用户,既然返回那就是已经登录成功的用户

        // 调用业务层,查询角色
        List<TRole> roleList = userRoleService.selectRoleByUser(user);
        // 将用户所关联的所有角色添加到该用户的令牌??
        for(TRole role:roleList){
            System.out.println("角色"+role.getKeyword());
            authorizationInfo.addRole(role.getKeyword()); // keyword就是我们定义的角色的名字
        }
        // 同理我们再查询出所有的权限,将所有的全新再添加到用户的令牌中
        List<TPermission> permissionList = permissionService.selectPermissionByUserId(user.getId());
        if(permissionList.size() >0 && permissionList!= null){
            for(TPermission permission :permissionList){
                System.out.println("权限"+permission.getKeyword());
                authorizationInfo.addStringPermission(permission.getKeyword());
            }
        }
        return authorizationInfo;
    }

    // 认证
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("shiro 认证管理...");

        // 转换token
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;


        // 根据用户名查询用户信息
       // ShiroUser shiroUser = userService.selectByUserName(usernamePasswordToken.getUsername());
        TUser user = userService.selectByUsername(usernamePasswordToken.getUsername());
        if(user == null){
            // 用户不存在
            // 参数1:期望登录后,保存在subject中的信息
            // 参数2:如果返回null,说明用户不存在,报用户名
            // 参数3:realm名
            return null;
        }else{
            // 用户存在
            // 返回用户密码时,securityManager会自动比较返回的密码和用户输入的密码是否一致
            // 如果密码一致,则提示登录成功,如果密码不一致则报密码错误的异常
            return new SimpleAuthenticationInfo(user,user.getPassword(),getName());
        }

    }
}

运行结果:张三这个账号没有任何的权限和角色,于是张三就不能访问test1、2、3中任何的页面
这里写图片描述
这里写图片描述
这里写图片描述

李四登录
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

其实如果只是显示页面的菜单,我们可以自己动手实现,不用shiro框架,数据库的表和我上面定义的一样,然后将菜单添加到权限表里,当用户登录的时候,就查询该用户权限下的菜单,并显示,于是不同的权限的用户,登录后显示的菜单就不同了。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值