接入Shiro(1)——极简登录认证

2023-05-16

Shiro是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理等功能。之前的开发都是在低代码上直接使用Shiro,一般也不需要修改。

在SpringBoot的基础上,只接入Shiro的登录认证还是头一次。看各方文档和代码,不是一大坨概念讲的云里雾里,就是一大坨代码看的眼花缭乱。认证和授权拌在一起,咽又咽不下去,咽了一点也消化不了。

花了一天时间终于从JeecgBoot的代码中拆出了登录认证,又花了一天时间做了个极简版本,现在分享给大家。

一、背景

后端基于SpringBoot,前端基于vue使用antdv的组件,即前后端分离。

二、准备

1、添加Shiro依赖

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

2、前端请求

为了简化前端,直接使用Swagger2的API接口文档做前端测试。

Swagger2的接入,请参考之前的博文,接入knife4(3.0.3)。

3、后端接口

后端添加俩测试用的接口,一个接口不需要认证,另一个接口需要认证,代码如下:

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Api(tags = "测试Shiro")
@RestController
@RequestMapping("/demo")
public class DemoRC {

    /**
     * 这个接口不需要认证
     */
    @ApiOperation("Get1")
    @GetMapping("/get1")
    String get() {
        return "get1";
    }

    /**
     * 这个接口需要认证
     */
    @ApiOperation("Get2")
    @GetMapping("/get2")
    String get2() {
        return "get2";
    }
}

三、接入Shiro

1、添加Shiro配置

配置是过滤器的第一步,需要告诉框架,Shiro要处理什么样的网络请求,代码如下:

import com.example.demo.config.shiro.filters.CustomFilter;
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;

import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {

    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shirFilter() {
        // Swagger相关地址放入白名单
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        filterChainDefinitionMap.put("/doc.html", "anon");
        filterChainDefinitionMap.put("/swagger**/**", "anon");
        filterChainDefinitionMap.put("/webjars/**", "anon");
        filterChainDefinitionMap.put("/v3/**", "anon");

        // 测试接口放入白名单(不需要认证)
        filterChainDefinitionMap.put("/demo/get1", "anon");

        // 白名单之外的,都得通过该过滤器
        Map<String, Filter> filterMap = new HashMap<String, Filter>(1);
        filterMap.put("custom", new CustomFilter());
        filterChainDefinitionMap.put("/**", "custom");

        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager());
        // 设置过滤器
        shiroFilterFactoryBean.setFilters(filterMap);
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    /**
     * 创建默认安全管理对象
     */
    private DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 设置自己的认证员(只有自己知道token与用户名的关系)
        securityManager.setRealm(new CustomRealm());
        return securityManager;
    }
}

其中CustomFilter和CustomRealm是自定义类,下面来定义这俩类。

2、过滤器CustomFilter

import com.example.demo.config.shiro.CustomToken;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class CustomFilter extends BasicHttpAuthenticationFilter {

    /**
     * 是否允许通过,只做最基本的判断
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        try {
            return executeLogin(request, response);
        } catch (Exception e) {
            return false;
        }
    }

    /**`
     * 执行登录
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws IOException {
        // 获取Token
        HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
        String token = httpServletRequest.getHeader("ACCESS-TOKEN");
        // Token验证
        try {
            CustomToken customToken = new CustomToken(token);
            getSubject(request, response).login(customToken);
        } catch (AuthenticationException e) {
            HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
            httpServletResponse.sendError(401, e.getLocalizedMessage());
            return false;
        }
        return true;
    }

    /**
     * 对跨域提供支持
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
        HttpServletResponse httpServletResponse = WebUtils.toHttp(response);
        {
            httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
            httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
            httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
            // 是否允许发送Cookie,默认Cookie不包括在CORS请求之中。设为true时,表示服务器允许Cookie包含在请求中。
            httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true");
        }
        // 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }
}

如果是前后端不分离的,不需要实现方法preHandle。

这里要求前端发送的token保存在Header的“ACCESS-TOKEN”中,根据实际情况自己修改。

3、CustomToken

自定义一个Token类,以便做进一步的认证,代码如下:

import org.apache.shiro.authc.AuthenticationToken;

public class CustomToken implements AuthenticationToken {
    private final String token;

    public CustomToken(String token) {
        this.token = token;
    }

    @Override
    public Object getPrincipal() {
        return this.token;
    }

    @Override
    public Object getCredentials() {
        return this.token;
    }

    public String getToken() {
        return this.token;
    }
}

4、进一步认证CustomRealm

import org.apache.shiro.authc.*;
import org.apache.shiro.realm.AuthenticatingRealm;
import org.springframework.stereotype.Component;

@Component
public class CustomRealm extends AuthenticatingRealm {

    /**
     * 告诉框架,CustomToken类型的Token必须要通过当前类的认证
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof CustomToken;
    }

    /**
     * 登录认证
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
        String token = ((CustomToken)auth).getToken();
        if (token == null || token.length() < 1) {
            throw new AuthenticationException("未登录,请进行登录!");
        }
        // 假设token长度等于8才是正确的
        if (token.length() != 8) {
            throw new AuthenticationException("token错误!");
        }
        // 从token中得到用户名
        String username = token.substring(0, 4);
        // 根据用户名得到用户信息
        Object userInfo = username;
        return new SimpleAuthenticationInfo(userInfo, token, getName());
    }
}

四、测试

1、开启动态参数请求,以便修改Header

打开API接口文档,点击菜单“文档管理” -> “个性化设置”,勾选“开启动态请求参数”,然后刷新页面。如下图:

 2、测试

(1)不带Header

点击菜单“测试Shiro”下的“Get1”和“Get2”,分别请求,可以发现Get1能正常请求到数据,Get2请求的状态码是401,是token不存在时抛出的异常。如下图:

(2)token长度错误

Get2,添加请求头。请求头名称“ACCESS-TOKEN”,请求头内容“123456”。依然返回401,token长度不等于8时抛出的异常。如下图:

 (3)认证通过

请求头中输入“12345678”,正确得到了返回结果。如下图:

五、结束

终于,终于弄通了Shiro认证的基本流程,下一步处理登录逻辑生成token,然后进行token认证。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

接入Shiro(1)——极简登录认证 的相关文章

随机推荐

  • IOS开发基础篇--CAShapeLayer的strokeStart和strokeEnd属性

    一 案例演示 最近有一个小需求 xff0c 就是要做一个圆形进度条 xff0c 大概样子如下 xff1a 在不知道有CAShapeLayer的strokeStart和strokeEnd属性的时候 xff0c 我采取的方法就是实时的 移除旧的
  • 求n个数的最大值、最小值、平均值 (15 分)

    从键盘输入n xff08 3 lt 61 n lt 61 20 xff09 个整数 xff0c 求出这n个数的最大值 最小值 平均值 xff08 平均值保留2位小数 xff09 输入格式 在第一行输入一个介于3至20之间的整数n xff0c
  • Pandas数据处理/drop_duplicates()/映射map()/replace()/rename()/分箱/过滤异常值/随机抽样take()/random.permutation()

    1 删除重复元素 使用duplicated 函数检测重复的行 xff0c 返回元素为布尔类型的Series对象 xff0c 每个元素对应一行 xff0c 如果该行不是第一次出现 xff0c 则元素为True import numpy as
  • libjpeg的移植

    libjpeg是什么东西 xff1f libjpeg是一个开源源码包 xff0c 功能是图片格式之间的相互转换 compress decompress 移植步骤 xff1a xff11 解压源码 不要解压到共享目录中去 xff0c 共享文件
  • TCP实现服务器与单客户端连接(多线程)

    局域网内实现单客户端与服务器通信 客户端通过配置ip和端口号来连接服务器 客户端和服务器端各自具有发送和接收线程 可以实现一方持续发送 服务器端 package Net import java io IOException import j
  • python练习题(四十二):809*??=800*??+9*?? 其中??代表两位数, 809*??为四位数,8*??为两位数,9*??为3位数。求??代表的两位数,及809*??后的结果?

    span class token comment 题目 xff1a span span class token comment 809 61 800 43 9 其中 代表一个两位数 span span class token comment
  • Android 利用V4L2 调用camera

    为何要使用V4L2进行开发 出于安全原因 xff0c 使用Android 原生的Camera接口 xff0c 必须要使用可见的surface显示摄像头的preview图像 xff0c 即必须要让用户看到你的应用正在使用摄像头 另外Andro
  • MySql 8.0 设置允许远程登录授权

    MySQL 连接出现 is not allowed to connect to this MySQL Server错误提示 打开 MySQL8 0 Command Line Client 1 打开远程连接 mysql span class
  • 基于Jeecg的权限获取

    Jeecg的权限 xff0c 是通过菜单管理中的 按钮 权限 实现的 xff0c 前端和后端怎么获取这个权限呢 xff0c 本文简单做个记录 1 前端 import USER AUTH from 34 64 store mutation t
  • 基于Jeecg使用vue-konva

    konva是一个基于canvas的可视化框架 xff08 https konvajs org xff09 xff0c vue konva则是基于vue的 当前的一个小项目是基于Jeecg开发的 xff0c 想做可视化的功能 xff0c 于是
  • 基于JeecgBoot的v-has权限控制

    v has是一个很方便的前端权限控制标签 xff0c 但是只支持一个授权标识 xff0c 如果需要两个或以上的权限与或运算就不支持了 解决方案 xff1a 修改文件 64 utils hasPermission js 中的方法filterG
  • 前后端离线开发相关软件下载地址大全

    一 后端开发 xff1a 1 IDEA Windows xff1a https www jetbrains com zh cn idea download section 61 windows 2 JDK Windows xff1a htt
  • 离线部署GitLab

    一 背景 公司是局域网开发环境 xff0c 资源文件用svn管理 xff0c 代码用git管理 GitLab有点大 xff0c 而且只能在Linux下使用 xff0c 所以选择了轻巧的GitBlit 后来GitBlit被暴漏洞 xff0c
  • C# WPF实战项目升级了

    概述 之前用Caliburn Micro搭建的WPF实战项目 xff0c CM框架选用了 3 0 3 xff0c 实际上CM框架目前最新版已经到4 0 173了 xff0c 所有很有必须升级一下项目了 本来打算把平台框架也直接升级到 NET
  • 接入Swagger2(1) —— SwaggerUI

    关于Swagger不再多述 xff0c 这里只分享接入方式 1 接入 SpringBoot中引入Swagger相当简单 xff0c 只需要添加一个依赖就行 xff0c 如下 xff1a lt dependency gt lt groupId
  • 接入Swagger2(2) —— knife4j

    上一篇文章介绍了Swagger2默认UI的接入方式 xff0c 但是UI实在太丑 xff0c 而且布局也很难用 xff0c 这里介绍一个相对漂亮点的UI knife4j knife4j是基于swagger2开发的 xff0c 是swagge
  • SpringSecurity的使用(1)—— 起步

    年初就关注到SpringSecurity了 xff0c 看官网 xff0c 看不懂 xff1b 查资料 xff0c 跟我预想的不一样 xff0c 所以一直入不了门 有博文说添加依赖 xff0c 再次启动项目就会跳转到登录页面 我是前后端分离
  • 离线模式:Unresolved dependency: ‘com.xx.xx:xx-xx:jar:x.x.x‘

    SpringBoot最常用的编译器就是IDEA xff0c 如果不能联网 xff0c 则需要将工程设置为离线模式 1 遇到的问题 这两天遇到了这样的问题 xff1a JeecgBoot的工程中有添加依赖包 knifej spring boo
  • SpringBoot中Java调用dll

    1 背景 最近接手的项目 xff0c 底层算法是C 43 43 写的 xff0c 并且封装成了dll 而目前的需求 xff0c 需要把这些算法移到服务端执行 不可能把C 43 43 写的算法库改用Java重写一遍 xff0c 而且也基于执行
  • 接入Shiro(1)——极简登录认证

    Shiro是一个强大易用的Java安全框架 xff0c 提供了认证 授权 加密和会话管理等功能 之前的开发都是在低代码上直接使用Shiro xff0c 一般也不需要修改 在SpringBoot的基础上 xff0c 只接入Shiro的登录认证