---Spring Security是配置在具有登录功能的前后端交互项目中
配置要点:
首先一个项目想要有Spring Security支持,需要先在
1.依赖库里边加上Security和jjwt,fastjson依赖项
<!-- 在Spring Boot中的Spring Security编程支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- JJWT(Java JWT) -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!-- fastjson(实现对象与Json的相互转换) -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>
2.写Security配置类,写Jwt过滤器
Security配置类
继承WebSecurityConfigurerAdapter
还有两个重要的Bean要加上,PasswordEncoder以及AuthenticationManager
1约定密码编码格式,2用于后续认证操作的操作器
Security过滤器里边要声明白名单路径,然后制定访问控制策略,比如常用的是将OPTION请求放行
因为OPTION请求是请求中有复杂数据时会出现的一种请求,这种请求并没有实际数据,是由浏览器发起的,只是询问是否可以接收处理复杂请求,算是一个预检,我们可以直接放行,即告诉浏览器OK,
对于其他的请求我们都要求其具有认证.最后在这个配置Security的配置类中我们还要在最后将Jwt的过滤器加进来,因为对于密码等的验证Security框架是自动进行的,我们需要将Jwt的验证优先级提一下,让他在Security自动处理数据之前运行.
Jwt过滤器
Jwt过滤器即是处理那些没有被Security配置通过的请求,当接收到一个请求,本类会先将当前Security上下文中的认证信息清空,然后将请求头里的数据根据约定的消息头Authorization提取出来(未提取到直接放行,未将认证信息写入上下文就等着被拦截),这个对应的就是Jwt,然后对Jwt进行反解析(用到Jwt工具类),这句反解析的代码是可能抛出异常的,Jwt过期以及错误异常,当没有异常抛出时,就认为这个认证信息是有效的,由于往Jwt里边封装数据只能是以Map的形式,这里我们也是用get("<key>")的形式获取到当初存进去的信息,注意存放的时候Map<String,Object>格式约束导致我们拿到的数据都是Object类型,当我们在往Authentication(Security写入上下文约定的格式)放数据的时候还是(Object,Object,Collection<GrantedAuthorization>)类型,我们可以将诸如id,username这种用户信息封装一下放到第一个参数,第二个参数不管,置为null,至于第三个参数我们需要对数据类型进行转换才能装进去. 转好后写入Security上下文,那么本次请求就是拥有认证的,Security放行
Jwt工具类:
封装将Map<String,Object>数据转为jwt(String),复杂集合需要转为JSON格式在放进去,避免往出拿时格式错乱
封装解析Jwt数据回到Map<String,Object>类型的过程,这里讲一下其中各数据的转换经历
用户名:
AdminDetails[注1]是就是个用户名属性,到往jwt转换的时候也是用户名,直到被提取出来往Security上下文里边装的时候才与id封装成为个人主体信息AdminPrincipal
权限:
权限起初都是放在数据库中的字符串,当查询时我们将这一条条权限(String)放到了List集合中,因此我们根据用户名 查询得到的AdminLoginVO中permission是一个List<String>类型,在往AdminDetails中装的时候,由于AdminDetails需要的是一个List<GrantedAuthority>,因此就有了遍历permission,将每一条String数据通过new GrantedAuthority(<permission>)的方式转换成符合格式约定的类型,这样就将一个AdminDetails数据封装完毕了,送去认证密码(用到PasswordEncoder里边matches).通过后因为要往jwt转换,集合形式的数据放到jwt里边容易出错,因此转换为String(通过JSON当中间人)
JSON.toJSONString(List<>) ,jwt解析出来后又可以
JSON.parseArray(<String>, <集合中数据类型>.class)恢复为List<集合中数据类型>,他会以这个形式写入Security上下文.
[注1]
AdminDetail数据类型是我们因为其原本UserDetails类型没有id属性,UserDetails是User类型的一个实现,因此我们继承了User类再声明一个id属性,然后写了一个具有id属性的构造方法,在方法体中super父类实现 方法,然后将this.id=id;AdminDetails,这样使用AdminDetails创建的数据对象就具有id属性了,后续可get调用.
3.加注解
在Security配置类上加此注解,开启全局授权访问检查
@EnableGlobalMethodSecurity(prePostEnabled = true)
在Controller方法上加注解,配置访问权限
@PreAuthorize("hasAnyAuthority('/ams/admin/update')")
4.登录
用户输入的用户名,密码被封装着传入Controller,然后被解开放到AdminDetails里边根据名字查找对应的对象,没查到就直接返回null,在外边null就直接抛出异常了,不是null就将找到的数据封装起来成为一个AdminDetails对象送到外边进行认证,认证通过就继续往下走,提取(用户名,id,权限) 注意这个信息是放在验证用户登录信息返回的对象的principal中,可以get后直接强转成AdminDetails,注意权限List集合要转为String类型然后往map里边放,最后将map传给Jwt工具类转换成jwt码,以
JsonResult.ok(jwt)
返回,方便前端调用
5.前端调用
前端在发送登陆请求,登陆成功后通过response.data.data获取jwt,setItem到localStorage,让其他请求可以直接getItem,然后发请求放上这个请求头
this.axios.create({
headers: {
Authorization: jwtItem
}
}).post/get…