SpringBoot快速整合Shiro权限框架

Shiro是权限控制框架

权限控制向来是重要且麻烦的,不仅要web安全,还要有密码学,还要易使用。

Shiro简单好用

比如一般的开源项目都是使用的Shiro,学习的时候可以抓取一些GitHub上面的开源软件,然后查看他们的编码和使用技术,以及技术选型,比如若依这个项目就使用了Shiro权限控制。

我们干什么?(项目起因)

之前做了一个密码记事本,基本完成了功能。

但是没有权限认证,导致可能谁都可以删除我的密码,这样我的密码没有泄露的安全,但是有遗失的安全。

所以我需要加上权限认证,对于删除和修改操作需要权限认证。

自己写一个简单的权限认证的也不是不行,但是学习优秀的技术和思想,使用和借鉴别人的思想和用法,避免重复造轮子。

  • 需求分析

我需要有一张权限表

  1. 用户名,标识用户,10个汉字应该够了
  2. 密码,用户密码,考虑到可能会有加密,20位吧
  3. 权限,先做一个简单权限

借鉴Linux权限控制,我打算使用数字的模式设计权限。

权限使用个位
0表示无权限可读,2表示修改权限
4表示删除权限

这样基本就能完成我们的需求了

  • 数据库准备
CREATE TABLE `hello_user_pr` (
`id`  int NOT NULL AUTO_INCREMENT COMMENT '索引编号' ,
`username`  varchar(10) NOT NULL COMMENT '用户名' ,
`password`  varchar(20) NOT NULL COMMENT '密码' ,
`grant`  tinyint(4) NOT NULL COMMENT '授予权限,0为初始用户' ,
`create_time`  int NOT NULL COMMENT '创建时间,时间戳' ,
PRIMARY KEY (`id`)
)
ENGINE=InnoDB
;

因为业务不复杂,我直接使用一张表作为权限的控制表。

  • 编写实体层
    就在原来的基础上entities包下面->新建classUserPermission
package com.hello.entities;

import lombok.Data;

/**
 * @author :QiuShi
 * @Date :2020/11/12 10:56
 * @Version 1.0
 */
@Data
public class UserPermission {
    private Integer id;
    private String username;
    private String password;
    private Short grant;
    private Integer create_time;
}

  • 编写dao层
    dao包->右键新建InterfaceUserPermissionDao
    ->类上加上@Mapper注解
我们需要的功能是
1. 查询用户名是否存在
2. 查询密码是否正确
3. 查询所在的分组(grant里面的数字)
4. 添加保存用户
5. 修改用户
6. 通过分组查询用户

最后编写内容如下:

package com.hello.dao;

import com.hello.entities.UserPermission;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;

/**
 * @author :QiuShi
 * @Date :2020/11/12 11:02
 * @Version 1.0
 */
@Mapper
public interface UserPermissionDao
{
    UserPermission queryByName(@Param("username") String username);
    int create(UserPermission userPermission);
    void updateById(UserPermission userPermission);
    List<UserPermission> queryByGrant(@Param("grant") Short grant);
}

然后编写mapper.xml文件
resources->mapper->右键新建UserPermissionMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hello.dao.UserPermissionDao">
    <sql id="selectUserPermission">
        select *
		from `hello_user_pr`
    </sql>

    <select id="queryByName" parameterType="String" resultType="UserPermission">
        <include refid="selectUserPermission"/>
        where `username`=#{username}
    </select>

    <insert id="create" parameterType="UserPermission" useGeneratedKeys="true" keyProperty="id">
        insert into `hello_user_pr`(`username`,`password`,`grant`,`create_time`)
        values(#{username},#{password},#{grant},#{create_time})
    </insert>

    <update id="updateById" parameterType="UserPermission">
        update `hello_user_pr` set `username`=#{username},`password`=#{password},`grant`=#{grant},`create_time`=#{create_time} where `id` = #{id};
    </update>

    <select id="queryByGrant" resultType="UserPermission" parameterType="Short">
        <include refid="selectUserPermission"/>
         where `grant` =#{grant}
    </select>

</mapper>
  • 编写service层
    实例代码如下
package com.hello.service;

import com.hello.entities.UserPermission;
import org.apache.ibatis.annotations.Param;

import java.util.List;

/**
 * @author :QiuShi
 * @Date :2020/11/12 11:32
 * @Version 1.0
 */
public interface UserPermissionService
{
    boolean checkUsername(String username);
    UserPermission queryByName(String username);
    int createUser(UserPermission userPermission);
    void updateUserById(UserPermission userPermission);
    List<UserPermission> queryByGrant(@Param("grant") Short grant);
}

service->impl->右键新建class UserPermissionServiceImpl
实例代码如下

package com.hello.service.impl;

import com.hello.dao.UserPermissionDao;
import com.hello.entities.UserPermission;
import com.hello.service.UserPermissionService;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;

/**
 * @author :QiuShi
 * @Date :2020/11/12 11:37
 * @Version 1.0
 */
@Service
public class UserPermissionServiceImpl implements UserPermissionService
{
    @Resource
    private UserPermissionDao userPermissionDao;

    @Override
    public boolean checkUsername(String username) {
        UserPermission userPermission = userPermissionDao.queryByName(username);
        if (userPermission!=null&&userPermission.getId()!=null){
            return true;
        }
        return false;
    }

    @Override
    public UserPermission queryByName(String username) {
        //非空
        if(StringUtils.isNotEmpty(username)){
            UserPermission user = userPermissionDao.queryByName(username);
            return user;
        }
        return null;
    }

    @Override
    public int createUser(UserPermission userPermission) {
        return userPermissionDao.create(userPermission);
    }

    @Override
    public void updateUserById(UserPermission userPermission) {
        if(userPermission!=null&&userPermission.getId()!=null){
            userPermissionDao.updateById(userPermission);
        }
    }

    @Override
    public List<UserPermission> queryByGrant(Short grant) {
        List<UserPermission> userPermissions = userPermissionDao.queryByGrant(grant);
        return userPermissions;
    }
}

使用Shiro整合项目

Shiro官网
官方文档
官网api查询

  • pom依赖
        <!--springboot - shiro-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.6.0</version>
        </dependency>
  • 编写shiro的配置类
    src->main->java->com.hello
    ->右键新建包,shiro用来单独作为shiro相关的配置
  1. 新建ShiroConfig配置类
package com.hello.shiro;

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;

/**
 * @author :QiuShi
 * @Date :2020/11/12 11:59
 * @Version 1.0
 */
@Configuration
public class ShiroConfig
{
    /**
     * @param securityManager
     * 1、权限过滤最重要的是过滤,所以创建ShiroFilterFactoryBean对象
     * @return
     */
    @Bean(name = "shiroFilterFactoryBean")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(SecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //过滤什么呢?所以我们设置SecurityManager管理,去创建一个
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        return shiroFilterFactoryBean;
    }


    /**
     * @return
     * 2、过滤器需要SecurityManager对象,创建这样的Bean给过滤器使用
     */
    @Bean(name = "securityManager")
    public SecurityManager getSecurityManager(){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //管理器就使用默认提供的就行,但是我们的规则呢?当然是在Realm里面
        securityManager.setRealm(new UserRealm());
        return securityManager;
    }


    /**
     * @return
     * 管理器里面添加进去我们的规则,所以我们创建这样的自定义规则对象给他,还是交给容器
     * 需要继承AuthorizingRealm这个抽象类
     * Realm是领域的含义
     */
    @Bean(name = "userRealm")
    public UserRealm getRealm(){
        return new UserRealm();
    }
}

注意:导入的SecurityManager包是
import org.apache.shiro.mgt.SecurityManager;不是Java自带的SecurityManager

  1. 新建自定义规则
    com.hello->shiro->右键新建classUserRealm,继承AuthorizingRealm抽象类,实现抽象方法。
package com.hello.shiro;

import com.hello.service.UserPermissionService;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import javax.annotation.Resource;

/**
 * @author :QiuShi
 * @Date :2020/11/12 12:09
 * @Version 1.0
 */
public class UserRealm extends AuthorizingRealm
{
    @Resource
    private UserPermissionService userPermissionService;

    /**
     * ->角色授权
     * -> Authorization表示授权
     * ->授权的请求都会到这里,比如角色权限。
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("角色授权,权限验证");
        return null;
    }

    /**
     * ->身份验证
     * ->Authentication表示身份验证
     * ->表示执行登录验证的操作
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("请求登录");
        return null;
    }
}

这样就创建好了最简单的权限控制请求了。

但是这样没有拦截任何请求,比如我需要除了主页,其他页面都需要登录,我们如何指定拦截请求呢?

我们改写下ShiroConfig里面的方法,getShiroFilterFactoryBean里面的内容如下:

   @Bean(name = "shiroFilterFactoryBean")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") SecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //过滤什么呢?所以我们设置SecurityManager管理,去创建一个
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        Map<String,String> filterMap=new LinkedHashMap<>();
        /*
        * 无需登录 anon
        * 需要登录 authc
        * 记住我   user ->记住我,下次直接访问
        * 指定角色 roles ->必须角色授权
        * 指定权限 perms ->必须权限
        * */
        filterMap.put("/","anon");
        filterMap.put("/pwnote/queryLimit","anon");
        filterMap.put("/toLogin","anon");
        filterMap.put("/**","authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
        return shiroFilterFactoryBean;
    }

这样就对于首页不拦截,登录操作不拦截,其余的请求都需要登录。

但是默认是去找login.jsp文件,我们可以指定我们的登录页面和失败页面。
再进行改造一下

    @Bean(name = "shiroFilterFactoryBean")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") SecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        //过滤什么呢?所以我们设置SecurityManager管理,去创建一个
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        Map<String,String> filterMap=new LinkedHashMap<>();
        /*
        * 无需登录 anon
        * 需要登录 authc
        * 记住我   user ->记住我,下次直接访问
        * 指定角色 roles ->必须角色授权
        * 指定权限 perms ->必须权限
        * */
        filterMap.put("/","anon");
        filterMap.put("/pwnote/queryLimit","anon");
        filterMap.put("/**","authc");

        /*设置默认的页面配置*/
        //设置登录页面,去登录
        shiroFilterFactoryBean.setLoginUrl("/login");
        //设置登录成功页面
        shiroFilterFactoryBean.setSuccessUrl("/");
        //设置未授权页面,也可以到登录页面
        shiroFilterFactoryBean.setUnauthorizedUrl("/login");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
        return shiroFilterFactoryBean;
    }

好了,这样拦截功能显示正常了,现在需要实现登录操作。

  • 处理登录请求

拦截可以了,但是对于拥有账号密码的应该放行。

前端编写请求提交表单到后台,后台的处理方式如下

    @PostMapping("/toLogin")
    public String toLogin(String username,String password,Model model){
        //使用Subject来提交登录验证
        Subject subject = SecurityUtils.getSubject();
        //创建token
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password);
        //执行登录方法
        try {
            subject.login(usernamePasswordToken);
            return "redirect:/pwnote/queryLimit";
        }catch (UnknownAccountException e){
            model.addAttribute("msg","用户不存在");
            return "login";
        }catch (IncorrectCredentialsException e){
            model.addAttribute("msg","密码错误");
            return "login";
        }
    }

步骤:
通过SecurityUtils获取Subject对象,执行登录token的操作
subject.login(usernamePasswordToken);
,token里面保存登录的值。

  • 编写判断登录的条件

那么这个login怎么判断的呢?就是我们自定义的UserRealm规则里面去了。

src->main->java->com.hello.shiro里面的类UserRealm前面不是实现了两个方法,一个是登录,一个是授权。我们修改登录方法。

    /**
     * ->身份验证
     * ->Authentication表示身份验证
     * ->表示执行登录验证的操作
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //System.out.println("请求登录");

        UsernamePasswordToken token=(UsernamePasswordToken) authenticationToken;
        //1、判断用户名是否存在
        UserPermission user = userPermissionService.queryByName(token.getUsername());
        if(user==null){
            //不存在直接返回null,就会抛出UnknownAccountException异常
            return null;
        }
        //存在则使用自带的实现类判断密码是否正确,我们传入正确的密码给认证器去做同样的处理后判断是否一致
        return new SimpleAuthenticationInfo("",user.getPassword(),"userPermissionRealm");
    }

这样就可以实现登录成功的,可以访问所有的页面了。

高级权限分配

我们可能需要更加精细的设置,对于敏感的操作我们需要进行保护,有权限的才能使用。

  1. 授权过滤
    有了基础的过滤器,添加高级的过滤器非常简单,只是需要在
    com.hello.shiro->ShiroConfig
    里面的ShiroFilterFactoryBean里面添加需要拦截的页面就可以了。
    就在之前的filterMap上面追加就好了。
    但是过滤的条件变成perms[],里面输入权限的值
    比如 root
        filterMap.put("/pwnote/deleteById/*","perms[root]");
        filterMap.put("/pwnote/Vupdate/*","perms[admin]");

这样就实现了拦截。

  1. 实现授权
    实现授权和登录授权一样,都在我们写的UserRealm实现的两个方法里面。一个是登录,一个是授权。

在授权的页面我们进行判断授权。

    /**
     * ->角色授权
     * -> Authorization表示授权
     * ->授权的请求都会到这里,比如角色权限。
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //System.out.println("角色授权,权限验证");
        //实现授权,获取当前用户
        Subject subject = SecurityUtils.getSubject();
        UserPermission user = (UserPermission) subject.getPrincipal();
        //权限信息保存
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        //判断是什么权限
        Short grant = user.getGrant();
        switch (grant){
            case 0:
                simpleAuthorizationInfo.addStringPermission("normal");
                break;
            case 2:
                simpleAuthorizationInfo.addStringPermission("admin");
                break;
            case 4:
                simpleAuthorizationInfo.addStringPermission("root");
                break;
            default:
                break;
        }
        return simpleAuthorizationInfo;
    }

这里的SecurityUtils.getSubject();要想获取到当前用户,需要登录的时候,把当前的对象传递进去。

同样在UserRealm类里面,返回值传递进去就可以了。

return new SimpleAuthenticationInfo(user,user.getPassword(),"userPermissionRealm");

好了,现在实现了权限控制了,

我们的权限将是0表示normal
2表示admin,可以执行修改操作
4表示root,可以执行删除的操作

然后对页面做一下美化和设置,功能做一下完备,比如限制修改的时候,只是限制了访问视图页面,实际上还要添加真正的提交修改操作才行

        filterMap.put("/pwnote/updateById","perms[admin]");

整合基本完成,想要去查看的可以点击连接查看

没有登录的只能查看首页,新增和检索的需要登录
提供测试,账号密码都是123。

Shiro整合:我的密码本2

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值