Spring Security详细讲解(JWT+SpringSecurity登入案例)

这篇具有很好参考价值的文章主要介绍了Spring Security详细讲解(JWT+SpringSecurity登入案例)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一.SpringSecurity简介

1.SpringSecurity

SpringSecurity 是一个功能强大且高度可定制的身份验证和访问控制框架。它是保护基于 Spring 的应用程序的事实上的标准。
SpringSecurity 是一个致力于为 Java 应用程序提供身份验证和授权的框架。像所有 Spring 项目一样,Spring Security 的真正强大之处在于它可以如何轻松地扩展以满足自定义需求

官网地址:
Spring Security简介https://spring.io/projects/spring-security

Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
Spring Security英文教程:https://docs.spring.io/spring-security/reference/index.html
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
Spring Security中文教程:https://docs.gitcode.net/spring/guide/spring-security/overview.html
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证

2.SpringSecurity相关概念

  • 什么是SpringSecurity

SpringSecurity 是一个提供身份验证、授权和防止常见攻击的框架 。它对保护命令式和反应式应用程序都提供了一流的支持,是保护基于 Spring 的应用程序的事实上的标准。

  • 关于什么是认证和授权

​ 认证:验证当前访问系统的是不是本系统的用户,并且要确认具体是哪个用户

​ 授权:经过认证后判断当前用户是否有权限进行某个操作

  • SpringSecurity 特点

Spring 无缝整合,全面的权限控制,专门为 Web 开发而设计(旧版本不能脱离 Web 环境使用,新版本对整个框架进行了分层抽取,分成了核心模块和 Web 模块。单独引入核心模块就可以脱离 Web 环境),重量级。

  • SpringSecurity和Shiro的比较

功能,社区资源上SpringSecurity远远优于Shiro,但是Shiro是轻量级更加容易上手,在SSM框架中整合 Spring Security 比较麻烦,但是在SpringBoot项目中 Spring Security 提供了自动化配置方案,可以使用更少的配置来使用 Spring Security,所以推荐在SSM框架中使用Shiro,在SpringBoot和SpringCloud中使用SpringSecurity,你可以通过这篇博文了解这二种方式的实现SpringBoot学习—SpringSecurity与Shiro。

二.认证和授权

1.认证

SpringSecurity 为身份验证提供了全面的支持。身份验证是我们验证试图访问特定资源的用户身份的方式。对用户进行身份验证的一种常见方法是要求用户输入用户名和密码。一旦执行了身份验证,我们就知道了身份并可以执行授权。SpringSecurity 内置了对用户身份验证的支持。

(1) 使用SpringSecurity进行简单的认证(SpringBoot项目中)

创建一个SpringBoot的Web项目,功能非常简单就是通过访问sayHello接口,在游览器输出字符串 "Hello,SpringSecurity" :
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
项目pom.xml依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.dudu</groupId>
    <artifactId>springsecuritydemo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springsecuritydemo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <!--springBootWeb依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!--test测试-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

controller下创建SpringSecurityController:
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证

运行项目,访问sayHello接口,搞定:
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证

接下来在项目的pom.xml配置文件中导入SpringSecurity的依赖:

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

再次访问sayHello接口,就会跳转到一个登入界面,在项目中我们并没有编写该登入界面的代码,其实这就是SpringSecurity 内置的用户身份验证:
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
身份验证默认的账号为:user,密码在项目启动的时候在控制台会打印,注意每次启动的时候密码会发生变化!
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证

输入账号和密码后点击登入:
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
登入后,就能够成功访问sayHello接口了:
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证

(2) SpringSecurity的原理

SpringSecurity的原理其实就是一个过滤器链,内部包含了提供各种功能的过滤器,下图列出的是该过滤链中比较重要的几个过滤器。

Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
上图中的三个过滤器分别是:

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

在项目启动类中,按下图所示进行debug,就可以观察到这个过滤链,过滤链上一共有16个过滤器( run.getBean(DefaultSecurityFilterChain.class) )
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
这16个过滤器分别是:
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证

(3) SpringSecurity核心类

📢 注意,下面几个知识点来源于这篇博文:https://www.w3cschool.cn/springsecurity/ted11ii1.html,如果需要更详细的了解请查阅原文。

  • Authentication

Authentication 是一个接口,用来表示用户认证信息的,在用户登录认证之前相关信息会封装为一个 Authentication 具体实现类的对象,在登录认证成功之后又会生成一个信息更全面,包含用户权限等信息的 Authentication 对象,然后把它保存在 SecurityContextHolder 所持有的 SecurityContext 中,供后续的程序进行调用,如访问权限的鉴定等。

  • SecurityContextHolder

SecurityContextHolder 是用来保存 SecurityContext 的。SecurityContext 中含有当前正在访问系统的用户的详细信息。默认情况下,SecurityContextHolder 将使用 ThreadLocal 来保存 SecurityContext,这也就意味着在处于同一线程中的方法中我们可以从 ThreadLocal 中获取到当前的 SecurityContext。因为线程池的原因,如果我们每次在请求完成后都将 ThreadLocal 进行清除的话,那么我们把 SecurityContext 存放在 ThreadLocal 中还是比较安全的。这些工作 Spring Security 已经自动为我们做了,即在每一次 request 结束后都将清除当前线程的 ThreadLocal。

  • AuthenticationManager

AuthenticationManager 是一个用来处理认证(Authentication)请求的接口。在其中只定义了一个方法 authenticate(),该方法只接收一个代表认证请求的 Authentication 对象作为参数,如果认证成功,则会返回一个封装了当前用户权限等信息的 Authentication 对象进行返回。

  Authentication authenticate(Authentication authentication) throws AuthenticationException;
  • AuthenticationProvider

在 Spring Security 中,AuthenticationManager 的默认实现是 ProviderManager,而且它不直接自己处理认证请求,而是委托给其所配置的 AuthenticationProvider 列表,然后会依次使用每一个 AuthenticationProvider 进行认证,如果有一个 AuthenticationProvider 认证后的结果不为 null,则表示该 AuthenticationProvider 已经认证成功,之后的 AuthenticationProvider 将不再继续认证。然后直接以该 AuthenticationProvider 的认证结果作为 ProviderManager 的认证结果。如果所有的 AuthenticationProvider 的认证结果都为 null,则表示认证失败,将抛出一个 ProviderNotFoundException。

  • UserDetailsService

UserDetailsService是一个加载用户特定数据的核心接口,登录认证的时候 Spring Security 会通过 UserDetailsService 的 loadUserByUsername() 方法获取对应的 UserDetails 进行认证,认证通过后会将该 UserDetails 赋给认证通过的 Authentication 的 principal,然后再把该 Authentication 存入到 SecurityContext 中。

  • UserDetails

UserDetails是一个提供核心用户信息的接口,通过 UserDetailsService 的 loadUserByUsername() 方法获取,然后将该 UserDetails 赋给认证通过的 Authentication 的 principal。

常用方法:

方法名 解释
Collection<?extendsGrantedAuthority> getAuthorities(); 表示获取登录用户所有权限
String getPassword(); 表示获取密码
String getUsername(); 表示获取用户名
boolean isAccountNonExpired(); 表示判断账户是否过期
boolean isAccountNonLocked(); 表示判断账户是否被锁定
boolean isCredentialsNonExpired(); 表示凭证{密码}是否过期
boolean isEnabled(); 表示当前用户是否可用
  • PasswordEncoder

Spring Security 的PasswordEncoder接口用于执行密码的单向转换,以允许安全地存储密码。给定PasswordEncoder是单向转换,当密码转换需要双向(即存储用于对数据库进行身份验证的凭据)时,并不打算这样做。通常PasswordEncoder用于存储需要与用户在身份验证时提供的密码进行比较的密码

常用方法:

方法名 解释
String encode(CharSequence rawPassword); 表示把参数按照特定的解析规则进行解析
boolean matches(CharSequence rawPassword, String encodedPassword); 表示验证从存储中获取的编码密码与编码后提交的原始密码是否匹配。如果密码匹配,则返回true;如果不匹配,则返回false。第一个参数表示需要被解析的密码。第二个参数表示存储的密码。
default boolean upgradeEncoding(String encodedPassword) 表示如果解析的密码能够再次进行解析且达到更安全的结果则返回true,否则返回false。默认返回false。

内置的PasswordEncoder实现列表:

实现类 说明
NoOpPasswordEncoder(已废除) 明文密码加密方式,该方式已被废除(不建议在生产环境使用),不过还是支持开发阶段测试Spring Security的时候使用。
BCryptPasswordEncoder 使用广泛支持的bcrypt 算法来散列密码
Argon2Passwordencoder 使用Argon2 算法来散列密码, 是一种故意缓慢的算法,需要大量内存
PBKDF2PASSWORDENCODER 使用PBKDF2 算法来散列密码,是一种故意缓慢的算法
SCryptPasswordEncoder 使用scrypt算法来散列密码,是一种故意缓慢的算法,需要大量内存

SpringSecurity5.x版本默认的PasswordEncoder方式改成了DelegatingPasswordEncoder委托类,这是因为在5.0之前默认采用的NoOpPasswordEncoder,存在如下问题:

  1. 有许多应用程序使用旧的密码编码,无法轻松地进行迁移。
  2. 密码存储的最佳实践将再次改变。
  3. 作为一种框架 Spring,安全不能频繁地进行破坏更改

SpringSecurity引入了DelegatingPasswordEncoder,它通过以下方式解决了所有问题:

  1. 确保使用当前的密码存储建议对密码进行编码
  2. 允许验证现代和遗留格式的密码。
  3. 允许在将来升级编码

DelegatingPasswordEncoder委托支持动态的多种密码加密方式,它内部其实是一个Map集合,根据传递的Key(Key为加密方式)获取Map集合的Value,而Value则是具体的PasswordEncoder实现类。

方式1:创建默认的代理 PasswordEncoder(SpringSecurity5.x)

Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
我们来看看createDelegatingPasswordEncoder()的源码:

从下图我们可以看出使用PasswordEncoderFactories.createDelegatingPasswordEncoder()创建的PasswordEncoder,默认加密方式是bcrypt,PasswordEncoder的默认实现类是BCryptPasswordEncoder

Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证

运行效果:

从下图我们可以看出密码的一般格式为: {id}encodedPassword,如果使用的NoOpPasswordEncoder编码的话(不推荐使用该编码),数据库中的密码格式为 {noop}密码 ,如 {noop}123456 。

Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证

方式2:创建自定义代理 PasswordEncoder

Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
运行效果:
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证

实际项目中如果不采用默认方式,可以通过@Bean的方式来统一配置全局共用的PasswordEncoder,如下所示:
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证

上图所示在配置中还重新配置了users
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证

就一个用户,用户名为user,密码123456,使用该用户进行登入:
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
登入成功:
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证

(4) 认证登入案例(JWT+SpringSecurity实现登入案例)

📢 注意,下面的图来源于https://www.bilibili.com/video/BV1mm4y1X7Hc?p=1,并且该视频详细讲解了JWT+SpringSecurity的知识,如果想更详细的了解,跳转观看即可,关于JWT的知识你可以通过这篇博文->JWT详细讲解(保姆级教程)进行学习。

SpringSecurity的认证流程图如下,如果不按默认方式进行,将对应接口的实现类换成自己的实现类即可:
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
JWT的流程图如下:
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证

JWT+SpringSecurity实现登入案例:

案例来源于github上mall项目的学习教程,这个案例的数据并没有从Redis中获取,而是从数据库中获取,之所以说没有用Redis进行获取是因为前面推荐的https://www.bilibili.com/video/BV1mm4y1X7Hc?p=1视频就是采用Redis进行存储。

Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证

下面给出关键代码来了解这个认证流程

项目中有一个SecurityConfig类,表示SpringSecurity的配置
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
这个configure配置是整个Security Config的配置(过滤器链的配置),下图红框中的配置表示在UsernamePasswordAuthenticationFilter过滤器前添加一个过滤器。
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
这个过滤器是继承OncePerRequestFilter过滤器(之所以继承 OncePerRequestFilter 因为OncePerRequestFilter 一个请求只被过滤器拦截一次。请求转发不会第二次触发过滤器 ,而Filter会触发二次)
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
该过滤器存放在component文件下的JwtAuthenticationTokenFilter类
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证

程序运行后,过滤器会一个一个的执行当执行到UsernamePasswordAuthenticationFilter过滤器之前会先执行自定义的JwtAuthenticationTokenFilter过滤器中的doFilterInternal()方法:
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
该方法业务逻辑非常清晰,就是判断是否在请求头中存在JWT的Token,如果存在( 用户已经登入过了 )就从token的载荷中获取用户名,然后再根据UserDetaulsService的loadUserByUsername(username)获取用户信息 (UserDetaulsService被替换成从数据库中获取用户信息,详细代码如下图所示 ) 然后进行认证,如果不存在( 该请求为登入请求 ),用户的信息从登入界面获取( 用户名,密码 )然后进行认证。
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
这里的AdminUserDetails其实就是UserDetails接口的实现类(进行了扩展):
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证

自定义的JwtAuthenticationTokenFilter过滤器放行后,就会执行UsernamePasswordAuthenticationFilter过滤器,执行过程如下图:
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
下面通过Postman进行模拟
用户首次登入,访问http://localhost:8089/admin/login接口,并携带账号和密码:
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
在自定义JwtAuthenticationTokenFilter过滤器的doFilterInternal()方法中打断点,进行观察,因为首次登入头部没有信息,所以authHeader=null,最后 chain.doFilter(request, response);进行放行
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
接下来进行认证,代码会执行业务层的login(String username, String password)进行登入操作:
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
通过userDetailsService.loadUserByUsername(username)获取UserDetails对象,执行该方法会进入到SecurityConfig配置文件下的userDetailsService()中,从数据库中获取用户UmsAdmin信息和权限:
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
此时控制台输出结果:
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
从数据库中获取到的用户数据:
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
通过PasswordEncoder进行密码匹配
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
匹配成功,封装Authentication对象:
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
然后将Authentication保存到 SecurityContextHolder中
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
前面提到过UserDetails 会赋给认证通过的 Authentication 的 principal,确实如此
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
然后通过Token工具类生成Token字符串,并返回:
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
虽然传递的UserDetails对象,实际上只将用户名作为JWT的载荷( JWT中不要传入敏感信息 ):
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证

最后收到返回的Token并将Token返回给前端:
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
前端收到服务端的Token信息:
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
前端收到token信息后将JWT的token信息放在请求头中,然后再去访问相应接口,这里访问http://localhost:8089/admin/permission/1接口

Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
再次经过自定义过滤器的时候,就可以获取该请求头中的数据Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
从请求头中获取关键信息,用户名和JWT的token:
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
如果解析出的username不为null,就通过UserDetailsService.loadUserByUsername(username)获取UserDetails:
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
一样的,执行UserDetailsService.loadUserByUsername(username)还是执行我们编写的userDetailsService() ,从数据库中获取

Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
获取到的UserDetails对象
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证

控制台输出信息:
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
然后通过JWT的工具类进行验证:
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
验证通过后封装Authenticationm,并将authentication保存在SecurityContextHolder中
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
然后放行执行其他过滤器:
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证

同样的在业务层中实现业务代码,并返回给controller层:
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
controller层收到后返回给前端:
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证

Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证

2.授权

​ 在SpringSecurity中,会使用默认的FilterSecurityInterceptor来进行权限校验。在FilterSecurityInterceptor中会从SecurityContextHolder获取其中的Authentication,然后获取其中的权限信息,具体操作如下:

(1) 加入权限到Authentication中

所以我们在项目中只需要把当前登录用户的权限信息也存入Authentication中就可以了,在前面的项目中我们已经将权限写入到Authentication中了,就是在获取UserDetails的时候,将权限信息也写入到UserDetails中,然后再封装到Authentication中,整个过程如下:

将权限信息写入到UserDetails中
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
再将UserDetails封装到Authentication中
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
AdminUserDetails是UserDetails的接口实现类
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证

(2) SecurityConfig配置文件中开启注解权限配置

使用 @EnableGlobalMethodSecurity(prePostEnabled=true) 注解
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证

(3) 给接口中的方法添加访问权限

使用 @PreAuthorize("hasAuthority('pms:brand:read')") 注解
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证

重新运行后,访问 http://localhost:8089/admin/permission/1 接口 ( 不需要再登入,因为默认JWT失效时间为1周 ),由于该用户没有权限访问,所以访问失败:
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
接下来更换用户test,密码为123456进行登入:
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证

在http://localhost:8089/admin/permission/1中更换请求头的token信息:
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
再次访问接口(访问成功):
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证

(4) 用户权限表的建立

RBAC权限模型

基于角色的访问控制(RBAC)是实施面向企业安全策略的一种有效的访问控制方式。 其基本思想是,对系统操作的各种权限不是直接授予具体的用户,而是在用户集合与权限集合之间建立一个角色集合。每一种角色对应一组相应的权限。一旦用户被分配了适当的角色后,该用户就拥有此角色的所有操作权限。这样做的好处是,不必在每次创建用户时都进行分配权限的操作,只要分配用户相应的角色即可,而且角色的权限变更比用户的权限变更要少得多,这样将简化用户的权限管理,减少系统的开销。博文推荐:RBAC权限模型——项目实战

在RBAC模型里面,有3个基础组成部分,分别是:用户、角色和权限,它们之间的关系如下图所示:
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证

RBAC其实是一种分析模型,主要分为:基本模型RBAC0(Core RBAC)、角色分层模型RBAC1(Hierarchal RBAC)、角色限制模型RBAC2(Constraint RBAC)和统一模型RBAC3(Combines RBAC),其中RBAC0为核心,其他模型在RBAC0基础上进行扩展, RBAC0由用户,角色,会话和许可(“操作”和“控制对象”)四部分组成 ,如下图所示:

Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证

前面项目中的权限数据表的设计就是采用RBAC模型,如下图所示( 数据库的设计来源于github上的mall项目,这里不做解释 ):
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证

ums_role角色表,表中存在三种角色,商品管理员,订单管理员和超级管理员
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证

ums_admin用户表:
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证

ums_admin和ums_role关联表 ums_admin_role_relation:
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
ums_permission:许可表
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证

Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
ums_admin和ums_permission的关联表:ums_admin_permission_relation:
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
ums_role和ums_permission的关联表ums_role_permission_relation:
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证

查询权限的sql语句如下:

        SELECT
            p.*
        FROM
            ums_admin_role_relation ar
                LEFT JOIN ums_role r ON ar.role_id = r.id
                LEFT JOIN ums_role_permission_relation rp ON r.id = rp.role_id
                LEFT JOIN ums_permission p ON rp.permission_id = p.id
        WHERE
            ar.admin_id = #{adminId}
          AND p.id IS NOT NULL
          AND p.id NOT IN (
            SELECT
                p.id
            FROM
                ums_admin_permission_relation pr
                    LEFT JOIN ums_permission p ON pr.permission_id = p.id
            WHERE
                pr.type = - 1
              AND pr.admin_id = #{adminId}
        )
        UNION
        SELECT
            p.*
        FROM
            ums_admin_permission_relation pr
                LEFT JOIN ums_permission p ON pr.permission_id = p.id
        WHERE
            pr.type = 1
          AND pr.admin_id = #{adminId}

前面的项目中,获取数据库的权限由于有多张表参与,所以无法直接使用mybatis生成的dao进行操作,所以需要自己创建dao,如下:
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
UmsAdminRoleRelationDao.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="com.dudu.dao.UmsAdminRoleRelationDao">
    <select id="getPermissionList" resultMap="com.dudu.mbg.mapper.UmsPermissionMapper.BaseResultMap">
        SELECT
            p.*
        FROM
            ums_admin_role_relation ar
                LEFT JOIN ums_role r ON ar.role_id = r.id
                LEFT JOIN ums_role_permission_relation rp ON r.id = rp.role_id
                LEFT JOIN ums_permission p ON rp.permission_id = p.id
        WHERE
            ar.admin_id = #{adminId}
          AND p.id IS NOT NULL
          AND p.id NOT IN (
            SELECT
                p.id
            FROM
                ums_admin_permission_relation pr
                    LEFT JOIN ums_permission p ON pr.permission_id = p.id
            WHERE
                pr.type = - 1
              AND pr.admin_id = #{adminId}
        )
        UNION
        SELECT
            p.*
        FROM
            ums_admin_permission_relation pr
                LEFT JOIN ums_permission p ON pr.permission_id = p.id
        WHERE
            pr.type = 1
          AND pr.admin_id = #{adminId}
    </select>
</mapper>

dao层中定义UmsAdminRoleRelationDao的接口
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
业务层中通过adminRoleRelationDao获取数据库中的权限信息
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证

Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
每一个mapper接口中都没有@mapper所以需要添加一个MyBatisConfig的配置类通过@MapperScan注解扫描mapper
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证

3.自定义失败处理

我们还希望在认证失败或者是授权失败的情况下也能和我们的接口一样返回相同结构的json,这样可以让前端能对响应进行统一的处理。要实现这个功能我们需要知道SpringSecurity的异常处理机制。在SpringSecurity中,如果我们在认证或者授权的过程中出现了异常会被ExceptionTranslationFilter捕获到。在ExceptionTranslationFilter中会去判断是认证失败还是授权失败出现的异常。

  • 如果是认证过程中出现的异常会被封装成AuthenticationException然后调用 AuthenticationEntryPoint 对象的方法去进行异常处理。
  • 如果是授权过程中出现的异常会被封装成AccessDeniedException然后调用 AccessDeniedHandler 对象的方法去进行异常处理。

所以如果我们需要自定义异常处理,我们只需要自定义AuthenticationEntryPoint和AccessDeniedHandler然后配置给SpringSecurity即可,具体操作如下:

(1) 创建异常处理类

Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
RestAuthenticationEntryPoint类( 当未登录或者token失效访问接口时,自定义的返回结果【认证失败】 ):

package com.dudu.component;

import cn.hutool.json.JSONUtil;
import com.dudu.common.api.CommonResult;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 当未登录或者token失效访问接口时,自定义的返回结果【认证失败】
 */
@Component
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json");
        response.getWriter().println(JSONUtil.parse(CommonResult.unauthorized(authException.getMessage())));
        response.getWriter().flush();
    }
}

RestfulAccessDeniedHandler类( 当访问接口没有权限时,自定义的返回结果【授权失败】 ) :

package com.dudu.component;

import cn.hutool.json.JSONUtil;
import com.dudu.common.api.CommonResult;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 当访问接口没有权限时,自定义的返回结果【授权失败】
 */
@Component
public class RestfulAccessDeniedHandler implements AccessDeniedHandler{
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json");
        response.getWriter().println(JSONUtil.parse(CommonResult.forbidden(e.getMessage())));
        response.getWriter().flush();
    }
}

(2) 配置移除处理类

在SecurityConfig中configure()方法中配置异常处理类:

  • 先注入对应的处理器

Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证

  • 然后我们可以使用HttpSecurity对象的方法去配置。

Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证

4.跨域问题

浏览器出于安全的考虑,使用 XMLHttpRequest对象发起 HTTP请求时必须遵守同源策略,否则就是跨域的HTTP请求,默认情况下是被禁止的。 同源策略要求源相同才能正常进行通信,即协议、域名、端口号都完全一致。 (前后端分离项目,前端项目和后端项目一般都不是同源的,所以肯定会存在跨域请求的问题。),如何设置跨域,你可以通过这篇博文springboot设置Cors跨域的四种方式进行学习。

  • 添加跨域配置类
package com.dudu.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;

/**
 * 全局跨域配置
 */
@Configuration
public class GlobalCorsConfig{

    /**
     * 允许跨域调用的过滤器
     */
    @Bean
    public CorsFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        //允许所有域名进行跨域调用
        config.addAllowedOriginPattern("*");
        //允许跨越发送cookie
        config.setAllowCredentials(true);
        //放行全部原始头信息
        config.addAllowedHeader("*");
        //允许所有请求方法跨域调用
        config.addAllowedMethod("*");
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", config);
        return new CorsFilter(source);
    }
}

  • 开启SpringSecurity的跨域访问

在SpringSecurity配置文件的configure(HttpSecurity httpSecurity)方法下通过httpSecurity.cors()运行跨域:

Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证

三.源码下载

JWT+SpringSecurity实现登入案例的代码采用github上的mall项目教程的代码,你可以通过该教程的链接进行下载,该教程路径https://www.macrozheng.com/mall/architect/mall_arch_05.html:
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证
当然你也可以通过我的微信公众号进行下载,里面包括了博文中涉及到的所有代码,微信公众号搜索程序员孤夜(或扫描下方二维码),后台回复 登入案例 ,即可获取本篇文章所使用的源代码下载链接。
Spring Security详细讲解(JWT+SpringSecurity登入案例),Spring全家桶,spring,java,Spring Security,后端,spring安全认证文章来源地址https://www.toymoban.com/news/detail-781219.html

到了这里,关于Spring Security详细讲解(JWT+SpringSecurity登入案例)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Spring Security 构建基于 JWT 的登录认证

    一言以蔽之,JWT 可以携带非敏感信息,并具有不可篡改性。可以通过验证是否被篡改,以及读取信息内容,完成网络认证的三个问题:“你是谁”、“你有哪些权限”、“是不是冒充的”。   为了安全,使用它需要采用 Https 协议,并且一定要小心防止用于加密的密钥泄露。

    2024年02月16日
    浏览(32)
  • Spring Boot 整合SpringSecurity和JWT和Redis实现统一鉴权认证

    本文主要讲了Spring Security文章,如果有什么需要改进的地方还请大佬指出⛺️ 🎬作者简介:大家好,我是青衿🥇 ☁️博客首页:CSDN主页放风讲故事 🌄每日一句:努力一点,优秀一点 Spring Security Spring Security是一个强大且高度可定制的身份验证和访问控制框架。它是保护基

    2024年02月05日
    浏览(39)
  • Java开发 - 单点登录初体验(Spring Security + JWT)

    目录 ​​​​​​​ 前言 为什么要登录 登录的种类 Cookie-Session Cookie-Session-local storage JWT令牌 几种登陆总结  用户身份认证与授权 创建工程 添加依赖 启动项目 Bcrypt算法的工具 创建VO模型类 创建接口文件 创建XML文件 补充配置 添加依赖 添加配置 创建配置类 测试上面的配置

    2024年02月02日
    浏览(36)
  • spring-security -oauth2 整合 JWT

    在这个基础上,进行整合。 spring security oauth2学习 -- 快速入门_本郡主是喵的博客-CSDN博客 先把  reids,common-pools  等依赖删掉。 删掉redis的下相关配置 1.1 导入依赖 1.2 核心代码 创建 jwtTokenConfig.java 在 AuthenticationServer.java 里面新增这些。  运行,启动!  复制这个token去官网解析

    2024年02月09日
    浏览(42)
  • Spring Security OAuth 2.0 资源服务器— JWT

    目录 一、JWT的最小依赖 二、JWT的最基本配置 1、指定授权服务器 2、初始预期(Startup Expectations) 3、运行时预期(Runtime Expectations) 三、JWT认证是如何工作的 四、直接指定授权服务器 JWK Set Uri 五、提供 audiences 六、覆盖或取代启动自动配置 1、使用jwkSetUri() 2、使用decoder()

    2024年02月05日
    浏览(44)
  • SpringBoot2.3集成Spring Security(二) JWT认证

    紧接上文,我们已经完成了 SpringBoot中集成Spring Security,并且用户名帐号和密码都是从数据库中获取。但是这种方式还是不能满足现在的开发需求。 使用JWT的好处: 无状态认证:JWT本身包含了认证信息和声明,服务器不需要在会话中保存任何状态。这样使得应用程序可以更加

    2024年02月11日
    浏览(44)
  • Spring Boot + Vue的网上商城之springsecurity+jwt+redis实现用户权限认证实现

    在网上商城项目中,用户的安全性是非常重要的。为了实现用户权限认证和安全校验,我们可以使用Spring Security、JWT和Redis来实现。本篇博客将详细介绍后端和前台的实现过程,并提供相应的代码案例。 当用户点击登录按钮时,前端发送一个POST请求到后端的登录接口,传递用

    2024年02月07日
    浏览(41)
  • Spring Boot 优雅集成 Spring Security 5.7(安全框架)与 JWT(双令牌机制)

    本章节将介绍 Spring Boot 集成 Spring Security 5.7(安全框架)。 🤖 Spring Boot 2.x 实践案例(代码仓库) Spring Security 是一个能够为基于 Spring 的企业应用系统提供声明式的安全访问控制解决方案的安全框架。 它提供了一组可以在 Spring 应用上下文中配置的 Bean,充分利用了 Spring

    2024年02月12日
    浏览(34)
  • SpringCloud gateway+Spring Security + JWT实现登录和用户权限校验

    原本打算将Security模块与gateway模块分开写的,但想到gateway本来就有过滤的作用 ,于是就把gateway和Security结合在一起了,然后结合JWT令牌对用户身份和权限进行校验。 Spring Cloud的网关与传统的SpringMVC不同,gateway是基于Netty容器,采用的webflux技术,所以gateway模块不能引入spri

    2024年02月03日
    浏览(39)
  • 【深入浅出 Spring Security(十三)】使用 JWT 进行前后端分离认证(附源码)

    JWT 全称 Java web Token,在此所讲述的是 JWT 用于身份认证,用服务器端生成的JWT去替代原始的Session认证,以提高安全性。 JWT本质是一个Token令牌,是由三部分组成的字符串,分别是头部(header)、载荷(payload)和签名(signature)。头部一般包含该 JWT 的基本信息,例如所使用的

    2024年02月12日
    浏览(29)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包