基于springboot搭建的shiro框架--快速入门篇

5 篇文章 0 订阅
2 篇文章 0 订阅

最近因公司业务需要,研究了一下shiro框架,虽然在研究过程中也遇到过不少问题,不过好在最后都解决了。于是在这里写一篇文章记录一下自己的使用过程。本篇文章只讨论shiro的快速使用,暂不考虑原理分析,后续可能会出源码分析的文章。
话不多说,我们马上进入正题,本篇文章采用springboot2.0,jdk1.8。
先贴一张项目结构
在这里插入图片描述

一、引入相关依赖并添加配置

首先我们在搭建好的springboot框架中引入maven依赖。

<dependency>
   <groupId>org.apache.shiro</groupId>
   <artifactId>shiro-spring</artifactId>
   <version>1.4.0</version>
</dependency>

当然除此之外还需要引入web相关依赖,如mysql,druid,mybatis等,同时我们在测试的过程中使用的时thymeleaf,相关的maven请自行添加。

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-thymeleaf</artifactId>
  <version>2.1.4.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.1.5.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>RELEASE</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter -->
<dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>druid-spring-boot-starter</artifactId>
   <version>1.1.17</version>
</dependency>

添加完成后我们在application中添加相关的配置,主要是druid,thymeleaf的相关配置。


server:
  port: 8099
spring:

  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driverClassName: com.mysql.jdbc.Driver
    platform: mysql
    #注意修改成自己的仓库名称哦
    url: jdbc:mysql://localhost:3306/candy?allowMultiQueries=true&useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8
    username: root
    password: root


    # 连接池设置
    druid:
      initial-size: 5
      min-idle: 5
      max-active: 20
      # 配置获取连接等待超时的时间
      max-wait: 60000
      # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
      time-between-eviction-runs-millis: 60000
      # 配置一个连接在池中最小生存的时间,单位是毫秒
      min-evictable-idle-time-millis: 300000
      # Oracle请使用select 1 from dual
      validation-query: SELECT 'x'
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      # 打开PSCache,并且指定每个连接上PSCache的大小
      pool-prepared-statements: true
      max-pool-prepared-statement-per-connection-size: 20
      # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
      filters: stat,wall,log4j2
      # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
      connection-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
      # 合并多个DruidDataSource的监控数据
      use-global-data-source-stat: true


  #thymeleaf模板引擎
  thymeleaf:
    mode: HTML5
    encoding: utf-8
    servlet:
      content-type: text/html
    #开发时关闭缓存
    cache: false
    suffix: .html
    prefix: classpath:/templates/ #这里表示页面存放的位置是resource目录下的templates文件夹。

mybatis:
  mapper-locations: classpath:mapper/*.xml #这里表示mapper的路径是resources文件下mapper文件夹下所有.xml结尾的文件,请修改成你自己的mapper存放的地址
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  #打印sql、参数、查询结果
  type-aliases-package: com.candy.shiro.entity  #这里修改成你自己的实体类的路径

该配置文件中需要修改的地方基本上已经标注出来了,请注意修改,不要直接粘过去使用哦。

二、创建数据库表

本次项目一共需要5张表
在这里插入图片描述
其中比较重要的是用户表,角色表,权限表。具体得sql如下,直接粘贴运行即可

-- 权限表 --
create table permission (
    pid int(11) not null auto_increment,
    name varchar(255) not null default '',
    url varchar(255) default '',
    primary key(pid)
) engine = innoDB default charset = utf8;

INSERT INTO permission VALUES( '1','add','');
INSERT INTO permission VALUES( '2','delete','');
INSERT INTO permission VALUES( '3','edit','');
INSERT INTO permission VALUES( '4','query','');
-- 用户表 --
create table user(
    uid int(11) not null auto_increment,
    username varchar(255) not null default '',
    password varchar(255) not null default '',
    primary key(uid)
) engine = InnoDB default charset = utf8;
INSERT INTO user VALUES( '1','admin','123');
INSERT INTO user VALUES( '2','demo','123');

-- 角色表 --
create table role(
    rid int(11) not null auto_increment,
    rname varchar(255) not null default '',
    primary key(rid)
) engine = InnoDB default charset = utf8;
INSERT INTO ROLE VALUES( '1','admin');
INSERT INTO ROLE VALUES( '2','customer');

-- 权限角色关系表 --
 create table permission_role(
    rid int(11) not null,
    pid int(11) not null,
    key idx_rid (rid),
    key id_pid (pid)
 ) engine = InnoDB default charset = utf8;
insert INTO permission_role VALUES('1','1');
insert INTO permission_role VALUES('1','2');
insert INTO permission_role VALUES('1','4');
insert INTO permission_role VALUES('1','3');
insert INTO permission_role VALUES('2','1');
insert INTO permission_role VALUES('2','4');

-- 用户角色关系表 --
CREATE TABLE user_role(
	uid int(11) not null,
	rid int(11) not null,
	key idx_uid (uid),
	key idx_rid (rid)
)ENGINE = INNODB default CHARSET = utf8;

INSERT INTO user_role VALUES(1,1);
INSERT INTO user_role VALUES(2,2);

三、生成项目结构,编写sql

这里我直接使用easy code直接生成了项目结构,非常好用,推荐大家使用。当然你也可以使用其它得代码自动生成工具,如果你得项目结构已经生成了,请继续向下看。
我们找到刚才生成的User实体类,在里面加上

  private Set<Role> roles = new HashSet<>();

并生成getter setter方法;同时在Role实体类中添加

private Set<Permission> permissions = new HashSet<>();

private Set<User> users = new HashSet<>();

并生成getter setter 方法
添加完成之后我们在UserDao中添加一个方法

    /**
     * @Author : qzx
     * @Description : //TODO 通过名字查询用户
     * @Date : 16:34 2019/6/13
     * @Param : [username]
     * @return : com.candy.shiro.entity.User
     **/
    User findByUserName(@Param("username") String username);

通过用户名查找对应的用户,添加完成后再UserMapper.xml中编写对应的sql
新建一个 resultMap

    <resultMap id="userMap2" type="com.candy.shiro.entity.User">
        <id property="uid" column="uid" jdbcType="INTEGER"/>
        <result property="username" column="username" jdbcType="VARCHAR"/>
        <result property="password" column="password" jdbcType="VARCHAR"/>
        <collection property="roles" ofType="com.candy.shiro.entity.Role">
            <id property="rid" column="rid"/>
            <result property="rname" column="rname"/>
            <collection property="permissions" ofType="com.candy.shiro.entity.Permission">
                <id column="pid" property="pid"/>
                <result property="name" column="name"/>
                <result property="url" column="url"/>
            </collection>
        </collection>
    </resultMap>

使我们在查询User的同时也会查到该User的角色和该角色拥有的权限。
接下来编写sql语句:

    <select id="findByUserName" parameterType="string" resultMap="userMap2">
        select u.*,r.*,p.*
        from user u
          inner join user_role ur on ur.uid = u.uid
          inner join role r on r.rid = ur.rid
          INNER JOIN  permission_role pr on pr.rid = r.rid
          INNER JOIN permission p on p.pid = pr.pid
        where u.username = #{username}
    </select>

连接查询出用户的角色和权限。
写完了这些别忘了在service层中调用这个方法哦。
这样我们就做完了前期的准备工作了,接下来开始进入重点!!!

四、编写shiro配置文件

简单的实现shiro做登录验证,我们只需要编写两个配置类,AuthRealm配置类和ShiroConfiguration配置类,其中AuthRealm的主要目的使从前端接收我们的登录用户的参数,并将参数发送给shiro框架的SimpleAuthorizationInfo类进行验证。ShiroConfiguration配置类主要是为了定义角色或权限可以访问的路径,以及一些基本的配置。
首先我们编写AuthRealm类

package com.candy.shiro.conf.shiro;

import com.candy.shiro.entity.Permission;
import com.candy.shiro.entity.Role;
import com.candy.shiro.entity.User;
import com.candy.shiro.service.UserService;
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.util.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.ArrayList;
import java.util.Set;

/**
 * @author :q'z'x
 * @ClassName :AuthRealm
 * @date : 2019/6/13 16:22
 * @description : TODO
 */
public class AuthRealm extends AuthorizingRealm {

    @Autowired
    UserService userService;


    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        /**
         * @Author : qzx
         * @Description : //TODO 从session里面获取用户,并根据该用户查询所在的角色和该角色的权限
         * @Date : 16:54 2019/6/13
         * @Param : [principalCollection]
         * @return : org.apache.shiro.authz.AuthorizationInfo
         **/
        User user = (User) principalCollection.fromRealm(this.getClass().getName()).iterator().next();
        ArrayList<String> permissionList = new ArrayList<>();
        ArrayList<String> roleNameList = new ArrayList<>();
        Set<Role> roleSet = user.getRoles();
        //遍历该用户的角色和权限,并存放到list中
        if(!CollectionUtils.isEmpty(roleSet)){
            for (Role role : roleSet){
                roleNameList.add(role.getRname());
                Set<Permission> permissionSet = role.getPermissions();
                if(!CollectionUtils.isEmpty(permissionSet)){
                    for(Permission permission : permissionSet){
                        permissionList.add(permission.getName());
                    }
                }
            }
        }
        //将角色和权限交给shiro处理
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addStringPermissions(permissionList);
        info.addRoles(roleNameList);
        return info;
    }
    //认证登录
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
        String username = usernamePasswordToken.getUsername();
        //这个接口改成自己的接口
        User user = userService.findByUserName(username);
        return new SimpleAuthenticationInfo(user,user.getPassword(),this.getClass().getName());
    }
}

这个配置类里有登录验证和授权验证。登录认证主要是把该用户的所有信息都查询到,主要还是看授权认证。授权认证需要把用户从session中获取,然后将用户的角色和该角色的权限全部遍历出来,交给shrio处理。
另一个配置类代码如下:

package com.candy.shiro.conf.shiro;

import org.apache.shiro.cache.MemoryConstrainedCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;

/**
 * @author :qzx
 * @ClassName :ShiroConfiguration
 * @date : 2019/6/13 17:32
 * @description : TODO
 */
@Configuration
public class ShiroConfiguration {

    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") SecurityManager manager) {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(manager);
        //登录
        bean.setLoginUrl("/login");
        //登录成功之后跳转
        bean.setSuccessUrl("/index");
        //没有请求访问的时候
        bean.setUnauthorizedUrl("/unauthorized");
        //以下是某些请求如何拦截
        LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        //DefaultFilter类里有关于authc,anon的相关信息,这是shiro默认的几个验证方法
        filterChainDefinitionMap.put("/index","authc");
        filterChainDefinitionMap.put("/login","anon");
        filterChainDefinitionMap.put("/loginUser","anon");
        //只允许角色为admin的用户访问
        filterChainDefinitionMap.put("/admin","roles[admin]");
        //表示具有edit的权限才可以访问
        filterChainDefinitionMap.put("/edit","perms[edit]");
        //这个必须放在最后面表示其它接口是不需要登录认证的
        filterChainDefinitionMap.put("/**","user");
        bean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        return bean;
    }



    @Bean("securityManager")
    public DefaultWebSecurityManager securityManager(@Qualifier("authRealm") AuthRealm authRealm){
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(authRealm);
        return manager;
    }

    @Bean("authRealm")
    public AuthRealm authRealm(@Qualifier("credentialMatcher") CredentialMatcher matcher){
        AuthRealm authRealm = new AuthRealm();
        authRealm.setCredentialsMatcher(matcher);
        //开启缓存
        authRealm.setCacheManager(new MemoryConstrainedCacheManager());
        return authRealm;
    }

    @Bean("credentialMatcher")
    public CredentialMatcher credentialMatcher(){
        return new CredentialMatcher();
    }
    /**
     * rememberMe cookie 效果是重开浏览器后无需重新登录
     *
     * @return SimpleCookie
     */
    private SimpleCookie rememberMeCookie() {
        // 设置 cookie 名称,对应 login.html 页面的 <input type="checkbox" name="rememberMe"/>
        SimpleCookie cookie = new SimpleCookie("rememberMe");
        // cookie.setSecure(true); // 只在 https中有效 注释掉 正常
        // 设置 cookie 的过期时间,单位为秒,这里为一天
        cookie.setMaxAge(2000);
        return cookie;
    }


    //shiro和Spring相关联
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager manager){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(manager);
        return authorizationAttributeSourceAdvisor;
    }

    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
        creator.setProxyTargetClass(true);
        return creator;
    }


}

shiroFilterFactoryBean是里面最重要的一个类了,每一步的意思我都已经加注释了,比较关键的就setFilterChainDefinitionMap里面的配置了,比如说filterChainDefinitionMap.put("/index",“authc”); 这句代码表示想要访问index这个路径的页面,则必须经过表单验证通过才可以。filterChainDefinitionMap.put("/admin",“roles[admin]”);表示只有角色admin才可以访问/admin这个路径。具体的可以进到DefaultFilter类里查看实现。
另外要注意的是filterChainDefinitionMap.put("/**",“user”);这行代码必须放在最后,不然会出问题的。
到这里为止我们的准备工作就差不多了,下面我们要写controller类了。

五、编写controller类和页面并进行测试

package com.candy.shiro.controller;

import com.candy.shiro.entity.User;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpSession;

/**
 * @author :qzx
 * @ClassName :TestController
 * @date : 2019/6/13 18:07
 * @description : TODO
 */
@Controller
public class TestController {

	
    @RequestMapping("/login")
    public String login() {
        return "login";
    }

    @RequestMapping("/index")
    public String index() {
        return "index";
    }

    @RequestMapping("/unauthorized")
    public String unauthorized(){
        return "unauthorized";
    }

    @RequestMapping("/admin")
    @ResponseBody
    public String admin() {
        return "admin success";
    }
    @RequestMapping("/edit")
    @ResponseBody
    public String edit() {
        return "edit success";
    }

    @RequestMapping("/logout")
    public String logout() {
        //取出当前验证的主体
        Subject subject = SecurityUtils.getSubject();
        if(subject != null){
        //退出登录
            subject.logout();
        }
        return "login";
    }

    @RequestMapping("/loginUser")
    public String loginUser(@RequestParam("username") String username, @RequestParam("password") String password, HttpSession session) {
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        Subject subject = SecurityUtils.getSubject();
        //shiro的认证逻辑
        try {
            subject.login(token);
            User user = (User) subject.getPrincipal();
            session.setAttribute("user", user);
            return "index/index";
        } catch (Exception e) {
            return "login";
        }
    }
}

controller类的代码很好理解,基本上不用解释,注意自己的路径,有可能和我的不一样。
接下来我们在templates下面写页面,页面结构参考我最开始发的那张结构图(如果想和我的路径一样的话)。
login的页面:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Login</title>
</head>
<body>
<h1>登录</h1>
<form action="/loginUser" method="post">
    <input type="text" name="username" /> <br>
    <input type="password" name="password" /><br>
    <input type="submit" value="提交" />
</form>
</body>
</html>

index的页面:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Login</title>
</head>
<body>
<h1>欢迎,您已登录成功!</h1>

</body>
</html>

到这里我们就可以启动一下进行测试了,启动项目,打开浏览器输入 http://localhost:8099/login 进入到登录页面(如果没有进行登录的话,访问其它接口会自动跳转到登录页面)。
在这里插入图片描述
输入账号密码(admin;123)会进入登录成功界面:
在这里插入图片描述
这是我们修改地址后缀为edit,点击访问,会访问到以下页面,说明访问成功了,admin拥有访问edit接口的权限。
在这里插入图片描述
接下来我们将后缀改为logout退出登录,重新输入账号密码(demo;123)点击访问登录成功后,将后缀改为edit,会出现如下假面
在这里插入图片描述
系统自动跳转到unauthorized接口,说明该用户没有权限访问edit(可以自己定义一个无权限访问的页面)。

六、结语

到这里为止,我们的shiro使用方法就都已经介绍完毕了,不知道小伙伴有没有学会呢?如果学会了使用方法,建议自己打断点走一走内部实现流程,通过源码分析来进一步加深对shiro框架的理解和操作。喜欢本篇文章或者有什么想问的欢迎在评论区留言。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值