SpringSecurity实现前后端分离登录token认证详解

news2025/2/13 4:50:29

目录

1. SpringSecurity概述

1.1 权限框架

1.1.1 Apache Shiro

1.1.2 SpringSecurity

1.1.3 权限框架的选择

1.2 授权和认证

1.3 SpringSecurity的功能

2.SpringSecurity 实战

2.1 引入SpringSecurity

2.2 认证

2.2.1 登录校验流程

 2.2.2 SpringSecurity完整流程

 2.2.3 认证流程详解

2.3 思路分析

2.4 代码实战

2.4.1  自定义UserDetailsService类

2.4.2 密码加密存储

2.4.3 登录接口

2.4.4 token认证过滤器

2.4.5 接口测试


1. SpringSecurity概述

1.1 权限框架

目前市面上比较流行的权限框架主要实Shiro和Spring Security,这两个框架各自侧重点不同,各有各的优劣。

1.1.1 Apache Shiro

Apache Shiro(读作"sheeroh”,即日语"城")是一个开源安全框架,提供身份验证、授权、密码学和会话管理。Shiro框架直观、易用,同时也能提供健壮的安全性。

特点:

Shiro的特点:
1. 易于理解的Java Security APl;
2. 简单的身份认证(登录),支持多种数据源(LDAP,JDBC,Kerberos,ActiveDirectory等);·
3. 对角色的简单的签权(访问控制),支持细粒度的签权;
4. 支持一级缓存,以提升应用程序的性能;
5. 内置的基于POJO企业会话管理,适用于Web 以及非 Web的环境;
6.异构客户端会话访问;
7.非常简单的加密API;
8.不跟任何的框架或者容器捆绑,可以独立运行。

1.1.2 SpringSecurity

Spring Security是一个能够为基于Spring的企业应用系统提供描述性安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC(依赖注入,也称控制反转)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。

Spring Security是Spring家族中的一个安全管理框架。相比与另外一个安全框架Shiro,它提供了更丰富的功能,社区资源也比shiro丰富。一般Web应用的需要进行认证和授权。而认证和授权也是SpringSecurity作为安全框架的核心功能。

SpringSecurity的特点:

1. 与Spring Boot集成非常简单。
2. 功能强大,高度可定制化。
3. 支持OAuth2.0。
4. 强大的加密ARI。
5. 防止跨站请求伪造攻击(CSRF)。
6. 提供Spring Cloud分布式组件。

1.1.3 权限框架的选择

Spring Security 是 Spring 家族中的一个安全管理框架,实际上,在 Spring Boot 出现之前,Spring Security 就已经发展了多年了,但是使用的并不多,安全管理这个领域,一直是 Shiro 的天下。
相对于 Shiro,在 SSM 中整合 Spring Security 都是比较麻烦的操作,所以,SpringSecurity 虽然功能比 Shiro 强大,但是使用反而没有 Shiro 多(Shiro 虽然功能没有Spring Security 多,但是对于大部分项目而言,Shiro 也够用了)。自从有了 Spring Boot 之后,Spring Boot 对于 Spring Security 提供了自动化配置方案,可以使用更少的配置来使用 Spring Security。
 

1.2 授权和认证

一般来说,Web应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分,这两点也是SpringSecurity重要核心功能。

1)用户认证:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。


(2)用户授权:是验证某个用户是否有权限执行某个操作。经过认证后判断当前用户是否有权限进行某个操作。

下面进行介绍一下RBAC:

RBAC (Role Based Access Control)基于角色的访问控制,通过抽象出“用户、角色、权限”"三个概念,实现用户分配角色,角色分配权限的权限管理方式,也是目前企业中权限管理主要实现方案。

案例如下图所示:

 

1.3 SpringSecurity的功能

Spring Security对Web安全性的支持大量地依赖于Servlet过滤器。这些过滤器拦截进入请求,并且在应用程序处理该请求之前进行某些安全处理。 Spring Security提供有若干个过滤器,它们能够拦截Servlet请求,并将这些请求转给认证和访问决策管理器处理,从而增强安全性。

如今的Spring Security已经成为Spring Framework下最成熟的安全系统,它为我们提供了强大而灵活的企业级安全服务,如:

    Ø        认证授权机制

    Ø         Web资源访问控制

    Ø        业务方法调用访问控制

    Ø        领域对象访问控制Access Control List(ACL)

    Ø        单点登录(Central Authentication Service)

    Ø        信道安全(Channel Security)管理等功能

2.SpringSecurity 实战

2.1 引入SpringSecurity

导入依赖

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

引入依赖后我们在尝试去访问之前的接口就会自动跳转到一个SpringSecurity的默认登陆页面,默认用户名是user,密码会输出在控制台。必须登陆之后才能对接口进行访问。
 

2.2 认证

2.2.1 登录校验流程

登录校验流程如下图所示:

 2.2.2 SpringSecurity完整流程

SpringSecurity的原理其实就是一个过滤器链,内部包含了提供各种功能的过滤器。这里我们可以看看入门案例中的过滤器。

 

 UsernamePasswordAuthenticationFilter:负责处理我们在登陆页面填写了用户名密码后的登陆请求。入门案例的认证工作主要有它负责。

ExceptionTranslationFilter:处理过滤器链中抛出的任何AccessDeniedException和AuthenticationException。

FilterSecurityInterceptor:负责权限校验的过滤器。


我们可以通过Debug查看当前系统中SpringSecurity过滤器链中有哪些过滤器及它们的顺序。如下图所示:

 2.2.3 认证流程详解

 

Authentication接口::它的实现类,表示当前访问系统的用户,封装了用户相关信息。

AuthenticationManager接口:定义了认证Authentication的方法。

UserDetailsService接口:加载用户特定数据的核心接口。里面定义了一个根据用户名查询用户信息的方法。

UserDetails接口:提供核心用户信息。通过UserDetailsService根据用户名获取处理的用户信息要封装成UserDetails对象返回。然后将这些信息封装到Authentication对象中。
 

2.3 思路分析

在前后端分离中我们一般采用token验证,所以在这里我们要自定义登录接口,通过自定义接口调用调用ProviderManager的方法进行认证 ,如果认证通过生成jwt,然后将jwt返回给前端,同时将用户信息包括用户权限信息等存入到redis数据库中,redis数据库作为缓存读取速度远远大于从数据库中进行读取用户信息。我们还要自定义UserDetailsService,在这个实现类中去查询数据库。除此之外我们还需要定义JWT认证过滤器,从前端请求头中获取token,解析token获取其中的userid,然后根据userid从redis中获取用户信息存入SecurityContextHolder。具体流程如下图:

2.4 代码实战

2.4.1  自定义UserDetailsService类

首先定义一个UserDetails类代码如下:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginUser implements UserDetails, Serializable {
    private User user;
    //返回权限信息
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }
    //返回用户密码
    @Override
    public String getPassword() {
        return user.getPassword();
    }
       //返回用户账号
    @Override
    public String getUsername() {
        return user.getLoginname();
    }

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

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

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

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

自定义实现UserDetailsService接口,重写其中的方法。代码如下:

@Service
public class UserDetailServiceImpl implements UserDetailsService {
    @Autowired
    TbUserMapper tbUserMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //根据用户名从数据库中查询数据信息
        QueryWrapper<TbUser> queryWrapper=new QueryWrapper<>();
        queryWrapper.eq("username",username);
        TbUser tbUser = tbUserMapper.selectOne(queryWrapper);
        //查询不到该用户信息抛异常
        if(tbUser==null){
            throw new RuntimeException("用户名或者密码错误");
        }
       //封装成UserDetails返回
        return new MyUser(tbUser);
    }
}

2.4.2 密码加密存储

实际项目中我们不会把密码明文存储在数据库中。 
​1. 默认使用的PasswordEncoder要求数据库中的密码格式为:{id}password 。它会根据id去判断密码的加密方式。但是我们一般不会采用这种方式。所以就需要替换PasswordEncoder。​

2.我们一般使用SpringSecurity为我们提供的BCryptPasswordEncoder。

3.我们只需要使用把BCryptPasswordEncoder对象注入Spring容器中,SpringSecurity就会使用该PasswordEncoder来进行密码校验。

4.​我们可以定义一个SpringSecurity的配置类,SpringSecurity要求这个配置类要继承WebSecurityConfigurerAdapter。

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {


    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

}

2.4.3 登录接口

我们需要自定义登陆接口,然后让SpringSecurity对这个接口放行,让用户访问这个接口的时候不用登录也能访问。 
​ 在接口中我们通过AuthenticationManager的authenticate方法来进行用户认证,所以需要在SecurityConfig中配置把AuthenticationManager注入容器。 
​ 认证成功的话要生成一个jwt,放入响应中返回。并且为了让用户下回请求时能通过jwt识别出具体的是哪个用户,我们需要把用户信息存入redis有效时间为30分钟,可以把用户id作为key。登录接口代码如下:

Configuration代码如下:

@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Bean
    PasswordEncoder password(){
        return new BCryptPasswordEncoder();
    }

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

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()//关闭csrf
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) //不通过session获取SecurityContext
                .and()
                .authorizeRequests()
                .antMatchers("/user/login").anonymous() //对于登录接口允许匿名访问
                .anyRequest().authenticated(); //除上面外的所有请求全部需要鉴权认证
UsernamePasswordAuthenticationFilter.class);
    }
}

登录接口代码如下:



    @Autowired
    RedisTemplate redisTemplate;

    @Autowired
    AuthenticationManager authenticationManager;



   @PostMapping("/user/login")
    public Result login(@RequestBody TbUser tbUser){
        //通过AuthenticationManager的authenticate方法来进行用户认证
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken=new UsernamePasswordAuthenticationToken(tbUser.getUsername(),tbUser.getPassword());
        Authentication authenticate = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
        if(authenticate==null){
            return Result.error("401","登录校验失败");
        }
        else {
            //获取用户信息
            MyUser myUser = (MyUser) authenticate.getPrincipal();
            //获取用户id
            Long id = myUser.getTbUser().getId();
            //根据用户id生成token
            String token = JwtUtil.generateToken(id);
            //将token放在数据库中
            redisTemplate.opsForValue().set(String.valueOf(id),myUser,30, TimeUnit.MINUTES);
            return Result.success(token);
        }

    }

2.4.4 token认证过滤器
 

需要自定义一个过滤器,这个过滤器会去获取请求头中的token,对token进行解析取出其中的userid。使用userid去redis中获取对应的LoginUser对象。然后封装Authentication对象存入SecurityContextHolder。代码如下:

JwtAuthenticationTokenFilter自定义认证过滤器代码如下:
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    @Autowired
    RedisTemplate redisTemplate;

    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        //获取请求头中的token
        String token = httpServletRequest.getHeader("token");
        //如果token为空直接放行,由于用户信息没有存放在SecurityContextHolder.getContext()中所以后面的过滤器依旧认证失败符合要求
        if(!StringUtils.hasText(token)){
            filterChain.doFilter(httpServletRequest,httpServletResponse);
            return;
        }
        Long userId;
        try {
            //通过jwt工具类解析token获得userId,如果token过期或非法就会抛异常
            DecodedJWT decodedJWT = JwtUtil.decodeToken(token);
            userId = decodedJWT.getClaim("userId").asLong();
        }catch (Exception e){
            e.printStackTrace();
            throw new RuntimeException("token非法");
        }
        //根据userId从redis中获取用户信息,如果没有该用户就代表该用户没有登录过
        MyUser myUser = (MyUser) redisTemplate.opsForValue().get(String.valueOf(userId));
        if(Objects.isNull(myUser)){
            throw new RuntimeException("用户未登录");
        }
        //将用户信息存放在SecurityContextHolder.getContext(),后面的过滤器就可以获得用户信息了。
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken=new UsernamePasswordAuthenticationToken(myUser,null,null);
        SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
        filterChain.doFilter(httpServletRequest,httpServletResponse);
    }
}

在Configuration类中配置自定义过滤器,代码如下:

@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Autowired
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
 //自定义过滤器放在UsernamePasswordAuthenticationFilter过滤器之前
            http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

    }
}

2.4.5 接口测试

在访问/user/login接口之前,其余的接口都不能访问到,然后先登录访问/user/login接口,测试结果如下图:

 

然后携带请求头token访问其它方法就可以正常的进行访问了,由此可见我们的代码测试验证成功。

至此该篇文章SpringSecurity认证内容结束,下一篇文章作者将继续写SpringSecurity授权内容,未完待续。


 

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

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

相关文章

翻译的技巧

400字左右的文章中划出5个句子&#xff0c; 30分钟内将其翻译成中文&#xff0c;分值10分。文章的题材大多是有关政治、经济、文化、教育、科普以及社会生活&#xff0c;议论文为主&#xff0c;说明文为辅&#xff0c;结构严谨&#xff0c;逻辑性强&#xff0c;长难句较多。不仅…

基于深度学习的高精度人脸口罩检测识别系统(PyTorch+Pyside6+YOLOv5模型)

摘要&#xff1a;基于深度学习的高精度人脸口罩检测识别系统可用于日常生活中或野外来检测与定位人脸口罩目标&#xff0c;利用深度学习算法可实现图片、视频、摄像头等方式的人脸口罩目标检测识别&#xff0c;另外支持结果可视化与图片或视频检测结果的导出。本系统采用YOLOv5…

mcu:利用Cortex-M中的DWT实现高精度计时

1、Cortex-M中的DWT 在Cortex-M里面有一个外设叫DWT(Data Watchpoint and Trace)&#xff0c;是用于系统调试及跟踪。 它有一个32位的寄存器叫CYCCNT&#xff0c;它是一个向上的计数器&#xff0c;记录的是内核时钟运行的个数&#xff0c;内核时钟跳动一次&#xff0c;该计数器…

node-red 部署案例指导

配置node-red静态资源目录 找到 node-red 的设置文件 可以在启动日志中看到 以我的为例 C:\Users\fizz\.node-red\settings.js 我们在.node-red目录创建一个static目录。用于存放静态文件。 然后修改setting.js的 httpStatic: ‘C:/Users/fizz/.node-red/static/’, 重启…

探索戴森在科技行业的统治地位:分析其后吹风机的成功

2016年&#xff0c;戴森&#xff08;Dyson&#xff09;公司推出重新定义审美与功能的吹风机Supersonic&#xff0c;定价为3000元&#xff0c;七年来&#xff0c;Supersonic不仅没有因其价格远高于竞品而被市场淘汰&#xff0c;反而稳居国内市场吹风机的“龙头宝座“&#xff0c…

低秩矩阵(Low-Rank)的意义

&#xff11;&#xff0e;回顾基础&#xff1a; 矩阵的秩度量的是矩阵行列之间的相关性&#xff0c;如果各行各列都是线性无关的&#xff0c;矩阵就是满秩。非零元素的行或列决定了秩的大小。&#xff0f;&#xff0f;划重点&#xff0c;秩可以度量矩阵自身相关性 讲个小故事…

调用阿里API实现全国快递物流查询

作者介绍 王梅(姓名)&#xff0c;女&#xff08;性别&#xff09;&#xff0c;西安工程大学电子信息学院&#xff0c;2022级研究生 研究方向&#xff1a;机器视觉与人工智能 电子邮件&#xff1a;1095647386qq.com 王泽宇&#xff0c;男&#xff0c;西安工程大学电子信息学院…

一文让你轻松拿捏 Spring MVC

博主介绍&#xff1a; ✌博主从事应用安全和大数据领域&#xff0c;有8年研发经验&#xff0c;5年面试官经验&#xff0c;Java技术专家✌ Java知识图谱点击链接&#xff1a;体系化学习Java&#xff08;Java面试专题&#xff09; &#x1f495;&#x1f495; 感兴趣的同学可以收…

高完整性系统——霍尔逻辑

文章目录 霍尔三元组案例1案例2 逻辑推导规则forward v.s. backwardforwardbackward rule of assignmentrules of consequence结合上述两个 rulerule of sequencing更大的程序案例skip ruleconditional rule案例 要证明这个程序需要从上往下进行&#xff0c;先单独证明 f:1, i:…

你“被”全链路了么?全链路压测实践之理论

要说当下研发领域最热门的几个词&#xff0c;全链路压测 肯定跑不了。最近的几次大会上&#xff0c;也有不少关于全链路的议题。之前有朋友在面试过程中也有被问到了什么是全链路压测&#xff0c;如何有效的开展全链路压测。今天我们就来聊聊全链路压测&#xff0c;但本文不会涉…

Redis.conf 详解

我们启动 Redis&#xff0c;一般都是通过 Redis.conf 启动。 因此&#xff0c;我们必须了解 Redis.conf 的配置&#xff0c;才能更好理解和使用 Redis。 单位 单位注意事项&#xff1a;当需要内存大小时&#xff0c;可以指定为1k 5GB 4M等 通常形式&#xff1a; 1k > 1000字…

搜索在计算机中的地位十分重要

无论是在内部系统还是在外部的互联网站上&#xff0c;都少不了检索系统。数据是为了用户而服务。计算机在采集数据&#xff0c;处理数据&#xff0c;存储数据之后&#xff0c;各种客户端的操作pc机或者是移动嵌入式设备都可以很好的获取数据&#xff0c;得到 想要的数据服务。 …

k8s学习-CKS考试必过宝典

目录 CKS考纲集群安装&#xff1a;10%集群强化&#xff1a;15%系统强化&#xff1a;15%微服务漏洞最小化&#xff1a;20%供应链安全&#xff1a;20%监控、日志记录和运行时安全&#xff1a;20% 报名模拟考试考试注意事项考前考中考后 参考 CKS考纲 集群安装&#xff1a;10% 使…

数据库技术及应用小科普(附部分例题)

数据库的基础 介绍 &#xff08;手机撰写&#xff0c;多有不便&#xff0c;求铁铁们多多包涵&#xff09;图书目录部分期末习题 介绍 &#xff08;手机撰写&#xff0c;多有不便&#xff0c;求铁铁们多多包涵&#xff09; 内容简介 《数据库技术及应用教程》系统地介绍了数据库…

【每日挠头算法题(5)】重新格式化字符串|压缩字符串

欢迎~ 一、重新格式化字符串思路1&#xff1a;构造模拟具体代码如下&#xff1a; 思路2&#xff1a;双指针法具体代码如下&#xff1a; 二、字符串压缩思路1&#xff1a;简单替换 总结 一、重新格式化字符串 点我直达~ 思路1&#xff1a;构造模拟 1.遍历字符串&#xff0c;…

iOS横竖屏切换

基础概念UIDeviceOrientationUIInterfaceOrientationUIInterfaceOrientationMaskUIViewController相关AppDelegate相关工程配置相关 横竖屏切换实例竖屏界面如何present横屏界面竖屏界面如何push横屏界面横屏竖切换机制分析系统如何知道App对界面朝向的支持不同界面的朝向控制自…

Qt学习06:QPainter绘画

文章首发于我的个人博客&#xff1a;欢迎大佬们来逛逛 Qt学习06&#xff1a;QPainter绘画 Qt绘图 Paint System Qt的绘制系统支持在屏幕和打印设备上使用相同的API进行绘制&#xff0c;主要基于QPainter、QPaintDevice和QPaintEngine类。 QPainter用于执行绘图操作&#xff…

JAVA基础 - SPI机制使用详解(三)

简述 SPI&#xff08;Service Provider Interface的缩写&#xff09; 意思是&#xff1a;“服务提供者的接口”&#xff0c;专门提供给服务提供者或者扩展框架功能的开发者去使用的接口。SPI 将服务接口和服务实现分离开来&#xff0c;将服务调用方和服务实现方进行解耦&#…

Rocketmq面试(四)RocketMQ 的推模式和拉模式有什么区别?

一、PUSH模式 public class Consumer {public static void main(String[] args) throws InterruptedException, MQClientException {// 初始化consumer&#xff0c;并设置consumer group nameDefaultMQPushConsumer consumer new DefaultMQPushConsumer("please_rename_…

基于STM32的重力感应售货机系统设计

一、项目介绍 随着智能物联网技术的不断发展&#xff0c;人们的生活方式和消费习惯也正在发生改变。如今越来越多的人习惯于在线购物、自助购物等新型消费模式&#xff0c;因此智能零售自助柜应运而生。 本项目设计开发一款基于STM32主控芯片的智能零售自助柜&#xff0c;通过…