一、Web登录流程
有以下缺点可以改进:
- 现在使用的是security自带的登陆页面,比较丑。 想换成自己项目的,优化的登录页
- 用户使用的是security给的用户名和密码。 想真实地去数据库里获取真实的用户名和密码
- security自带的cookie\session模式。 想自己生成jwt,无状态登陆
- 前端页面怎么携带jwt。 想请求头里带上
- 鉴权操作完全没有。 想鉴权做完善。
总而言之,自己的一些特定需求,都没有实现
二、SpringSecurity源码查看
springsecurity 就是通过一些过滤器、拦截器,实现登陆鉴权的流程的
2.1 springsecurity 登陆流程
springsecurity就是一个过滤器链
,内置了关于springsecurity的16
个过滤器
注意:上图中只写出了几个核心过滤器,其他的如下图
- UsernamePasswordAuthenticationFilter:处理我们登陆页面输入的用户名和密码是否正确的过滤器
- ExceptionTranslationFilter:处理前面的几个过滤器中,有了问题,抛出错误,不让用户登录
- FilterSecurityInterceptor:经行一个权限校验的拦截器
我们可以从boot项目中找到所有有关security的过滤器链
2.2 认证流程 再细化
debug UsernamePasswordAuthenticationFilter 运行机制
三、自定义登录
思路:
(1)登录:
a、自定义登录接口->调用prodivermanager auth方法->登陆成功生成jwt->存入redis
b、自定义userdetailsmanager实现类-> 从数据库中获取系统用户
(2)访问资源:
自定义认证过滤器->获取token-> 从token中获取userid-> 从redis中通过userid获取用户信息-> 存SecurityContextHolder
3.1 JWT简介
JSON Web Token
(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。无状态。
好处:不需要服务器端 存session
特点:可以被看到,但是不能篡改,因为第三部分用了秘钥
一个JWT实际上就是一个字符串,它由三部分组成,头部、载荷与签名
头部(Header)
头部用于描述关于该JWT的最基本的信息,例如其类型以及签名所用的算法等。这也可以被表示成一个JSON对象。
{"typ":"JWT","alg":"HS256"}
在头部指明了签名算法是HS256算法。 我们进行BASE64编码https://c.runoob.com/front-end/693/,编码后的字符串如下:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
载荷(playload)
载荷就是存放有效信息的地方
定义一个payload:
{"sub":"1234567890","name":"liming","admin":true,"age":18}
然后将其进行base64加密,得到Jwt的第二部分
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6ImxpbWluZyIsImFkbWluIjp0cnVlLCJhZ2UiOjE4fQ==
签证(signature)
jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
-
header (base64后的)
-
payload (base64后的)
-
secret
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分
hs256("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6ImxpbWluZyIsImFkbWluIjp0cnVlLCJhZ2UiOjE4fQ==",secret)
将这三部分用.连接成一个完整的字符串,构成了最终的jwt:
aHMyNTYoImV5SjBlWEFpT2lKS1YxUWlMQ0poYkdjaU9pSklVekkxTmlKOS5leUp6ZFdJaU9pSXhNak0wTlRZM09Ea3dJaXdpYm1GdFpTSTZJbXhwYldsdVp5SXNJbUZrYldsdUlqcDBjblZsTENKaFoyVWlPakU0ZlE9PSIsc2VjcmV0KQ==
3.2 JJWT签发与验证token
JJWT是一个提供端到端的JWT创建和验证的Java库
官网文档:https://github.com/jwtk/jjwt
创建token
1、新建项目jwtTest中的pom.xml中添加依赖:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
2、创建测试类,代码如下:
@Test
public void testJwt(){
JwtBuilder jwtBuilder = Jwts.builder()
.setId("666")//设置id
.setSubject("testJwt")//主题
.setIssuedAt(new Date())//签发日期
.signWith(SignatureAlgorithm.HS256, "liming");
String jwt = jwtBuilder.compact();
System.out.println(jwt);
}
运行打印结果:
//再次运行,会发现每次运行的结果是不一样的,因为我们的载荷中包含了时间
eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI2NjYiLCJzdWIiOiJ0ZXN0Snd0IiwiaWF0IjoxNjg1MjUzNjMwfQ.65OPPl5Y6C51XaEMU2l38oGf0ylHvcHHzxEw2FPKhis
解析token
我们刚才已经创建了token ,在web应用中这个操作是由
服务端进行
然后发给客户端
,客户端在下次向服务端发送请求时需要携带
这个token(这就好像是拿着一张门票一样),那服务端接到这个token 应该解析出token中的信息(例如用户id),根据这些信息查询数据库返回相应的结果
@Test
public void testJwt(){
JwtBuilder jwtBuilder = Jwts.builder()
.setId("666")//设置id
.setSubject("testJwt")//主题
.setIssuedAt(new Date())//签发日期
.signWith(SignatureAlgorithm.HS256, "liming");
String jwt = jwtBuilder.compact();
System.out.println(jwt);
Claims claims = Jwts.parser().setSigningKey("liming").parseClaimsJws(jwt).getBody();
System.out.println(claims);
}
运行打印效果:
{jti=666, sub=testJwt, iat=1685253964}
注意:设置签名key必须和生成时一致
设置过期时间
有很多时候,我们并不希望签发的token是永久生效的,所以我们可以为token添加一个过期时间
@Test
public void testJwt(){
//当前时间
long l = System.currentTimeMillis();
Date date = new Date(l + 10000);
JwtBuilder jwtBuilder = Jwts.builder()
.setId("666")//设置id
.setSubject("testJwt")//主题
.setIssuedAt(new Date())//签发日期
.setExpiration(date)//用于设置过期时间 ,参数为Date类型数据
.signWith(SignatureAlgorithm.HS256, "liming");
String jwt = jwtBuilder.compact();
System.out.println(jwt);
try {
Thread.sleep(15000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
Claims claims = Jwts.parser().setSigningKey("liming").parseClaimsJws(jwt).getBody();
System.out.println(claims);
}
打印效果:
当前时间超过过期时间,则会报错
自定义claims
我们刚才的例子只是存储了id和subject两个信息,如果你想存储更多的信息(例如角色)可以定义自定义claims
@Test
public void testJwt(){
JwtBuilder jwtBuilder = Jwts.builder()
.setId("666")//设置id
.setSubject("testJwt")//主题
.setIssuedAt(new Date())//签发日期
.claim("userId","123")
.signWith(SignatureAlgorithm.HS256, "liming");
String jwt = jwtBuilder.compact();
System.out.println(jwt);
Claims claims = Jwts.parser().setSigningKey("liming").parseClaimsJws(jwt).getBody();
System.out.println(claims);
}
运行效果:
eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI2NjYiLCJzdWIiOiJ0ZXN0Snd0IiwiaWF0IjoxNjg1MjU1ODk1LCJ1c2VySWQiOiIxMjMifQ.UdeEEeq3oRg8wwImn0DVFMm35uw__xuyXdgBAADLe8g
{jti=666, sub=testJwt, iat=1685255895, userId=123}