目录
Spring Security是Spring家族中的一个安全管理框架,相比与另外一个安全框架Shiro,它提供了更丰富的功能,社区资源也比Shiro丰富。
一般来说大型项目用Spring Security比较多,小项目用Shiro比较多,因为相比于Spring Security,Shiro上手比较简单。
一般Web应用需要进行认证和授权。
- 认证:验证当前访问系统的是不是本系统用户,并且要确认具体是哪个用户
- 授权:经过认证后判断当前用户是否有权限进行某个操作
而认证和授权正是Spring Security作为安全框架的核心功能!
一. 快速入门
我们先简单构建出一个SpringBoot项目。
这个时候我们访问我们写的一个简单的hello接口,验证是否构建成功。
接着引入SpringSecurity。
这个时候我们再看看访问接口的效果。
引入了SpringSecurity之后,访问接口会自动跳转到一个登录页面,默认的用户名是user,密码会输出到控制台,必须登录后才能对接口进行访问。
二. 认证
2.1 登陆校验流程
首先我们要先了解登录校验流程,首先前端携带用户名和密码访问登录接口,服务器拿到这个用户名和密码之后去和数据库中的进行比较,如果正确使用用户名/用户ID,生成一个jwt,接着把jwt响应给前端,之后登录后访问其他的请求都会在请求头中携带token,服务器每次获取请求头中的token进行解析、获取UserID,根据用户名id获取用户相关信息,查看器权限,如果有权限则响应给前端。
2.2 原理初探
SpringSecurity的原理其实就是一个过滤器链,内部提供了各种功能的过滤器,这里我们先看看上方快速入门中涉及的过滤器。
- UsernamePasswordAuthenticationFilter负责处理在登录页面填写的用户名密码后的登录请求
- ExceptionTranslationFilter处理过滤器链中抛出的任何AccessDeniedException和AuthenticationException
- FilterSecurityInterceptor负责权限校验的过滤器
我们也可以通过Debug查看当前系统中SpringSecurity过滤器链中有哪些过滤器以及顺序。
接下来我们来看看认证流程图的解析。
这里我们只需要能看懂其过程即可,简单来说就是:
用户提交了用户名和密码,UsernamePasswordAuthenticationFilter将其封装未Authentication对象,并且调用authenticate方法进行认证,接着在调用DaoAuthenticationProvider的authenticate方法进行认证,再调用loadUserByUserName方法查询用户,这里的查询是在内存中进行查找,然后将对应的用户信息封装未UserDetails对象,通过PasswordEncoder对比UserDetails中的密码和Authentication的密码是否正确,如果正确就把UserDetails中的权限信息设置到Authentication对象中,接着返回Authentication对象,最后使用SecurityContextHolder.getContext().setAuthentication方法存储该对象,其他过滤器会通过SecurityContextHoder来获取当前用户信息。(这一段不用记忆能听懂即可)
那么我们知道了其过程,才能对其进行修改,首先这里的从内存中查找,我们肯定是要该为从数据库中查找(这里需要我们自定义一个UserDetailsService的实现类),并且也不会使用默认的用户名密码,登录界面也一定是自己编写的,不需要用他提供的默认登录页面。
基于我们分析的情况,可以得到这样的一张图。
这个时候就返回了一个jwt给前端,而这时前端进行的其他请求都会携带token,那么我们第一步就需要先校验是否携带token,并且解析token,获取对应的userid,并且将其封装为Anthentication对象存入SecurityContextHolder(为了其他过滤器可以拿到)。
那么这里还有一个问题,从jwt认证过滤器中取到了userid后如何获取完整的用户信息?
这里我们使用redis,当服务器认证通过使用用户id生成jwt给前端的时候,以用户id作为key,用户的信息作为value存入redis,之后就可以通过userid从redis中获取到完整的用户信息了。
2.3 解决问题
2.3.1 思路分析
从上述的原理初探中,我们也大概分析出了我们要是自己实现前后端分离的认证流程,需要做的事情。
登录:
a.自定义登录接口
调用ProviderManager的方法进行认证,如果认证通过生成jwt
把用户信息存入redis中
b.自定义UserDetailsService
在这个实现类中去查询数据库
校验:
a.自定义jwt认证过滤器
获取token
解析token获取其中userid
从redis中获取完整用户信息
存入SecurityContextHolder
2.3.2 准备工作
首先需要添加对应的依赖
<!-- SpringSecurity启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- redis依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- fastjson依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.33</version>
</dependency>
<!-- jwt依赖 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
接着我们需要用到Redis需要加入Redis相关的配置
首先是FastJson的序列化器
package org.example.utils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import java.nio.charset.Charset;
/**
* Redis使用fastjson序列化
* @param <T>
*/
public class FastJsonRedisSerializer<T> implements RedisSerialize