使用spring Security + JWT 实现前后端分离登录(纯代码无原理)
构建项目代码已上传gitee如果你看到了该文章说明对spring框架有一定了解,相信你已经知道如何通过springboot进行项目构建springboot想要支持安全验证以及web支持只需要在pom(如果是maven)文件中引入以下startermaven//安全包<dependency><groupId>org.springframework.boot</group
构建项目
如果你看到了该文章说明对spring框架有一定了解,相信你已经知道如何通过springboot进行项目构建
springboot想要支持安全验证以及web支持只需要在pom(如果是maven)文件中引入以下starter
maven
//安全包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
//web包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
gradle
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
启动项目
- 浏览器输入
http://localhost:8080
默认情况下控制台会输出一串密码串
- security 默认情况下会拦截所有请求 并生成默认登录页
- 输入用户
user
密码就是控制台中的密码串
- 输入成功后出现以下页面(404是因为我们并没有配置/路由)(如果出现以下页面表示登录成功)
- 默认情况下security使用是表单登录通过session 与cookie 进行认证的
- 我们打开浏览器控制台发现有一段cookie
- 我们删除该cookie刷新浏览器发现又让重新登录 (说明我们猜想是正确的)
如何自定义默认的登录名与密码呢?
只需要在application.yml中配置即可
自定义安全认证与授权
- 认证: 认证即系统通过用户凭据 (密码),token,密钥等进行身份认证的过程 证明
你是谁
- 授权: 系统,通过认证以后给与你一定权限,例如一个按钮的点击等,赋予你能
干什么
的权限
- 在springboot 中自定义security只需要继承
WebSecurityConfigurerAdapter
并且添加@EnableWebSecurity
注解即可配置security- 我们创建一个类
JwtSecurityConfig
继承WebSecurityConfigurerAdapter
- 实现
config
方法
- 我们创建一个类
可能有些人要问为什么不是下图:
这是应为formLogin
是表单登录基于session-cookie
机制进行用户认证的,而前后端分离一般使用jwt
即用户状态保存在客户端中,
前后端交互通过api接口无session
生成,所以我们不需要配置formLogin
-
我们需要禁用session
-
我们再次访问
http://localhost:8080
-
发现没有登录页面,而是返回403页面,这是因为security 检测到项目中有
WebSecurityConfigurerAdapter
的实现类并配置了config,所以它认为用户要自定义security所以默认的表单认证失效(除非你配置了formLogin) 至于403 是因为我们配置了所有路径都需要通过认证(包括/
路径) -
返回403 正是我们想要的,说明了security起作用了,拦截保护了所有请求,同时还赋予我们自定义功能
创建一个controller (添加一个方法login 作为登录入口)
- 配置
/login
为匿名访问 防止被拦截无法进行登录
- security登录关键类是
AuthenticationManager
认证管理器,如果我们什么都不配置默认使用的是ProviderManager
是ioc运行时注册的bean 我们无法在编译时声明注入到controller,所以需要我们把默认的ProviderManager
在编译期声明为bean - 配置 JwtSecurityConfig
开始认证
- 正常认证流程为下图(显然我们缺少查询用户信息这一步 ,security这么强大当然给我们留了接口)
- 如果你的用户信息来源于内存中那配置以下信息(JwtSecurityConfig)即可
- 对于我们实际项目来说用户信息可能存储方式多种多样 security也给我提供了一个接口用于扩展用户信息获取方式
- 第一步创建一个类
MyUserDetailsServiceImpl
实现UserDetailsService
- 第二步将该Service 配置给认证管理器(也就是把内存存储配置替换掉)
- 第一步创建一个类
认证管理器在进行认证时会调用
loadUserByUsername
方法把客户端传递回来的用户名传给username
参数 我们根据该参数去我们存储用户信息的地方调用用户信息并填充UserDetails
对象返回即可,真正做密码匹配的是DaoAuthenticationProvider
它是认证管理器AuthenticationManager
的子类 ,默认情况我们无需配置该类。
- 这里为了测试我简单硬编码用户信息
- 访问
/login
进行登录 (选中你们喜欢的客户端(postman
)进行测试)我使用apipost
- 如果我们把用户名写错看看会发生什么事
我们发现并没有我们期待的事发生控制台并没有报错 抛出
UsernameNotFoundException
异常,并且客户端返回403异常,为什么会出现这种问题? 因为security在默认情况(用户未配置认证异常处理时候会默认返回403异常),那么我们怎么自定义呢?
- 创建一个类
MyAuthenticationEntryPoint
实现AuthenticationEntryPoint
该接口只有一个方法
参数 authException 即 认证错误所抛出的异常 我们根据异常重写reponse 给客户端返回认证错误即可。
- 配置异常处理类
- 我们再次登录
我们发现控制台报错
Bad credentials
出现这种错误一般是用户名密码错误,权限不够,密码不匹配等异常。通过debug发现在AbstractUserDetailsAuthenticationProvider
中有以下代码
其实security这样做主要为了安全考虑 (我们也推荐最好不要关闭该开关)但是有小伙伴说我就要抛出UsernameNotFoundException
,没问题。 这里给出三种解决方案(其实是两种,有两种是相同目的不同解决思路)
- 自定义抛出异常(这种最简单)
- 自定义DaoAuthenticationProvider(前面讲到它是主要认证类)在JwtSecurityConfig中注册该类并注释掉认证器配置即可
在登录 (控制台抛出UsernameNotFoundException
)nice
我们其实就是想关闭屏蔽用户名不存在异常的开关,有没有一种方式可以在spring 初始化完DaoAuthenticationProvider后调用前改变对象的属性呢?答案是有, 这就涉及到ioc初始化bean的相关知识 ,在spring ioc 给用户预留一个接口
BeanPostProcessor
ioc会在实例化bean后初始化bean前和后调用该类。类似与aop 。该接口有两个方法postProcessBeforeInitialization
前置处理器 ,postProcessAfterInitialization
后置处理器 。到这里是不是有小伙伴想到怎么做了吗?我们拦截DaoAuthenticationProvider
修改开关属性即可。
注意: ioc容器还有一个给用户预留的接口BeanFactoryPostProcessor
该接口会在ioc 实例化bean之前调用,我们可以实现该接口用于配置bean元数据等信息。想了解更多请看spring官方文档在这里不在展开介绍
- 再次登录 发现也实现了抛出用户名不存在错误
- 封装错误给用户
- 至于密码错误你们自行拦截处理就可以了
- 当我们登录成功后在进行访问
/
路径时候控制台出现
这是由于我们虽然登录成功了,但是我们禁用了session 所以服务器无法判断当前登录的用户是谁导致没有认证实体
生成token
- 我使用
java-jwt
作为token生成包 因为它简单易用 - 编写
tokenService
- 在登录入口注入tokenService 生成token
因为用户登录状态在token中存储在客户端,所以每次请求后台请求头携带token 后台通过自定义token过滤器拦截解析token完成认证并填充用户信息实体。
创建token解析Filter JwtDecoderFilter
- 添加自定义Filter到Security
- 访问test路径 (携带登录所获取的token)
至于如何自定义授权我会在下一篇文章讲解
更多推荐
所有评论(0)