springboot整和shiro安全框架的基本使用

1.shiro 简介

和springsecurity一样同属于安全框架负责用户信息认证和授权,但是更为轻量适合数据量不大的web,前后端分离

官网地址:http://shiro.apache.org/

是一款主流的 Java 安全框架,不依赖任何容器,可以运行在 Java SE 和 Java EE 项目中,它的主要作用是对访问系统的用户进行身份认证、授权、会话管理、加密等操作。

原生java api filter(过滤器) Interceptor(拦截器都可以实现) 但是当数据量较大时 不方便管理,shiro 操作模式:用户认证->分配角色->角色被赋予对应权限->操作数据
所以重点是为不同用户赋予不同角色,而不是直接赋予用户权限,更加安全便利
Shiro 就是用来解决安全管理的系统化框架。

2.核心组件

认识任何一个框架需要做到的第一步就是了解核心组件
用户、角色、权限

会给角色赋予权限,给用户赋予角色

 1.UsernamePasswordToken,Shiro 用来封装用户登录信息,使用用户的登录信息来创建令牌 Token。

2.SecurityManager,Shiro 的核心部分,负责安全认证和授权。

3、Suject,Shiro 的一个抽象概念,包含了用户信息,login,logout操作等

4、Realm,开发者自定义的模块,根据项目的需求,验证和授权的逻辑全部写在 Realm 中。

5、AuthenticationInfo,用户的角色信息集合,认证时使用。

6、AuthorzationInfo,角色的权限信息集合,授权时使用。

7、DefaultWebSecurityManager,安全管理器,开发者自定义的 Realm 需要注入到
DefaultWebSecurityManager 进行管理才能生效。

8、ShiroFilterFactoryBean,过滤器工厂,Shiro 的基本运行机制是开发者定制规则,Shiro
去执行,具体的执行操作就是由 ShiroFilterFactoryBean 创建的一个个 Filter 对象来完成。

Shiro 的运行机制如下图所示。
ra你好在这里插入图片描述用户在login页面写入个人信息 然后传入subject抽象对象 ,这是用户输入部分,代码后端部分是自定义realm安全模块然后SecurityManager 里面注入自定义安全模块,自定义安全模块分为2个小部分,authentication(用户认证)用于是实现认证部分,权限模块

3.具体实现步骤

一.pom文件导入依赖

<dependencies>
        <dependency><!--我是用的mp框架-->
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.2</version>
        </dependency>

        <dependency><!--我这里演示mvc页面开发 引入解析器渲染页面-->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency><!--数据库链接驱动-->
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency><!--简化开发-->
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency><!---springboot整合shiro-->
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.9.1</version>
        </dependency> 

二.配置文件

application.properties

   # 应用名称
spring.application.name=springbootshiro

# 应用服务 WEB 访问端口
server.port=8077

# THYMELEAF (ThymeleafAutoConfiguration)
# 开启模板缓存(默认值: true )
spring.thymeleaf.cache=true
# 检查模板是否存在,然后再呈现
spring.thymeleaf.check-template=true
# 检查模板位置是否正确(默认值 :true )
spring.thymeleaf.check-template-location=true
#Content-Type 的值(默认值: text/html )
spring.thymeleaf.content-type=text/html
# 开启 MVC Thymeleaf 视图解析(默认值: true )
spring.thymeleaf.enabled=true
# 模板编码
spring.thymeleaf.encoding=UTF-8
# 要被排除在解析之外的视图名称列表,⽤逗号分隔
spring.thymeleaf.excluded-view-names=
# 要运⽤于模板之上的模板模式。另⻅ StandardTemplate-ModeHandlers( 默认值: HTML5)
spring.thymeleaf.mode=HTML5
# 在构建 URL 时添加到视图名称前的前缀(默认值: classpath:/templates/ )
spring.thymeleaf.prefix=classpath:/templates/
# 在构建 URL 时添加到视图名称后的后缀(默认值: .html )
spring.thymeleaf.suffix=.html

yml

#cj 是sql是jdbc更新过后的版本
#配置数据源
spring:
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 222222
    url: jdbc:mysql://localhost:3306/crm?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8

mybatis-plus:
  global-config:
    db-config:
      type-aliases-package: com.springboot.springbootshiro.M1.entity
      #配置数据库实体对象的位置和mapper映射文件的位置
      # table-prefix: tbl_ #配置前缀这样就不需要全部写完表名了
      id-type: auto # 迎合数据库的自动增加策略  #mybatisplus 可以直接继承basemappeer可以直接对数据库经行操作
  configuration: #MP的日志信息
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapper-locations: classpath:/mappers/*.xml

三.项目目录以及各文件

在这里插入图片描述#### 首先数据库信息如下
account
在这里插入图片描述
用户对应实体类
account

import java.lang.annotation.ElementType;

@Data
@TableName("account")
public class account {
    @TableId(value = "id",type = IdType.AUTO)
     private int id;
    @TableField("username")
    private String username;
    @TableField("password")
    private String password;
    @TableField("perms")
    private String perms;
    @TableField("rule")
    private String rule;
}
数据交互层以及service

mapper层

@Mapper
/**
 * 接口不会注入ioc的 实现类才可以 mapper层和repostiroy是持久层
 * 本身mapper是动态生成的2
 */
public interface accountmapper extends BaseMapper<account> {


}

service以及实现类

import com.baomidou.mybatisplus.extension.service.IService;
import com.springboot.springbootshiro.M1.entity.account;

public interface accountservice extends IService<account>{
    public  account finduser(String username);
}

实现类

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.springboot.springbootshiro.M1.entity.account;
import com.springboot.springbootshiro.M1.mappers.accountmapper;
import com.springboot.springbootshiro.M1.service.accountservice;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class accountserviceImpl  extends ServiceImpl<accountmapper, account> implements accountservice {
    @Autowired
    accountmapper mapper;
    @Override
    public account finduser(String username) {
        QueryWrapper<account> queryWrapper = new QueryWrapper<account>();
        //根据用户输入账号查询
        queryWrapper.eq("username",username);
        account account = mapper.selectOne(queryWrapper);
//todo 先判断是否纯在用户名
        if (account!=null){
            return  account;

        }
        else return null;
    }
}
自定义realm安全模块(处理逻辑)

通过上述操作 以及可以完全登录的数据交互
然后开始自定义安全模块realm的编写

import com.springboot.springbootshiro.M1.entity.account;
import com.springboot.springbootshiro.M1.service.accountservice;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthenticatingRealm;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * 开发者自定义的模块,根据项目的需求,自定义验证和授权的逻辑全部写在 Realm 中。
 * 然后注入DefaultWebSecurityManager
 * 默认继承AuthorizingRealm俩个抽象方法 一个authenticationinfo用户信息
 * authorise 授权信息

 */
public class AccountRealm extends AuthorizingRealm {
    @Autowired
    com.springboot.springbootshiro.M1.service.accountservice accountservice;
    // TODO: 2022/10/2  权限信息
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        /**
         * 开始授权 从登陆用户信息上获取权限 现在还没用登录 所以先编写认证模块
         */

        return null;
    }


    //todo 做认证信息 这里抛出用户信息异常
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
       //todo 此时用户信息密码已经转入封装到    authenticationToken 然后强转为账户密码令牌
        //获取用户信息令牌
        UsernamePasswordToken  token=(UsernamePasswordToken)authenticationToken;
        account finduser =    accountservice.finduser(token.getUsername());//todo 把token中封装的信息传入 service查找用户名是否存在
        if (finduser!=null){
            //todo 账户验证后验证密码  AuthenticationInfo认证信息
            //三个参数 查询到的对象 ,密码,
            /**
             * finduser,finduser.getPassword() token中密码和查询到的正确密码经行对比
             * getNamecash 异常名
             */
            return  new   SimpleAuthenticationInfo(finduser,finduser.getPassword(),getName());
        }

        return null;
    }
}
shiro的配置文件 将自定义模块注入

html页面在resouce目录的template下/自动解析index为首页(默认规则)

import com.springboot.springbootshiro.M1.realm.AccountRealm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

@Configuration
/**
 * 相当于spring中的xml配置文件 然后配置bean
 * 每个方法就相当于一个标签对
 * 所以需要返回值
 * 方法名名是就是bean的名字
 */
public class shirocong {
    //第一步注入自定义的安全模块
    @Bean
    public AccountRealm accountRealm(){
        //todo 注入ioc容器
        return new AccountRealm();
    }

    /**
     * DefaultWebSecurityManager,安全管理器,开发者自定义的 Realm 需要注入到 DefaultWebSecurityManager 进行管理才能生效。
     *  第二步  把自己的写的安全模块注入ioc的默认的模块
     * @Qualifier (因为本质都是过滤器) 从ioc装配 注入默认模块
     */
    @Bean
    public DefaultWebSecurityManager iocdefaultsecuritymaneger(@Qualifier("accountRealm") AccountRealm accountRealm){
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        //todo 向默认管理器注入自己的安全模块
     defaultWebSecurityManager.setRealm(accountRealm);
        return defaultWebSecurityManager;
    }

    /**
     * //第三步 ShiroFilterFactoryBean 注入shirofilter 过滤器bean生产出来
     * 最后将整个默认模块生产出来
     * @param securityManager
     * @return
     */
    /**
     * 访问权限如下:
     *
     * 1、必须登录才能访问 main.html
     *
     * 2、当前用户必须拥有 manage 授权才能访问 manage.html
     *
     * 3、当前用户必须拥有 administrator 角色才能访问 administrator.html
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("iocdefaultsecuritymaneger") DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
          factoryBean.setSecurityManager(securityManager);
          //用map设置 权限设置 俩个参数(路径,规则) 规则如下 
          Map<String,String> map = new Hashtable<>();
            map.put("/main","authc");
        /**
         * 如果没有登录无法访问 并且自动跳转到login界面 (没有该页面的话显示找不到)
         */
        map.put("/manage","perms[manage]");
          map.put("/administrator","roles[admin]");
          //当我们为页面路径写了认证信息后 当我们访问时以上页面 会自动跳转到login.jsp(没有该页面所以是传乱码)
          factoryBean.setFilterChainDefinitionMap(map);
        /**
         * 一般电商项目不会给index设置拦截   在index页面通过超链接跳转可以  但是通过主页访问其他页面不行
         */
        //设置登录页面 因为为授权默认是跳转到login jsp 这里视图解析器 将登陆页面换html
         factoryBean.setLoginUrl("/login");
//          //设置未授权页面
//          factoryBean.setUnauthorizedUrl("/unauth");
          return factoryBean;
     }
    }

游览器访问结果:
在这里插入图片描述

编写认证和授权规则:

认证过滤器

anon:无需认证。(游客商品游览 ,查询信息)

authc:必须认证。

authcBasic:需要通过 HTTPBasic 认证。

user:不一定通过认证,只要曾经被 Shiro 记录即可,比如:(登录页面的记住我 cookie存放信息类似)。

授权过滤器

perms:必须拥有某个权限才能访问。(读,写)

role:必须拥有某个角色才能访问。(游客,manager)

port:请求的端口必须是指定值才可以。

rest:请求必须基于 RESTful风格,POST、PUT、GET、DELETE 。 (/PATH/{参数})

ssl:必须是安全的 URL 请求,协议 HTTPS。

controller完全用户认真

以上shiro配置完成自定义的给页面经行权限 ,信息,角色认证配置,接下来到controller实现过程

@Controller
public class accountcontroller {
   @RequestMapping("/{url}")
    public String index(@PathVariable("url") String url){
       //根据路径跳转
   return  url;
}
@PostMapping("/login")
    public String login(@RequestParam("username") String username, @RequestParam("password")String password, Model model){
    System.out.println(username);
       //subject装载用户信息 所以需要获取subject
    Subject subject= SecurityUtils.getSubject();
    //用户输入的信息封装在token里面
    UsernamePasswordToken token=new UsernamePasswordToken(username,password);
    //subject 是和shiro的封装类   shiro 经行登录验证
    try
    {
        subject.login(token);
    //todo 登录成功
        return "index";//获得授权
    }
    catch (UnknownAccountException e){
        e.printStackTrace();//catch  UnknownAccountException用户名不存在的异常
        //返回异常对饮信息 map model 随着转发而转发
        model.addAttribute("msg","账户错误");
        return  "login";//登录失败返回  其他页面返回主页
    }
    catch (IncorrectCredentialsException e){//AuthenticationException e用户信息异常

//   抛出没找到数据库对应数据异常 如果realm模块那边用户正确但是密码失败 catch  AuthenticationException 用户信息异常
        //IncorrectCredentialsException 密码信息异常
        e.printStackTrace();
        model.addAttribute("msg","密码不存在");
        return  "login";
    }

}
}

此时访问只需要认真的路径 可以访问
在这里插入图片描述
访问需要mange权限的无法到达
在这里插入图片描述

到此用户认证完成 接下来经行权限授权
授权

先修改realm的授权部分
此时的 realm文件只看 doGetAuthorizationInfo授权部分

 /**
 * 开发者自定义的模块,根据项目的需求,自定义验证和授权的逻辑全部写在 Realm 中。
 * 然后注入DefaultWebSecurityManager
 * 默认继承AuthorizingRealm俩个抽象方法 一个authenticationinfo用户信息
 * AuthorizationInfo 授权信息
 * 返回认证信息集合(AuthenticationInfo)和授权信息(SimpleAuthorizationInfo )集合

 */
public class AccountRealm extends AuthorizingRealm {
    @Autowired
    com.springboot.springbootshiro.M1.service.accountservice accountservice;
    // TODO: 2022/10/2  权限信息
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        /**
         * 认证完成  开始授权 从登陆用户信息上获取权限
         * 把当前用户的角色,权限信息让shiro执行
         */
        //和controller 一样通过subject获取当前用户信息
        Subject subject= SecurityUtils.getSubject();
        //获取shiro中注入的用户对象  new SimpleAuthenticationInfo(finduser,finduser.getPassword(),getName());这一步传入的
        account account= (account) subject.getPrincipal();
        //todo 得到该用户后设置角色和权限
        //设置角色 多个用集合 使用set的原因是防止 重复(一个角色对应一种权限)
        Set<String> roles=new HashSet<>();
        roles.add(account.getRole());//shiro把给当前字段认证为角色
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles);//ps 这个author授权信息对象 认证返回给shiro的是authen认证信息对象
      //设置权限  角色和权限都是 SimpleAuthorizationInfo属于授权部分的
        info.addStringPermission(account.getPerms()) ;//设置权限
        return info;//返回给shiro配置权限信息集合
    }


    //todo 做认证信息
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
       //todo 此时用户信息密码已经转入封装到    authenticationToken 然后强转为账户密码令牌
        //获取用户信息令牌
        UsernamePasswordToken  token=(UsernamePasswordToken)authenticationToken;
        account finduser = accountservice.finduser(token.getUsername());//todo 把token中封装的信息传入 service查找是否存在
        if (finduser!=null){
            //todo 账户验证后验证密码  AuthenticationInfo认证信息
            //三个参数 查询到的对象 ,密码,
            /**
             * finduser,finduser.getPassword() token中密码和查询到的正确密码经行对比
             * getNamecash 异常名
             */
            return  new SimpleAuthenticationInfo(finduser,finduser.getPassword(),getName());
        }

        return null;
    }
}

现在使用其他用户登录
在这里插入图片描述
ls 可以访问 manage在这里插入图片描述
无法访问在这里插入图片描述而有角色权限的ww账户可以访问

在这里插入图片描述并且和session一样只要登录后游览器都可以访问对应页面,关闭游览器需要重新登录

四.优化 异常信息 及其功能

异常报错白板表看,所以我们可以自定意
默认规则

  • 默认情况下,Spring Boot提供/error处理所有错误的映射

  • 机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。对于浏览器客户端,响应一个“ whitelabel”错误视图,以HTML格式呈现相同的数据

{
  "timestamp": "2020-11-22T05:53:28.416+00:00",
  "status": 404,
  "error": "Not Found",
  "message": "No message available",
  "path": "/asadada"
}
  • 要对其进行自定义,添加View解析为error

  • 要完全替换默认行为,可以实现 ErrorController 并注册该类型的Bean定义,或添加ErrorAttributes类型的组件以使用现有机制但替换其内容。
    (一般用这个)

  • /templates/error/下的4xx,5xx页面会被自动解析

在这里插入图片描述
通常权限报错是401 ,所以写了这个页面
在这里插入图片描述

但是这里我们使用shiro的指定未授权页面

shiroconfig.java

  factoryBean.setUnauthorizedUrl("/unauth");
然后controller添加对应处理

这里就不再写个页面了返回文字就行

@GetMapping("/unauth")
@ResponseBody
    public  String error(){
       return "未授权无法返回";
}

登录无权限的账户后
在这里插入图片描述#### 四.退出当前用户以及优化
1.想要网站显示登录的用户信息,用户名?生日?年龄等
controller ligin方法添加

 subject.login(token);//这一步的时候 就已经返回 SimpleAuthenticationInfo 此时subject里面存入SimpleAuthenticationInfo
    //todo 登录成功
        account account=(account) subject.getPrincipal();//通过pricinpal取
        //然后存入域对象session 能够显示用户个人信息
        Session session = subject.getSession();//subject 里面封装了session
        session.setAttribute("username",account.getUsername());//把账户名存进去
        return "index";//获得授权

然后显示页面取出数据
(做判断是防止如未登录 没有session会抛出异常)

  <div th:if="${session.account != null}">
        <span th:text="${session.account.username}+'欢迎回来!'"></span><a href="#">退出</a>
    </div>

2.当我们需要切换用户 需要logout推出

@ResponseBody
@GetMapping("/logout")
    public  String logout(){
       //等处操作 就是销毁session
    Subject subject=SecurityUtils.getSubject();
    //并且subject封装好了改操作 logut
    subject.logout();
    return  "退出成功";
}
五.通过不同角色权限展示不同菜单

1.导入依赖

<dependency>
    <groupId>com.github.theborakompanioni</groupId>
    <artifactId>thymeleaf-extras-shiro</artifactId>
    <version>2.0.0</version>
</dependency>

2.配置configuration类中添加bean (shirogconfig也行)

 @Bean
public ShiroDialect shiroDialect(){ 
   return new ShiroDialect();
   }

3.index中使用

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro"><!--添加shiro方言-->
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="shortcut icon" href="#"/>
</head>
<body>
    <h1>index</h1>
    <div th:if="${session.account != null}">
        <span th:text="${session.account.username}+'欢迎回来!'"></span><a href="/logout">退出</a>
    </div>
    <a href="/main">main</a> <br/>
    <div shiro:hasPermission="manage"><!--权限判断当前用户拥有该权限显示div内容否则不显示-->
        <a href="manage">manage</a> <br/>
    </div>
    <div shiro:hasRole="admini">
        <a href="/administrator">administrator</a><!--权限判断当前用户拥有该角色显示div内容否则不显示-->
    </div>
</body>
</html>

到目前位置基本以及常用得功能以及掌握,与springsecurity有诸多相似之处,再学习Springsecurity前可以复习本文
– 致自己

4.实际前后端分离项目的使用

待更新 今天先休息

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蓝胖子不是胖子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值