第06讲:Security在前后端分离项目中的综合应用

news2025/1/14 3:35:25

一、基本业务开发

1.1、需求分析

由于Security对用户进行鉴权和授权是通过用户名去数据库中取权限,所以我们需要开发一个功能,这个功能就是通过username去数据库里查该用户所具备的所有权限

1.2、完成需求

1.2.1、数据库脚本

请下载文章末尾的源代码

1.2.2、UserDAO.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="demo.dao.UserDao">

    <resultMap id="userAnthMap" type="Users">
        <id column="id" property="id"></id>
        <result column="username" property="username"></result>
        <result column="account" property="account"></result>
        <result column="password" property="password"></result>
        <collection property="anths" ofType="java.lang.String">
            <result column="anth_code" />
        </collection>

    </resultMap>

    <select id="queryUserInfoAndAnths" resultMap="userAnthMap" >
        SELECT
            u.id,
            u.username,
            u.account,
            u.password,
            ta.anth_code
        FROM
            t_user u
            LEFT JOIN t_user_anth tua ON u.id = tua.user_id
            LEFT JOIN t_anth ta ON tua.anth_id = ta.id
            where u.account = #{account}

    </select>
</mapper>

二、Security底层开发

2.1、鉴权服务类SecurityService

根据username去数据库中查询该用户所具备的权限,并通过Security进行授权

package demo.service;

import demo.dao.UserDao;
import demo.domain.Users;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

/**
 * 获取用户名和密码的service
 */
@Service
public class SecurityService implements UserDetailsService {
    @Autowired
    @Lazy
    private PasswordEncoder passwordEncoder;
    @Autowired(required=false)
    private UserDao userDao;
    
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //根据username,去数据库查该用户的信息
        Users users = userDao.queryUserInfoAndAnths(username);
        //设置的authorityString必须和SecurityConfig中设置的hasAuthority字符串一致
        //当前用户具有insert权限,具有admin角色(角色必须加ROLE_的前缀)
        String anths = String.join(",", users.getAnths());
        if (users != null) {
            return  new User(users.getAccount(),passwordEncoder.encode(users.getPassword()),
                    AuthorityUtils.commaSeparatedStringToAuthorityList(anths));
        } else {
            throw  new UsernameNotFoundException("用户名和密码输入错误!");
        }
    }
}

2.2、登录成功后的处理器

package demo.handler;

import com.alibaba.fastjson.JSON;
import demo.util.JWTUtil;
import demo.util.ResponseResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.concurrent.TimeUnit;

/**
 * 前后端分离的项目情况下,登录成功后,返回的不再是一个页面地址,还是一个json
 * 处理用户登录成功后返回数据:比如用户名等信息
 */
@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
    @Autowired
    private RedisTemplate<String,String> redisTemplate;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest,
                                        HttpServletResponse httpServletResponse,
                                        Authentication authentication)
            throws IOException, ServletException {
        /**
         * 获取登录成功的用户信息
         */
        try {
            User user = (User)authentication.getPrincipal();
            // 把jwt放入redis,并设置有效时间
            String jwt = JWTUtil.createJWT(user.getUsername());
            //TimeUnit.SECONDS : 秒
            //TimeUnit.MINUTES : 分钟
            redisTemplate.opsForValue().set("jwt:"+user.getUsername(), jwt,30, TimeUnit.MINUTES);

            // 设置字符集
            httpServletResponse.setContentType("application/json;charset=UTF-8");
            PrintWriter pw = httpServletResponse.getWriter();
            String json = JSON.toJSONString(new ResponseResult<>().ok(jwt));
            pw.print(json);
            pw.flush();
            pw.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2.3、登录失败后的处理器

package demo.handler;

import com.alibaba.fastjson.JSON;
import demo.util.ResponseResult;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
 * 登录失败后进去,返回给前端提示信息
 */
public class LoginFailHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        // 设置字符集
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        PrintWriter pw = httpServletResponse.getWriter();
        String json = JSON.toJSONString(ResponseResult.FAIL);
        pw.print(json);
        pw.flush();
        pw.close();
    }
}

2.4、用户未登录直接访问系统资源的处理器

package demo.handler;

import com.alibaba.fastjson.JSON;
import demo.util.ResponseResult;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
 * 前后后端分离项目情况下:用户未登录直接访问系统资源,
 * 会被该类拦截
 */
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        // 设置字符集
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        PrintWriter pw = httpServletResponse.getWriter();
        String json = JSON.toJSONString(ResponseResult.NOLOGIN);
        pw.print(json);
        pw.flush();
        pw.close();
    }
}

2.5、拦截没有权限访问该资源的处理器

就算登陆成功了,但是如果该用户没有访问该资源的权限,则进行处理

package demo.handler;

import com.alibaba.fastjson.JSON;
import demo.util.ResponseResult;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
 * 虽然你知道用户名,和密码
 * 但是,拦截你没有权限访问该资源操作
 *
 */
public class MyAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        // 设置字符集
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        PrintWriter pw = httpServletResponse.getWriter();
        String json = JSON.toJSONString(ResponseResult.NOAUTH);
        pw.print(json);
        pw.flush();
        pw.close();
    }
}

2.6、用户注销的处理器

在前后端分离项目中,会让session失效,所以该处理器可以不进行配置

package demo.handler;

import com.alibaba.fastjson.JSON;
import demo.util.ResponseResult;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
 * 用户退出操作
 */
public class MyLogoutSuccesshandler implements LogoutSuccessHandler {
    @Override
    public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        // 设置字符集
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        PrintWriter pw = httpServletResponse.getWriter();
        String json = JSON.toJSONString(ResponseResult.LOGOUT);
        pw.print(json);
        pw.flush();
        pw.close();
    }
}

2.7、注册jwt的处理器

使用redis对jwt进行保存,在该处理器中对合法的jwt的用户进行redis续期

package demo.handler;

import demo.service.SecurityService;
import demo.util.JWTUtil;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
 * security 整合jwt用的过滤器
 * 功能点1:判断出登录请求外,是否携带了jwt, 否:放掉不处理
 * 功能点2:判断携带的jwt 是否合法,否:放掉不处理
 * 功能点3:拿redis的jwt和请求头的jwt 做对比
 *         3.1:redis的jwt 已经过期  放掉不处理
 *         3.2:在jwt的值对比一下    放掉不处理
 *
 */
@Component
public class JWTFilter extends OncePerRequestFilter {
    @Autowired
    private RedisTemplate<String,String> redisTemplate;
    @Autowired
    private SecurityService service;
    @SneakyThrows
    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest,
                                    HttpServletResponse httpServletResponse,
                                    FilterChain filterChain)
            throws ServletException, IOException {

        //功能点1:在请求头拿到jwt
        String jwt = httpServletRequest.getHeader("jwt");
        if (jwt == null){
            //放给security 其他过滤器,该方法不做处理
            filterChain.doFilter(httpServletRequest,httpServletResponse);
            return;
        }

        // 功能点2:jwt不合法
        if (!JWTUtil.decode(jwt)){
            filterChain.doFilter(httpServletRequest,httpServletResponse);
            return;
        }

        //功能点3 获取jwt的用户信息
        Map payLoad = JWTUtil.getPayload(jwt);
        String username = (String) payLoad.get("username");

        //拿到redis的jwt
        String redisJWT = redisTemplate.opsForValue().get("jwt:" + username);

        //判断redis是否有该jwt
        if (redisJWT == null){
            filterChain.doFilter(httpServletRequest,httpServletResponse);
            return;
        }

        if (!jwt.equals(redisJWT)){
            filterChain.doFilter(httpServletRequest,httpServletResponse);
            return;
        }

        //给redis 的jwt续期
        redisTemplate.opsForValue().set("jwt:" + username,jwt,30,
                TimeUnit.MINUTES);

        //获取用户名,密码,权限
        UserDetails userDetails = service.loadUserByUsername(username);

        // 获取用户信息 生成security容器凭证
        UsernamePasswordAuthenticationToken  upa =
                new UsernamePasswordAuthenticationToken(userDetails.getUsername()
                        ,userDetails.getPassword(),userDetails.getAuthorities());

        //放入凭证
        SecurityContextHolder.getContext().setAuthentication(upa);

        // 本方法共功能执行完了,交给下一个过滤器
        filterChain.doFilter(httpServletRequest,httpServletResponse);
    }
}

2.8、在Security配置类中进行总配置

  • 配置注解鉴权
  • 配置鉴权的UserService
  • 配置各种过滤器
  • 配置跨域请求
  • 配置session禁用
  • 配置跨站脚本攻击
package demo.config;

import demo.handler.*;
import demo.service.SecurityService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/**
* Spring Security配置类
*/
@EnableWebSecurity
//开启security注解,用到哪个注解,就将哪一个注解设置为true
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled=true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
   @Autowired
   private SecurityService securityService;

   @Autowired
   private LoginSuccessHandler loginSuccessHandler;

   /**
    * 注入spring容器
    */
   @Autowired
   private JWTFilter jwtFilter;

   @Override
   protected void configure(AuthenticationManagerBuilder auth) throws Exception {
       auth.userDetailsService(securityService).passwordEncoder(passwordEncoder());
   }

   @Override
   protected void configure(HttpSecurity http) throws Exception {
       //配置没有权限访问时跳转的自定义页面
       http.formLogin() //表单登陆
               .loginProcessingUrl("/user/login") //登录访问的路径
               .successHandler(loginSuccessHandler)  //【登录成功后的处理器】从login.html页面登录成功之后跳转到该路径,用于前后端分离项目返回用户信息
               .failureHandler(new LoginFailHandler()) //登录失败后的处理器
               .permitAll();

       /**
        * 未登录,就想访问系统资源
        */
       http.exceptionHandling().
               authenticationEntryPoint(new MyAuthenticationEntryPoint())
               .accessDeniedHandler(new MyAccessDeniedHandler());

       /**
        * 退出系统
        */
       http.logout().logoutSuccessHandler(new MyLogoutSuccesshandler());

       http.authorizeHttpRequests() //认证配置
               .anyRequest() //任何请求
               .authenticated(); //所有请求都拦截

       http.cors(); //开启跨域,需结合demo.config.CorsConfig

       // 注册jwt的过滤器
       http.addFilterAfter(jwtFilter, UsernamePasswordAuthenticationFilter.class);

       //前后端项目中要禁用掉session
       http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

       http.csrf().disable(); //关闭跨站脚本攻击
   }

   /**
    * 将PasswordEncoder注入到ioc容器
    * @return
    */
   @Bean
   public PasswordEncoder passwordEncoder() {
       return new BCryptPasswordEncoder();
   }
}

三、测试

不登录的情况下请求Controller
在这里插入图片描述

登陆之后获取jwt
在这里插入图片描述

携带jwt请求Controller
在这里插入图片描述

四、源代码

Security在前后端分离项目中的综合应用.zip

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/94190.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JavaScript高级 |深入闭包

本文已收录于专栏⭐️ 《JavaScript》⭐️ 学习指南&#xff1a;闭包基本概念内存管理垃圾回收GC算法-标记清除GC算法-标记整理GC算法-分代收集GC算法-增量收集GC算法-闲时收集内存泄露完结散花参考文献闭包 闭包是JavaScript中非常容易让人迷惑的知识点。 《在你不知道的Java…

灯泡与影子(三分)

题目描述: 有一天&#xff0c;小明发现他的影子长度随着他在灯泡和墙壁之间走动时会发生变化&#xff0c;一个突发奇想在他的脑海里闪过&#xff0c;他现在想知道他来回走动&#xff0c;他的影子的最大长度是多少&#xff1f; 输入格式: 第一行包含一个整数T (T < 100),表…

C语言 常用标准库函数代码实现

一、内存 1. memcpy函数 memcpy 函数用于 把资源内存&#xff08;src所指向的内存区域&#xff09; 拷贝到目标内存&#xff08;dest所指向的内存区域&#xff09;&#xff1b;拷贝多少个&#xff1f;有一个size变量控制拷贝的字节数&#xff1b; 函数原型&#xff1a;void …

网络工程毕业设计 SSM汽车租赁系统(源码+论文)

文章目录1 项目简介2 实现效果2.1 界面展示3 设计方案3.1 概述3.2 系统流程3.3 系统结构设计4 项目获取1 项目简介 Hi&#xff0c;各位同学好呀&#xff0c;这里是M学姐&#xff01; 今天向大家分享一个今年(2022)最新完成的毕业设计项目作品&#xff0c;【基于SSM的汽车租赁…

mitmproxy 抓包神器-2.抓取Android 和 iOS 手机 https 请求

前言 抓取手机请求的前提条件是确保手机和电脑在同一网段上&#xff0c;也就是说使用同一WiFi。 启动服务 mitmweb 命令启动服务&#xff0c;默认监听8080端口 (venv) D:\demo\mitmproxy_xuexi>mitmweb [11:59:49.361] HTTP(S) proxy listening at *:8080. [11:59:49.36…

linux redhat8.0 权限管理

在linux里面&#xff0c;一切皆文件&#xff0c;不同的用户对文件有不同的管理权限&#xff0c;而只有root用户能为其他用户分配权限。 读&#xff08;r&#xff09;写&#xff08;w&#xff09;执行&#xff08;x&#xff09;数字表示421文件&#xff08;默认644&#xff09;…

SpringBoot-OneDay

优势 创建独立的spring程序自动配置spring简化的maven配置内嵌tomcat提供生产就绪型功能&#xff0c;如指标&#xff0c;健康检查和外部配置特性 为基于Spring 的开发提供更快的入门体验 开箱即用&#xff0c;没有代码生成&#xff0c;也无需XML 配置。同时也可以修改默认值来…

【从零开始学习深度学习】28.卷积神经网络之NiN模型介绍及其Pytorch实现【含完整代码】

目录1. NiN块介绍2. 构建NiN模型3.NIN模型每层输出形状4. 获取Fashion-MNIST数据和训练NiN模型5. 总结前几篇文章介绍的LeNet、AlexNet和VGG在设计上的共同之处是&#xff1a;先以由卷积层构成的模块充分抽取空间特征&#xff0c;再以由全连接层构成的模块来输出分类结果。其中…

RocketMQ基本概念及功能

文章目录背景架构模型NameServer 名字服务器Broker 代理服务器生产者主题队列消息消息标签消息位点消费者消费位点消费者分组订阅关系参考文章背景 RocketMQ是阿里巴巴在2012年开发的分布式消息中间件&#xff0c;专为万亿级超大规模的消息处理而设计&#xff0c;具有高吞吐量…

【VScode插件开发】<二>插件实践开发+发布

开发环境配置完&#xff0c;就得好好琢磨开发内容了&#xff0c;不能老停留在hello world上呀&#xff01; 一、开发文档结构分析 1.Package.json {"name": "kidtest","displayName": "KidTest","description": "for…

Gnoppix Linux系统发布

导读基于 Kali Linux 的 Linux 滚动发行版 Gnoppix 22.12 带来了 GNOME 43、Linux 内核 6.0 和新的升级。作为传统的现场 CD 发行版 Knoppix 项目的继承者&#xff0c;​​Gnoppix Linux​​ 是专门为渗透测试和反向工程而设计的。它为网页应用安全和数字权利保护进行了优化。除…

Java也可以轻松编写并发程序

如今&#xff0c;多核处理器在服务器&#xff0c;台式机及笔记本电脑上已经很普遍了&#xff0c;同时也被应用在更小的设备上&#xff0c;比如智能手机和平板电脑。这就开启了并发编程新的潜力&#xff0c;因为多个线程可以在多个内核上并发执行。在应用中要实现最大性能的一个…

SpringBoot+Vue实现前后端分离的小而学在线考试系统

文末获取源码 开发语言&#xff1a;Java 使用框架&#xff1a;spring boot 前端技术&#xff1a;JavaScript、Vue.js 、css3 开发工具&#xff1a;IDEA/MyEclipse/Eclipse、Visual Studio Code 数据库&#xff1a;MySQL 5.7/8.0 数据库管理工具&#xff1a;phpstudy/Navicat JD…

访问者模式(Visitor)

参考&#xff1a; 模板方法设计模式 (refactoringguru.cn) design-patterns-cpp/TemplateMethod.cpp at master JakubVojvoda/design-patterns-cpp GitHubhttps://github.com/JakubVojvoda/design-patterns-cpp/blob/master/state/State.cpp) 文章目录一、什么是访问者模式…

【Python机器学习】Sklearn库中Kmeans类、超参数K值确定、特征归一化的讲解(图文解释)

一、局部最优解 采用随机产生初始簇中心 的方法&#xff0c;可能会出现运行 结果不一致的情况。这是 因为不同的初始簇中心使 得算法可能收敛到不同的 局部极小值。 不能收敛到全局最小值&#xff0c;是最优化计算中常常遇到的问题。有一类称为凸优化的优化计算&#xff0c;不…

数字货币市场风暴肆虐,币圈人应该把握哪些新的赛道机遇

11月11日&#xff08;周五&#xff09;美股盘前&#xff0c;曾经为全球第二大加密货币交易所FTX在推特发布了申请破产保护的声明&#xff0c;创始人SBF已经辞去CEO职务。据声明&#xff0c;FTX已经任命John J. Ray III 担任CEO&#xff0c;SBF还将协助相关破产事宜。据FTX在推特…

[附源码]Python计算机毕业设计Django面向高校活动聚App

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

记录--三分钟打造自己专属的uni-app工具箱

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 介绍 可曾想过我们每次创建新项目&#xff0c;或者换地方写程序&#xff0c;都要把之前写过的工具类找出来又要复制粘贴一遍有些麻烦&#xff0c;尤其是写uni-app自定义模板主要还是开发工具完成的。这…

反序列化漏洞之CVE-2016-7124

目录 魔术函数 发生条件 靶场练习 魔术函数 __constuct: 构建对象的时被调用 __destruct: 明确销毁对象或脚本结束时被调用 __invoke: 当以函数方式调用对象时被调用 __toString: 当一个类被转换成字符串时被调用 __wakeup: 当使用unserialize时被调用&#xff0c;可用于做些…

【python】pandas 之 DataFrame、Series使用详解

目录 一&#xff1a;Pandas简介 二&#xff1a;Pandas数据结构 三&#xff1a;Series 四&#xff1a;字典生成Series 五&#xff1a;标量值生成Series 六&#xff1a;Series类似多维数组 七&#xff1a;Series类似字典 八&#xff1a;矢量操作与对齐 Series 标签 九…