Spring Security详细学习第一篇

news2024/12/27 12:53:38

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.coloradmin.cn/o/1605631.html

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

相关文章

单分支:if语句

示例&#xff1a; /*** brief how about if? show you here.* author wenxuanpei* email 15873152445163.com(query for any question here)*/ #define _CRT_SECURE_NO_WARNINGS//support c-library in Microsoft-Visual-Studio #include <stdio.h>#define if_state…

C语言学习/复习23---

一、数据的存储 二、数据类型的介绍 三、整型在内存中的存储 将原码转换为补码。如果数是正数&#xff0c;则补码与原码相同&#xff1b;如果数是负数&#xff0c;则先将原码按位取反&#xff0c;然后加1。将补码转换原补码。如果数是正数&#xff0c;则补码与原码相同&#x…

【笔试强训】双指针的思想!

1.数组中字符串的最小距离 题目链接 解题思路&#xff1a; 小技巧 ✌&#xff1a;标记两个字符串是否被找到&#xff0c;每次找到一个字符串就更新一次答案来保证找到的是最小距离。 实现代码&#xff1a; #include <iostream> using namespace std;int main() {in…

快手本地生活服务商系统怎么操作?

当下&#xff0c;抖音和快手两大短视频巨头都已开始布局本地生活服务&#xff0c;想要在这一板块争得一席之地。而这也很多普通人看到了机遇&#xff0c;选择成为抖音和快手的本地生活服务商&#xff0c;通过将商家引进平台&#xff0c;并向其提供代运营服务&#xff0c;而成功…

截图快捷键失效的解决方法 _ 统信UOS _ 麒麟KOS _ 中科方德NFS

原文链接&#xff1a;截图快捷键失效的解决方法 | 统信UOS | 麒麟KOS | 中科方德NFS Hello&#xff0c;大家好啊&#xff01;在日常使用计算机时&#xff0c;截图功能是我们经常需要用到的一个实用工具&#xff0c;它可以帮助我们快速保存屏幕上的信息&#xff0c;用于报告错误…

恭喜上岸的准研究生们,入学后还有这些奖学金

很多学校都开设了研究生的新生奖学金&#xff0c;有些学校是不分学校等级的全覆盖&#xff0c;比如北京科技大学前两年给研一新生每人发1万。 一般来说&#xff0c;新生奖学金的等级划分就是按考研成绩&#xff0c;所以大家一定要尽可能的考高的分数&#xff0c;不仅仅对评奖学…

云HIS医院管理系统源码 SaaS模式 B/S架构 基于云计算技术

一、系统概述 云HIS系统源码是一款满足基层医院各类业务需要的健康云产品。该系统能帮助基层医院完成日常各类业务&#xff0c;提供病患预约挂号支持、收费管理、病患问诊、电子病历、开药发药、住院检查、会员管理、财务管理、统计查询、医生工作站和护士工作站等一系列常规功…

累积分布函数图(CDF)的介绍、matlab的CDF图绘制方法(附源代码)

在对比如下两个误差的时候&#xff0c;怎么直观地分辨出来谁的误差更低一点&#xff1f;&#xff1a; 通过这种误差时序图往往不容易看出来。 但是如果使用CDF图像&#xff0c;以误差绝对值作为横轴&#xff0c;以横轴所示误差对应的累积概率为纵轴&#xff0c;绘制曲线图&am…

gitlab(docker)安装及使用

GitLab GitLab 是一个用于仓库管理系统的开源项目&#xff0c;使用Git作为代码管理工具&#xff0c;并在此基础上搭建起来的Web服务。 下载(docker) 查询docker镜像gitlab-ce gitlab-ce是它的社区版 [rootlocalhost ~]# docker search gitlab-ce NAME …

Xshell和XFtp下载和使用

Xshell和XFtp下载和使用 最好是官网直接下载。 链接: Xshell官网 Xshell官网最近出了免费个人使用版&#xff0c;但是我直接下载的话感觉非常非常慢&#xff0c;或许挂个梯子会好的多。看到图片的红色字没&#xff0c;可能被骗的人比较多。运行之前的Xshell会显示需要最新版的软…

python/pygame 挑战魂斗罗 笔记(二)

一、建立地面碰撞体&#xff1a; 现在主角Bill能够站立在游戏地图的地面&#xff0c;是因为我们初始化的时候把Bill的位置固定了self.rect.y 250。而不是真正的站在地图的地面上。 背景地图是一个完整的地图&#xff0c;没有地面、台阶的概念&#xff0c;就无法通过碰撞检测来…

Java中类装载的执行过程

类装载的执行过程 类从加载到虚拟机中开始&#xff0c;直到卸载为止&#xff0c;它的整个生命周期包括了&#xff1a;加载、验证、准备、解析、初始化、使用和卸载这7个阶段。其中&#xff0c;验证、准备和解析这三个部分统称为连接&#xff08;linking&#xff09;。 1.加载 …

《八》QSplitter拆分器以及QDockWidget窗口详解

QSplitter简介 QSplitter拆分器允许用户通过拖动子部件之间的边界来控制它们的大小。 单个拆分器可以控制任意数量的小部件。QSplitter的典型用法是创建几个小部件&#xff0c;并使用insertWidget()或addWidget()添加它们。 常用方法 默认情况下&#xff0c;QSplitter会动态…

ollama大语言模型

查看已经安装的大语言模型 ollama list运行大语言模型 ollama run llama2:latest

【前端面试3+1】16 TCP与UDP的区别、如何清除浮动、哪些原因造成阻塞页面渲染、【相同的树】

一、TCP与UDP的区别 TCP&#xff08;Transmission Control Protocol&#xff09;和UDP&#xff08;User Datagram Protocol&#xff09;是两种常用的网络传输协议&#xff0c;它们有以下几点区别&#xff1a; 1、连接性&#xff1a; TCP是面向连接的协议&#xff0c;通信双方在…

multiprocessing Process子进程会把非main的代码重新再次执行

from funasr import AutoModel print("2222")def play_audio(text):print(text)if __name__ __main__:audio_process multiprocessing.Process(targetplay_audio, args("1111",))audio_process.start() # 启动进程audio_process_id audio_process.pid解…

Android播放音频、视频

1、播放音频 <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:orientation"vertical"android:layout_width"match_parent"android:layout_height"match_parent" ><Buttonandroid:id"i…

【Python基础】Redis

文章目录 [toc]进入Redis交互模式Redis服务测试切换仓库字符串Key命令HashListSetZSet数据添加数据查询数据更新数据删除查询存在的所有key 个人主页&#xff1a;丷从心 系列专栏&#xff1a;Python基础 学习指南&#xff1a;Python学习指南 进入Redis交互模式 redis-cliRed…

ElasticSearch虚拟机安装(单机版)

1.下载7.10.2 下载链接&#xff0c;选择LINUX X86_64下载 2.创建用户 useradd es也可以使用系统默认用户&#xff08;非root&#xff09;,root用户会报错 3.解压 tar xvf elasticsearch-7.10.2-linux-x86_64.tar.gz假定目录在/home/es/elasticsearch-7.10.2-linux-x86_64 …

Springboot整合nacos报错无法连接nacos

报错信息&#xff1a;Nacos com.alibaba.nacos.api.exception.NacosException: Client not connected, current status:STARTING 关于这个报错的原因有很多&#xff1a;如Nacos未启动、网络问题、配置问题、版本不兼容问题等&#xff0c;我的报错原因主要是版本不兼容。 下面…