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

目录

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授权内容,未完待续。


 

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

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

相关文章

Splashtop 与 Pax8 合作为 MSP 提供简化的远程支持解决方案

2023年4月27日 科罗拉多州丹佛 Pax8 是一个行业领先的云商务市场&#xff0c;该公司今天宣布将通过 Pax8 市场在全球推出其全新运营供应商 Splashtop。Splashtop 的远程访问、支持以及端点监控和管理解决方案极具成本效益&#xff0c;而且功能强大&#xff0c;可以助力托管服务…

002、体系结构之TiDB Server

TiDB Server 1、TiDB总览1.1、TiDB Server架构1.2、TiDB Server 主要功能&#xff1a; 2、SQL语句处理语句的解析和编译SQL层协议层上下文解析层逻辑优化器物理优化器本地执行器分布式执行器 3、如何将表的数据转成kv形式4、在线DDL相关模块5、GC机制与相关模块6、TiDB Server …

你真的会写软件测试简历吗?为什么面试约不到,测试老鸟的建议...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 写好一份简历的三…

Frida技术—逆向开发的屠龙刀

简介 Frida是一种基于JavaScript的动态分析工具&#xff0c;可以用于逆向开发、应用程序的安全测试、反欺诈技术等领域。Frida主要用于在已安装的应用程序上运行自己的JavaScript代码&#xff0c;从而进行动态分析、调试、修改等操作&#xff0c;能够绕过应用程序的安全措施&a…

mac下部署和访问 Kubernetes 仪表板(Dashboard)

简介 Dashboard 是基于网页的 Kubernetes 用户界面。 你可以使用 Dashboard 将容器应用部署到 Kubernetes 集群中&#xff0c;也可以对容器应用排错&#xff0c;还能管理集群资源。 你可以使用 Dashboard 获取运行在集群中的应用的概览信息&#xff0c;也可以创建或者修改 Kub…

QT--配置Opencv

提示&#xff1a;本文为学习记录&#xff0c;若有疑问&#xff0c;请及时联系作者。 文章目录 前言一、下载已编译的opencv1..解压2..path路径 二、使用步骤1..pro文件2..h文件 总结 前言 只做第一个我&#xff0c;不做第二个谁。 一、下载已编译的opencv 适用于mingw编译器…

NoSQL数据库

NoSQL数据库 NoSQL简介NoSQL兴起的原因NoSQL与关系数据库的对比NoSQL的四大类型键值数据库列族数据库文档数据库图形数据库不同类型数据库比较分析RedisMongoDBCassandraNeo4j NoSQL三大基石CAPBASE最终一致性 NoSQL简介 “Not Only SQL”泛指非关系型的数据库&#xff0c;区别…

07_scrapy的应用——获取电影数据(通过excel保存静态页面scrapy爬虫数据的模板/通过数据库保存)

0、前言: 一般我们自己创建的一些python项目,我们都需要创建虚拟环境,其中会下载很多包,也叫做依赖。但是我们在给他人分享我们的项目时,不能把虚拟环境打包发送给别人,因为每个人电脑系统不同,我们可以把依赖导出为依赖清单,然后别人有了我们的依赖清单,就可以用一条…

项目使用tensorflow2会出错,下载并使用tensorflow1

背景&#xff1a;使用pycharm安装总显示安装失败&#xff0c;使用pip安装也不行&#xff0c;只能使用conda配置虚拟环境手动安装 1、下载安装anaconda 官网下载&#xff0c;双击安装。用anaconda就是想使用虚拟环境&#xff0c;万一没弄好直接删了重新搞就行。 2、创建虚拟环境…

Https加密超文本传输协议的运用

1.https的相关知识 1.1 https的简介 HTTPS &#xff08;全称&#xff1a;Hypertext Transfer Protocol Secure &#xff09;&#xff0c;是以安全为目标的 HTTP 通道&#xff0c;在HTTP的基础上通过传输加密和身份认证保证了传输过程的安全性 。HTTPS 在HTTP 的基础下加…

使用POI实现JAVA操作Excel

Apache POI POI提供API给JAVA程序对Microsoft Office格式档案读和写的功能 POI工具介绍 POI 是用Java编写的免费开源的跨平台的 Java API&#xff0c;Apache POI提供API给Java程式对Microsoft Office格式档案读和写的功能。主要是运用其中读取和输出excel的功能。 POI官网地…

常用设计模式(三)

接着之前的思路今天来介绍一下常用的设计模式有哪些 单例模式&#xff08;Singleton Pattern&#xff09; 又称为单体模式&#xff0c;保证一个类只有一个实例&#xff0c;并提供一个访问它的全局访问点。也就是说&#xff0c;第二次使用同一个类创建新对象的时候&#xff0c…

es相关的知识点

海量数据下如何提升es的操作性能 .filesystemcache os cache操作系统缓存 es中的数据,实际上写入磁盘,磁盘文件的操作系统,实际上会将数据写入到oscache中 es的搜索引擎严重依赖于底层的filesystemcache 如果filesystemcache的内存足够大,可以容纳所有的index segmentfile索引…

ABB CI546 3BSE012545R1 模块

ABB CI546 3BSE012545R1 模块. ABB CI546 3BSE012545R1 模块 电子电工技术的电力系统分析 1电子电工技能特色 电子电工技能是凭仗计算机技能开展起来的&#xff0c;并朝着智能化、网络化的方向开展。随着时代的开展&#xff0c;新式技能不断涌现&#xff0c;使传统电工技能运用…

基于 opencv 的人脸识别上课考勤系统,附源码,可作为毕业设计

一、简介 这个人脸识别考勤签到系统是基于大佬的人脸识别陌生人报警系统二次开发的。 项目使用Python实现&#xff0c;基于OpenCV框架进行人脸识别和摄像头硬件调用&#xff0c;同时也用OpenCV工具包处理图片。交互界面使用pyqt5实现。 该系统实现了从学生信息输入、人脸数据…

【微服务】SpringBoot 插件化开发模式详细总结

目录 一、前言 1.1 使用插件的好处 1.1.1 模块解耦 1.1.2 提升扩展性和开放性 1.1.3 方便第三方接入 1.2 插件化常用实现思路 二、Java常用插件实现方案 2.1 serviceloader方式 2.1.1 java spi 2.1.2 java spi 简单案例 2.2 自定义配置约定方式 2.2.1 添加配置文件…

【备战秋招】每日一题:4月15日美团春招:题面+题目思路 + C++/python/js/Go/java带注释

2023大厂笔试模拟练习网站&#xff08;含题解&#xff09; www.codefun2000.com 最近我们一直在将收集到的各种大厂笔试的解题思路还原成题目并制作数据&#xff0c;挂载到我们的OJ上&#xff0c;供大家学习交流&#xff0c;体会笔试难度。现已录入200道互联网大厂模拟练习题&a…

【深入浅出 Spring Security(九)】解决跨域问题和 Axios 所需配置

跨域 一、SpringMVC 跨域的解决方案CrossOrigin&#xff08;注解的方式解决&#xff09;addCorsMappings&#xff08;实现WebMvcConfigurer接口&#xff0c;重写方法&#xff09; 二、Spring Security 跨域的解决方案前后端跨域测试&#xff08;前端相关配置&#xff09; 啥是跨…

怎样才算一个计算机知识体系完整的毕业生

为什么突然想写这个话题呢&#xff1f; 最近有不少新关注的读者&#xff0c;在后台问&#xff1a;大学学 Java 和 C 哪个好找工作&#xff0c;学前端好还是后端好&#xff0c;该学 Vue 还是 React。。。 仿佛看到了自己当年的模样&#xff0c;所以觉得有必要单独写一篇文章&a…

【数据结构与算法】02 栈 (栈的多重含义,静态、动态数组栈(顺序栈),链式栈,双端栈,括号匹配)

一、栈的多重含义1.1 硬件栈1.2 运行时栈1.3 软件栈1.4 技术栈1.5 TCP/IP协议栈 二、数据结构中的栈2.1 概念2.2 栈的操作2.3 数组栈&#xff08;顺序栈&#xff09;2.31 数组栈特性2.32 C语言实现▶ 静态数组栈▶ 动态数组栈 2.4链式栈2.41 链式栈特性2.42 C语言实现 三、进阶…