在进行权限管理前,我们经常遇到的问题便是有些页面你直接用url就可以访问,不管你登录没登录,还有就是你在页面中跳转的时候你无法批判你是否有这个权限使用,当然这个时候大部分人所想的是在访问前判断一下用户有没有这个权限,当然这也是一种方法,但是,这种解决方式需要在我们测试好的代码上动刀,不仅凭空增加了工作量,而且大大增加了代码的风险,这里我大致讲解一下 shiro这个安全框架,我们用它来弄权限管理

权限管理1.1 什么是权限管理

基本上涉及到用户参与的系统都要进行权限管理,权限管理属于系统安全的范畴,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户可以访问而且只能访问自己被授权的资源。

权限管理包括用户身份认证和授权两部分,简称认证授权。对于需要访问控制的资源用户首先经过身份认证,认证通过后用户具有该资源的访问权限方可访问

1.2 用户身份认证

1.2.1 概念

身份认证,就是判断一个用户是否为合法用户的处理过程。最常用的简单身份认证方式是系统通过核对用户输入的用户名和口令,看其是否与系统中存储的该用户的用户名和口令一致,来判断用户身份是否正确。
最常见的就是系统登录需要账号,密码,手机的指纹解锁需要对应的指纹

1.2.2 用户名密码身份认证流程

java 权限rbac表设计 java权限管理shiro_java

1.2.3 关键对象

上边的流程图中需要理解以下关键对象:

Subject:主体

访问系统的用户,主体可以是用户、程序等,进行认证的都称为主体;

Principal:身份信息

是主体(subject)进行身份认证的标识,标识必须具有唯一性,如用户名、手机号、邮箱地址等,一个主体可以有多个身份,但是必须有一个主身份(Primary Principal)。

credential:凭证信息

是只有主体自己知道的安全信息,如密码、证书等。

1.3 授权

1.3.1 概念

授权,即访问控制,控制谁能访问哪些资源。主体进行身份认证后需要分配权限方可访问系统的资源,对于某些资源没有权限是无法访问的。

1.3.2 授权流程

下图中橙色为授权流程。

java 权限rbac表设计 java权限管理shiro_shiro_02

1.3.3 关键对象

授权可简单理解为who对what(which)进行How操作:

Who,即主体(Subject),主体需要访问系统中的资源。

What,即资源(Resource),如系统菜单、页面、按钮、类方法、系统商品信息等。资源包括资源类型和资源实例,比如商品信息为资源类型,类型为t01的商品为资源实例,编号为001的商品信息也属于资源实例。

How,权限/许可(Permission),规定了主体对资源的操作许可,权限离开资源没有意义,如用户查询权限、用户添加权限、某个类方法的调用权限、编号为001用户的修改权限等,通过权限可知主体对哪些资源都有哪些操作许可

java 权限rbac表设计 java权限管理shiro_java_03

1.4 基于url拦截

java 权限rbac表设计 java权限管理shiro_ci_04

Shiro过滤器&标签简介

判断 sesion中是否有 user 判断账户密码是否正确

(1)分析
需要过滤器控制 没有权限下访问链接
需要标签控制 没有权限下 界面元素的隐藏
》anon代表不认证也可以访问,通常对静态资源进行放行
》authc代表必须通过认证才可以访问,通常对动态资源(controller,jsp页面)进行拦截,如果用户没有认证,Shiro会自动跳转到login.jsp页面

Shiro登陆认证-判断session中的user

(1)过滤器
在项目中使用认证过滤器拦截资源(该拦截的拦截,该放行的放行)
(2)authc认证过滤器
必须认证才能访问,如果未认证跳登录页
(3)anon过滤器
放行,不需要拦截认证
(4)拦截路径问题

/* 
/**

applicationContext-shiro.xml

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <!--注入SecurityManager-->
        <property name="securityManager" ref="securityManager"/>
        <property name="filterChainDefinitions">
            <value>
                <!-- 如果请求提静态资源 anon 不要求session中有user,直接可以访问-->
                /css/**=anon
                /img/**=anon
                /plugins/**=anon
                <!-- 如果请求login.do方法 anon-->
                /system/user/login-shiro.do=anon
                <!-- 如果除以上之后的xxx.do 要求登录 authc查询session中是否有user-->
                /**=authc
            </value>
        </property>
        <!--如果 session就是没有user,表示未登录,页面转到login-shiro.jsp -->
        <property name="loginUrl" value="/login-shiro.jsp"/>

    </bean>

java 权限rbac表设计 java权限管理shiro_java_05

Shiro登录认证-用户密码判断

Shiro登陆认证三种结果

(1)正确
无异常
User user = (User)subject.getPrincipal()//获取user对象
(2)用户不存在
程序抛出UnknownAccountException异常
(3)用户密码出错
程序抛出IncorrectCredentialsException异常

Shiro登陆认证login方法

1) 获取Subject对象
Subject subject = SecurityUtils.getSubject();
(2)调用Subject的认证方法 : login 本质:AuthRealm的认证方法
subject.login(token);
(3)登录的结果
正确
用户不存在
用户密码出错

UserController

userController通地subject连接到securitymanager,再由securityManager去调用realm

//${path}/system/user/login-shiro.do
    @RequestMapping(path = "/login-shiro", method = {RequestMethod.GET, RequestMethod.POST})
    public String loginShiro(String email,String password) {
        //根据 email查询对应的用户
        l.info("loginShiro email " + email);
        l.info("loginShiro password " + password);
        //使用shiro框架进行认证  结果也是三种可能  正确 没有异常,用户不存在 UnknownAccountException,密码出错 IncorrectCredentialsException
        //本质是需要调用realm进行查找用户
        Subject subject = SecurityUtils.getSubject();//获取连接
        //1:先获取subject 表示对securitymanager连接
        //2:调用 securitymanager
        //3:再调用realm
        //难验信息
        UsernamePasswordToken token = new UsernamePasswordToken(email, password);//身份验证
        try {
            subject.login(token);//正确  --realm
            //正确
            l.info("正确");
            //保存用户信息
            //要求访问realm返回一个user对象
            User user = (User) subject.getPrincipal();//  --realm
            session.setAttribute("loginUser",user);
            //一个 Module对象 就是左侧栏上的一个菜单项
            List<Module> menus = iModuleService.findModulesByUser(user);
            session.setAttribute("menus",menus);
            l.info("login menus "+menus);
            //跳到主页
            return "redirect:/home/toMain.do";
        } catch (UnknownAccountException e) {//用户不存在
            e.printStackTrace();
            l.info("用户不存在");
            request.setAttribute("error","用户不存在");
            return "forward:/login-shiro.jsp";
        }catch (IncorrectCredentialsException e){//密码出错
            e.printStackTrace();
            l.info("密码不对");
            request.setAttribute("error","邮箱或者密码不对");
            return "forward:/login-shiro.jsp";
        }


    }

AuthRealm

//认证(登录账号密码)
    //subject.login(token);
    //参1 接受 subject.login(token); 方法的值  authenticationToken

    @Autowired
    IUserService userService;
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        l.info("AuthRealm doGetAuthenticationInfo 函数执行了");
        //响应login方法和getPrincipal方法的调用
        UsernamePasswordToken usernamePasswordToken= (UsernamePasswordToken) token;

        String email  = usernamePasswordToken.getUsername();
        l.info("doGetAuthenticationInfo --"+email);
        //调service读dao
        User user =  userService.findUserByEmail(email);
        l.info("doGetAuthenticationInfo --"+user);
        if (user == null) {
            //用户不存在
            return null;//-->系统会将null转成UnknownAccountException抛出
        } else {
            //用户存在的
            //AuthenticationInfo 返回给 User user = (User) subject.getPrincipal();
            /**
             * 参数一:principal,存放用户登录信息,subject.getPrincipal()获取
             * 参数二:数据库的密码
             * 参数三:realm的别名,只有在多个Realm的时候才会用,一般不用
             */
            AuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), "");
            return info;
        }
    }