✅SpringSecurity+JWT 实现项目前端分离认证授权✅
1. 简介
Spring Security是Spring家族中的一个安全管理框架。相比与另外一个安全框架Shiro,它提供了更丰富的功能,杜区资源也比Shiro丰富。
一般来说中大型的项目都是使用Spring Security来做安全框架。小项目有Shiro的比较多,因为相比与Spring Security,Shiro的上手更加的简单,
一般Web应用的需要进行认证和授权,
认证:验证当前访问系统的是不是本系统的用户,并且要确认具体是哪个用户
授权:经过认证后判断当前用户是否有权限进行某个操作
而认证和授权也是Spring Security作为安全框架的核心功能。
在没有引入Spring Security框架之前访问一个网页是直接访问的
加入了Spring Security之后,默认跳转到登陆页面
这个实际上就是一个认证的过程,登录成功之后才能够访问默认的接口
是 有一个默认的退出接口的, logout
2. 登录校验流程
前后端沟通使用token,判断是否携带对应的Token来判断是否是系统的用户,也可以拿到Token获取到加密之前的一些数据,用来判断是哪一用户
登录的时候
- 前端发送用户名密码给服务器进行一个比对,如果说一切的信息都是正确的,服务器后将一些用户信息比如说用户名,用户密码,创建时间生成一个JWT,然后将这个生成的JWT Token响应给前端,前端进行一个存储(Local Storage)
- 然后前端再去访问其他请求的时候,都会在请求头中携带Token数据,然后服务器可以获取请求头中的Token进行解析获取到Token中的信息然后进行比对,如果解析成功就可以获取到之前一些加密的数据比如说UserID, 密码之类的
- 拿到UserID之后就可以获取用户的其他信息,比如说用户的权限,能否访问对应的接口等等。
- 接收用户凭据:当用户尝试登录时,他们的凭据(通常是用户名和密码)会被
UsernamePasswordAuthenticationFilter
接收,这是AbstractAuthenticationProcessingFilter
的一个具体实现。 - 生成Authentication对象:过滤器将基于用户提供的信息创建一个未经认证的
Authentication
对象。 - AuthenticationManager进行认证:这个未经认证的
Authentication
对象会被传递给AuthenticationManager
,具体是它的实现类ProviderManager
,来进行验证。 - AuthenticationProvider进行认证:
ProviderManager
会调用DaoAuthenticationProvider
,它是AbstractUserDetailsAuthenticationProvider
的一个实现。 - 加载用户详情:
DaoAuthenticationProvider
会使用UserDetailsService
接口来获取用户详情,这里具体使用了InMemoryUserDetailsManager
实现。 - UserDetailsService返回用户信息:
UserDetailsService
会加载用户的详细信息并返回一个UserDetails
对象。 - 密码比对:
DaoAuthenticationProvider
会使用PasswordEncoder
来比对提交的密码和UserDetails
中存储的密码。 - 构建经过认证的Authentication对象:如果用户凭据有效,
AuthenticationProvider
会构建一个包含用户权限的已认证Authentication
对象。 - 保存Authentication对象:成功认证后,认证对象会被存入
SecurityContext
中,以供后续请求使用。 - 更新SecurityContext:最后,
SecurityContextHolder
的上下文会被更新,以包含新的认证信息。
3. SpringSecurity实现流程
原理上其实就是一个过滤器链,内部包含了各种功能的过滤器
UsernamePasswordAuthentiationFilter
: 负责处理在登录页面填写的用户名密码请求登录。ExceptionTranslationFilter
: 认证授权这个流程出现的异常都会被这个过滤器所捕获。FilterSecurityInterceptor
: 这个过滤器主要是判断授权的功能。
4. 解决方案
登录
1️⃣ 自定义UserDetailService
, 在这个实现类中去查询数据库
2️⃣ 自定义登录接口,调用ProviderManager
的方法进行认证,如果生成通过生成JWT,把用户信息存入Redis中
校验
1️⃣ 定义基于JWT的认证过滤器
1️⃣ 获取Token
2️⃣ 解析Token,获取其中的UserID
3️⃣ 从Redis中获取用户信息
4️⃣ 存入SecurityContextHolder
5. Spring Security密码加密存储
Spring Security 明文存储前面加 {noop} 采用默认加密
实际项目中不会吧密码明文存储到数据库中。
默认使用的PasswordEncoder
要求数据库的密码格式为:{id}password。它会根据id去判断密码的加密方式,但是一般不会采用这种方式,所以就需要替换PasswordEncoder
.
一般使用Spring Security中的BCryptPasswordEncoder
在使用BCyptPasswordEncoder
加密的时候,虽然加密的原文是一致的,但是由于每次生成的盐值不同,所以每次产生的密文也不相同
BCyptPasswordEncoder
加密格式为: $2a$10$ + 盐(22位) + 密文
6. 登录接口
自定义登录接口的话肯定是要定义Controller
对于登录接口肯定是要Spring Security进行放行操作的。需要在对应的Spring Security配置类里面进行相应的配置,重写configure
方法
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//关闭csrf
.csrf().disable()
//不通过Session获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 对于登录接口 允许匿名访问
.antMatchers("/user/login").anonymous()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
}
实际上的操作实在Service当中
/**
* 用户登录
* @param user
* @return
*/
@Override
public ResponseResult login(User user) {
// AuthenticationManager 进行用户认证
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword());
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
// 如果认证没有通过,给出对应的提示
if(Objects.isNull(authenticate)){
throw new RuntimeException("登录失败");
}
// 如果认证通过了,使用UserID生成一个JWT JWT存入ResponseResult进行返回
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
String userId = loginUser.getUser().getId().toString();
String jwt = JwtUtil.createJWT(userId);
Map<String, String> map = new HashMap<>();
map.put("token", jwt);
// 把完整的用户信息存入Redis userId作为key
redisCache.setCacheObject("login:" + userId, loginUser);
return new ResponseResult(200, "登录成功", map);
}
7. 退出登录
退出登录实际上就是携带之前的Token登录访问,现在的用户相当于是一个未认证的状态,需要重新登录,可以将SecurityContextHolder
中的对象删除掉
当用户携带着他的Token访问注销接口的时候,服务器会获取到SecurityContextHolder
中的userId, 然后在Redis中删除对应的用户信息,当用户再次携带之前的Token访问其他服务的时候,会被第一个JwtToken
拦截器解析出Token中的用户ID,在Redis中查询不到,抛出异常,授权不成功
8. 授权
在Spring Security中,会使用默认的FilterSecurityInterceptor
类进行权限校验。在FilterSecirutyInterceptor
中会从SecurityContextHolder
中获取其中的Authentication
, 然后获取其中的信息, 当前用户是否拥有访问当前资源所需的权限。
所以在项目中只需要把当前登录用户的权限信息也存入Authentication
- 将用户的权限信息封装到
Authentication
这个对象当中 - 设置对应接口的一些访问权限
- 实际的权限信息应该从数据库进行一个查询
关于这个注解判断用户是否有hasAuthority()
括号里面的权限,实际上是执行下面的这个方法,用户必须拥有sys:user:info
这个权限才能够访问这个接口
8. 权限
RBAC权限模型(Role-Based Access Control) 基于角色的权限控制。这是目前最常杯卡法这使用也是相对容易,通用的权限模型。
一个用户可以有多个权限,但是如果只是用用户表以及权限表的话就会有一个问题,当有一个复杂的系统用户肯定是非常的多,那给每一个用户设置权限的时候就会显得非常的麻烦,这个权限粒度是非常小的,所以一个一个配置的话就会非常的麻烦,甚至产生数据冗余的问题。所以可以一次性配置一组权限信息,一系列的权限信息直接配置给用户,所以引入了一个角色的概念
- 用户表——user
- 权限表——menu
- 角色表(权限组)——role
权限和角色是有关联的。一个角色是可以具有多个权限的,同时一个权限是可以对应多个角色,其中是一个多对多的关系。需要一个角色权限关联表(role_menu)
- 角色权限关联表——role_menu
同理,用户表和角色表也是多对多的关系,这也需要一个关联表: 用户角色关联表(user-role)
- 用户角色关联表——user_role
SELECT DISTINCT
m.perms
FROM
sys_user_role ur
LEFT JOIN sys_role r ON ur.role_id = r.id
LEFT JOIN sys_role_menu rm ON ur.role_id = rm.role_id
LEFT JOIN sys_menu m ON m.id = rm.menu_id
WHERE
user_id = 1
AND r.`status` = 0
AND m.`status` = 0
标签:SpringSecurity,登录,Spring,JWT,用户,认证,Security,权限
From: https://www.cnblogs.com/pronting/p/18023954