Spring Security详细学习第一篇

Spring Security

  • 前言
  • Spring Security
    • 入门编辑
    • Spring Security底层原理
      • UserDetailsService接口
      • PasswordEncoder接口
    • 认证
      • 登录
      • 校验
      • 密码加密存储
      • 退出登录

前言

本文是作者学习三更老师的Spring Security课程所记录的学习心得和笔记知识,希望能帮助到大家

Spring Security

Spring Security基于Spring框架,提供了‘一套Web应用安全性的完整解决方案
Web应用安全性包括用户认证和用户授权是SpringSecurity的核心功能

  • 用户认证

系统认为用户是否能登录

  • 用户授权

判断用户是否有权限去做某些事情

入门编辑

第一步:搭建SpringBoot环境
第二步:导入相关依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

编写一个测试类创建项目

@RestController
public class logincontroller {
    @GetMapping("/hello")
    public String vos(){
        return "Hello,security";
    }
}

启动项目后会发现需要进行登录认证
在这里插入图片描述
默认用户名:user
密码:
在这里插入图片描述

Spring Security底层原理

SpringSecurity的底层就是一个过滤器链
FilterSecurityInterceptor:是一个方法级的权限过滤器,基本位于过滤链的最底层
ExceptionTranslationFilter:是一个异常过滤器,用来处理在认证授权过程中抛出的异常
UsernamePasswordAuthenticationFilter:对/login的POST请求做拦截,校验表单中的用户名,密码

UserDetailsService接口

UserDetailsService接口:当什么也没有配置的时候,账号和密码是由Spring Security定义生成的,而在实际项目中账号和密码都是从数据库中查询出来的,所以要通过自定义逻辑控制认证逻辑

PasswordEncoder接口

数据加密接口,用于返回User对象里面密码加密
通过BCryptPasswordEncorder 对象对密码进行加密

认证

认证流程:
在这里插入图片描述

在这里插入图片描述
在前端发送携带用户登录信息的请求的时候,会到达UsernamePasswordAuthenticationFilter过滤器当中,过滤器将用户信息封装成一个Authentication对象,其中只存在用户名和密码,然后通过调用方法aythenticate一步一步向下认证,最后通过用户名在内存中进行查找,把对应的信息封装到UserDetils对象当中去,返回的时候通过PasswordEncoder对比密码,正确返回,这时默认的用户登录的流程。
我们一般采用的是在数据库中进行查询对应的用户信息,如果查询正确生成JWT信息返回给前端界面,此处我们可以自己创建一个controller类代替UsernamePasswordAuthenticationFilter过滤器,在最后可以自定义UserDetailsService的实现类完成在数据库中的查询

这是生成一个JWT的过程,那么如何去校验JWT信息:
创建一个jwt认证过滤器(获取token,解析token,获取userid,封装Authentication对象存入SecurityContextHolder)

整体的认证思路:
登录:
一:自定义登录接口
调用ProviderManager的方法进行认证,如果认证通过生成jwt
把用户信息存入到redis中
二:自定义UserDetilsService
在这个实现中去查询数据库
校验:
一:定义jwt认证过滤器
获取token
解析token获取其中的userid
从redis中获取用户信息
存入到securityContextHolder

登录

自定义UserDetilsService方法:

@Service
public class userdetilservice implements UserDetailsService {
    @Autowired
    private Usermapper usermapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //查询用户信息
        LambdaQueryWrapper<User> lambdaQueryWrapper=new LambdaQueryWrapper<>();
        lambdaQueryWrapper.eq(User::getUsername,username);
        org.apache.catalina.User user = usermapper.selectOne(lambdaQueryWrapper);
        if(Objects.isNull(user))
        {
            throw new RuntimeException("用户名或者密码错误");
        }
        return null;
    }
}

这里是要将信息封装到UserDetils中,我们需要创建一个实体类继承UserDetils

@Data
@NoArgsConstructor
@AllArgsConstructor
public class userdetilsimpl implements UserDetails {
    private User user;
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    public String getPassword() {
        return user.getAge();
    }

    @Override
    public String getUsername() {
        return user.getUsername();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

}

运行测试,在进行运行的时候会报出,passwordEncoder的配对的值为null,这时我们需要在数据库的密码数据中加入前缀{noop},这样表示该密码进行明文保存

  • 自定义登录接口

将AuthenticationManager注入到容器当中
在上述所讲的配置类中继承的WebsecurityConfigurerAdapter中可以重写方法完成AuthenticationManager的配置进行用户认证

    @Bean	
    public AuthenticationManager authenticationManager() throws Exception{
        return super.authenticationManagerBean();
    }

通过三层架构完善登录功能:在service的实现层impl中注入AuthenticationManager
封装之后可以通过它所提供的方法:authenticate进行认证,参数需要一个authentication对象,由于它是一个接口但是我们的框架中提供了它的实现对象,通过登录的用户名和密码可以创建一个authentication对象


@Service
public class loginserviceimpl implements loginservice {
    @Autowired
    private AuthenticationManager authenticationManager;
    @Override
    public void login(User user) {
        //进行用户认证
        //在登录之后将用户名和密码封装成一个Authentiaion对象
        //
        UsernamePasswordAuthenticationToken us=new UsernamePasswordAuthenticationToken(user.getUsername(),user.getAge());
        Authentication authenticate = authenticationManager.authenticate(us);
        //判断是否认证通过
        if(Objects.isNull(authenticate)){
            throw  new RuntimeException("登陆失败");

        }
        //如果通过了使用userid生成一个jwt,jwt
        User user1 = (User) authenticate.getPrincipal();
        String string = user1.getId().toString();
        String jwt=JwtUtil.createJWT(string);
        Map<String,String> map=new HashMap<>();
        map.put("token",jwt);
        return new ResponseResult(200,"登录成功",map);
    }
}

校验

创建一个过滤器,之前的javaWeb讲解中过滤器都实现Filter,在这里我们继承OncePerRequestFilter就可,这样会使得前端请求来的请求只经过该过滤器一次
过滤器代码:

@Component
public class AuthonJwtfilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        //获取token
        String token = httpServletRequest.getHeader("token");
        //判断token是否为空
        if(!StringUtils.hasText(token))
        {
            //放行
            filterChain.doFilter(httpServletRequest,httpServletResponse);
            return;
        }
        // 解析token
        try {
            Claims claims=JwtUtil.parseJWT(token);
            String id=claims.getSubject();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("token非法");
        }
        //从redis中获取用户信息
        String rediskey="login:"+id;
        User user=redisCache.getCacheObject(rediskey);
        if(!Objects.isNull(user))
        {
            throw new RuntimeException("用户未登录");
        }
        //存入SecurityContextHolder
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken=new UsernamePasswordAuthenticationToken(user,null,null);
        SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
        //放行
        filterChain.doFilter(httpServletRequest,httpServletResponse);
    }
}

完成后需要在配置类中将jwt过滤器放在过滤链中:
在配置类的配置方法configure中配置

http.addFilterBefore(jwtAuthenticationTokenFilter,usernamePasswordAunthencationFilter.class);

密码加密存储

实际项目中我们不会将密码明文保存在数据库中,默认的PasswordEncoder要求数据库中的密码格式为:{id}password根据id进行判断加密方式
我们一般所用的是SpringSAecurity为我们提供的BCryptPasswordEncorder
我们将BCryptPasswordEncorder注入容器中,我们就可以根据passwordEncoder进行密码判断

我们需要进行Spring Security进行配置类:

@Configuration
public class securityconfig extends WebSecurityConfigurerAdapter {
    @Bean
    //创建BCryptPasswordEncoder注入容器
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

我们可以使用encode方法将明文密码进行加密
可以使用matches方法将密码与加密后的方法判断是否匹配

    @Test
    public void  TestBC(){
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        //对数据进行加密
        //使用盐和加密
        String encode = bCryptPasswordEncoder.encode("123456");
        bCryptPasswordEncoder.matches("1234","加密之后的密文");
    }
}

退出登录

如何退出登录:
我们只需要定义一个登录接口,然后获取securityContextHolder中的认证信息,删除redis中的数据,这样就会退出登录
也是通过三层架构进行退出登录操作,在服务实现类中:


public class zhuxiaoimpl {
    public ResponseResult logout(){
        UsernamePasswordAuthenticationToken authenticationToken= (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
        User user= (User) authenticationToken.getPrincipal();
        String string = user.getId().toString();
        //删除redis中的值
        redisCache.delete(string);
        return new ResponseResult(200,"注销成功");
    }
}
}

注意,在其中还应该删除SecurityContextHolder中保存的值,要不然就算登出,还是会保持认证状态的

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

buuctf——[ZJCTF 2019]NiZhuanSiWei

buuctf——[ZJCTF 2019]NiZhuanSiWei 1.绕过file_get_contents()函数 file_get_contents函数介绍 定义和用法 file_get_contents() 把整个文件读入一个字符串中。 该函数是用于把文件的内容读入到一个字符串中的首选方法。如果服务器操作系统支持&#xff0c;还会使用内存映射…

python实现将数据标准化到指定区间[a,b]+正向标准化+负向标准化

目录 一、公式介绍 (一)正向标准化公式 (二)负向标准化公式如下 (三)[a,b]取[0,1]的特例 二、构建数据集 三、自定义标准化函数 四、正向标准化 五、负向标准化 六、合并数据 一、公式介绍 将一列数据X标准化到指定区间[a,b] (一)正向标准化公式 nor_X(b-a)*(X-X_…

VUE-列表

VUE-列表 列表功能 如下例子 列表展示 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><meta http-equiv&qu…

网络分析工具

为了实现业务目标&#xff0c;每天都要在网络上执行大量操作&#xff0c;网络管理员很难了解网络中实际发生的情况、谁消耗的带宽最多&#xff0c;并分析是否正在发生任何可能导致带宽拥塞的活动。对于大型企业和分布式网络来说&#xff0c;这些挑战是多方面的&#xff0c;为了…

AI边缘计算盒子+ThingSense管理平台,推动明厨亮灶智慧监管新篇章

背景随着“互联网”时代的浪潮汹涌而至&#xff0c;国家及各地政府纷纷在“十四五”规划中明确指出&#xff0c;强化食品安全管理&#xff0c;利用技术手段实现智慧监管是刻不容缓的任务。为此&#xff0c;各地正加速推进“互联网明厨亮灶”的建设步伐&#xff0c;实现系统对接…

C# 报输入字符串格式不正确的原因

先放错误代码 23 class Voicewater 24 { 25 public void voicealarm(int tem) 26 { 27 Console.WriteLine("现在的温度是{}度了",tem); 28 } 29 } 解决方法…

14 Php学习:表单

表单 PHP 表单是用于收集用户输入的工具&#xff0c;通常用于网站开发。PHP 可以与 HTML 表单一起使用&#xff0c;用于处理用户提交的数据。通过 PHP 表单&#xff0c;您可以创建各种类型的表单&#xff0c;包括文本输入框、复选框、下拉菜单等&#xff0c;以便用户可以填写和…

Create an SAP Fiori App Using SAP Business Application Studio/连接时服务不可用

Create an SAP Fiori App Using SAP Business Application Studio 如果连接时遇到服务不可用 我们需要配置BTP上的连接。 参考文档 更改之后需要刷新 studio界面&#xff0c;重新选择就可以正常工作了

Linux 基于 UDP 协议的简单服务器-客户端应用

目录 一、socket编程接口 1、socket 常见API socket()&#xff1a;创建套接字 bind()&#xff1a;将用户设置的ip和port在内核中和我们的当前进程关联 listen() accept() 2、sockaddr结构 3、inet系列函数 二、UDP网络程序—发送消息 1、服务器udp_server.hpp initS…

【随笔】Git 高级篇 -- 远程服务器拒绝 git push reset(三十二)

&#x1f48c; 所属专栏&#xff1a;【Git】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &#x1f496; 欢迎大…

vue中使用水印

1. 在utils下创建watermark.js const watermark {}/**** param {要设置的水印的内容} str* param {需要设置水印的容器} container* param {需要设置水印的每一块的宽度} canWidth* param {需要设置水印的每一块的高度} canHeight* param {需要设置水印的字体} canFont* para…

【IDEA】JRebel LS client not configured

主要原因就是因为 jrebel 的版本跟 idea的版本对不上&#xff0c;或者说jrebel的版本比idea的版本还高&#xff0c;导致出现该错误 查看idea版本 ![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/a7ba43e6822947318cdb0d0e9d8d65e9.png 获取jrebel 版本 如何处理 …

“好”玩游戏让我本能痴迷游戏编程

源地址&#xff1a;https://www.ctvol.com/c-cdevelopment/5842.html 我的游戏生涯是从最开始的热血传奇开始的&#xff0c;那时候&#xff0c;我们年少轻狂&#xff0c;不知道多少80后的青春都洒在了这个游戏上面&#xff0c;那时候&#xff0c;热血传奇的热度比现在的英雄联…

【C++20】学习笔记:在事件驱动使用携程

2021年文章:比物理线程都好用的C++20的协程,在事件驱动代码中的应用解析 大神给出了可以用的例子:例子代码执行 源码 //https://bbs.huaweicloud.com/blogs/266537 #include <iostream> #include <vector>//这只是一个极简的模型示例,真实的代码要远比它复杂得…

在PostgreSQL中如何处理跨表的级联删除和更新?

文章目录 解决方案1. 使用外键约束和级联操作创建外键约束并指定级联删除创建外键约束并指定级联更新 2. 使用触发器&#xff08;Triggers&#xff09;创建触发器实现级联删除 示例代码示例1&#xff1a;使用外键约束和级联删除示例2&#xff1a;使用触发器实现级联删除 在Post…

Python根据公募基金在一定时期内持有的股票数据进行社会网络分析

【背景】根据提供的公募基金在一定时期内持有的股票数据&#xff0c;构建一个社会网络分析框架&#xff0c;度量每个基金在每年的度中心度、介数中心度和特征向量中心度&#xff0c;并对相关数据做出简要说明。 【代码】 import networkx as nx import pandas as pd import n…

Rust 语言 GUI 用户界面框架汇总(持续更新)

拜登&#xff1a;“一切非 Rust 项目均为非法”&#x1f60e; 什么是 GUI 图形用户界面&#xff08;Graphical User Interface&#xff0c;简称 GUI&#xff0c;又称图形用户接口&#xff09;是指采用图形方式显示的计算机操作用户界面。 现在的应用开发&#xff0c;是既要功…

机器人路径规划:基于Q-learning算法的移动机器人路径规划,可以自定义地图,修改起始点,提供MATLAB代码

一、Q-learning算法 Q-learning算法是强化学习算法中的一种&#xff0c;该算法主要包含&#xff1a;Agent、状态、动作、环境、回报和惩罚。Q-learning算法通过机器人与环境不断地交换信息&#xff0c;来实现自我学习。Q-learning算法中的Q表是机器人与环境交互后的结果&#…

设计模式系列:适配器模式

简介 适配器模式&#xff08;Adapter Pattern&#xff09;又称为变压器模式&#xff0c;它是一种结构型设计模式。适配器模式的目的是将一个类的接口转换成客户端所期望的另一种接口&#xff0c;从而使原本因接口不匹配而不能一起工作的两个类能够一起工作。 适配器模式有两种…

润开鸿与蚂蚁数科达成战略合作,发布基于鸿蒙的mPaaS移动应用开发产品

4月18日&#xff0c;江苏润和软件股份有限公司&#xff08;以下简称“润和软件”&#xff09; 旗下专注鸿蒙方向的专业技术公司及终端操作系统发行版厂商江苏润开鸿数字科技有限公司&#xff08;以下简称“润开鸿”&#xff09;与蚂蚁数科举行战略合作签约仪式&#xff0c;并发…
最新文章