JWT(JSON Web Token)是一个开放标准,用于在各方之间以JSON对象安全地传输信息。
1.JWT请求流程
- 用户使用浏览器发送账号和密码
2.服务器使用私钥创建一个JWT
3.服务器返回这个JWT给浏览器
4.浏览器将该JWT放在请求头中向服务器发送请求
5.服务器验证JWT
6.根据授权规则返回资源给浏览器
2.JWT的组成
JWT的格式为Header.Payload.Signature, 分为三个部分,header,payload,signature
在实际的应用中,JWT作为一个无状态的授权校验技术,非常适合分布式系统架构。服务器不需要存储用户状态,无需采用redis等技术来实现各个服务节点之间共享Session数据。
JWT的格式为:Header.Payload.Signature,即JWT包含3部分。为Header、payload、和signature。
header是通过Base64编码生成的字符串,header中存放的内容说明编码对象是一个JWT。
payload主要包含claim(断言),claim是一些实体的状态和额外的元数据,有三种类型:Reserved、Public和Private.
Reserved 预定义的
Public claim 根据需要定义自己的字段
Private claim自定义字段,可以用来双方之间交换信息负载。需要经过Base64Url编码后作为JWT的结构第二部分。
signature签名
签名需要使用编码后的header和payload及一个密钥,使用header中指定的签名算法进行签名。
流程:
1.将header和claim分别使用Base64编码,生成字符串header和payload。
2.将header和payload字符串以header.payload格式组合在一起,形成一个字符串。
3.使用过上面定义好的加密算法和一个存放在服务器上用于进行验证的密钥来对这个字符串进行加密,形成一个新的字符串,这个字符串就是signature。
接下来使用一个小demo来记录实际开发过程中的应用
3.下载地址
https://gitee.com/xgkp/yzjwt.git
4.配置安全类
JWT安全配置需要继承WebSecurityConfigurerAdapter,然后重写方法.
public class WebSecurityConfigJwt extends WebSecurityConfigurerAdapter
装载BCryptPasswordEncoder密码编码器
@Bean
public PasswordEncoder passwordEncoder3() {
return BCryptPasswordEncoder();
}
常规配置
@Override
propected void configure(HttpSecurity http) {
}
注意这里的详细配置
0.http.antMatcher("/jwt/**").
formLogin().usernameParameter("name").
passwordParameter("pwd").loginPage("/jwt/login").
successHander(jwtAuthenticationSuccesshandler).
failureHandler(jwtAuthenticatioFalHander)
这里指定登录的控制器
.and().authorizeRequests()
认证拼接
1.antMatchers("/register/mobile").permitAll()
表示手机注册这个接口的路径,谁都可以访问的
2.antMatchers("/article/\**\*").authenticated()
表示需要登陆之后才能访问article/路径下的资源
3.antMatchers("/jwt/tasks/**").hasRole("USER")
表示需要登录且角色为USER的才能访问jwt/tasks/路径下的资源。
5.处理注册
这里通过用户名和手机号注册:
1.先判断用户名是否被占用,再判断手机号是否被占用,常规操作,不解释。
2.接下来对密码进行加密操作 ,使用BCryptPasswordEncoder来实现
String pass = "admin";
BCryptPasswordEncoder bcryptPasswordEncoder = new BCryptPasswordEncoder();
String hashPass = bcryptPasswordEncoder.encode(pass);
System.out.println(hashPass);
这个方法的强大之处在于,即使你每次输入的密码是同一个值,但是它的出的结果是不一样的。而且虽然得出的结果不一样,但是校验还是能够通过。
3.用户信息角色绑定
List<Role> roles = new ArrayList<>();
UserRole role1 = userRoleRepository.findByRolename("ROLE_USER");
roles.add(role1);
user.setRoles(roles);
userRepository.save(user);
这里有两个需要 注意的地方
1.如果数据库role表里面没有内容,那么以上role1值肯定null
2.如果role表里有n内容,但是没有条数据满足role name= ROLE_USER,那么role1的值还是为null
这两种情况都会导致报错
A granted authority textual representation is requred
表示某个方法的实参不能为null
我这里的解决方案是测试之前在role表里面添加一条数据
id | cname | rolename |
---|---|---|
11 | canmetest | ROLE_USER |
不要问我为什么这里是ROLE_USER,我这里也是猜的,而猜测的根据来源是在代码中有个findRoleName("ROLE_USER")
6.处理登录
同样的逻辑处理 ,
1.先判断这个用户存不存在于数据库中,这时候为了方便多种方式安全验证登录,使用一个Service类来实现,简单逻辑不解释
@Service
public class JwtUserSecurityservice implements UseDetailServce
2.用户存在性验证完了以后需要验证后续处理,创建token之类的操作
这个是在另一个类中实现
@Component("jtAuthentcaionSuccessHandler")
public class JwtAuthenticationSuccesssHandler
extends
SavedReuestAwareAuthenticationSuccesHandler{}
重写一个方法
@Override
public void onAuthenticationSuccess
(HttpServletRequest httpServletRequest ,
HttpServletResponse httpServletResponse,
Authentication authentication)
throw IOException,ServletException {}
token值的生成和返回设置的实现思路
String token = JwtTokenUtils.createToken(user.getUsename(),role,true);
httpServletResponse.setHeader("token",JwtTokenUtils.TOKEN_PREFIX + token);
httpservletResponse.setContentType("application/json;charset=utf-8");
具体的参数实现细节
if (principal != null && principal instanceof UserDetails) {
UserDetails user = (UserDetails) principal;
httpServletRequest.getSession().setAttribute("userDetail", user);
String role = "";
Collection<? extends GrantedAuthority> authorities = user.getAuthorities();
for (GrantedAuthority authority : authorities){
role = authority.getAuthority();
}
//...此处省略上一步的核心代码
}
登录验证失败的时候,需要进行的后续处理,直接构造一个json类型的返回值返回即可
httpServletRequest.setCharacterEncoding("UTF-8");
// 获得用户名密码
String username = httpServletRequest.getParameter("uname");
String password = httpServletRequest.getParameter("pwd");
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter out = httpServletResponse.getWriter();
out.write("{\"status\":\"error\",\"message\":\"用户名或密码错误\"}");
out.flush();
out.close();
其它的报错
PageRequest(int,int,org.springframework.data.domain.Sort) 在 org.springframework
这个说明Spring Boot中原来的构造方法过期了,使用新的写法
Sort sort = Sort.by(Sort.Order.desc("create_date"));
Pageable pageable =
PageRequest.of(Integer.parseInt(page), Integer.parseInt(size), sort);
7.开始测试
1.注册测试
post man 工具:
7.pngpost方法
form-data
mobile:18201430259
name:wangchuang
password:123456
2.登录测试
7_2.png 7_3.pngpost 方法
form-data
name:wangchuang
password:123456
将图示位置的token复制出来备用
3.权限操作测试
获取任务列表
7_4.pngpost请求
参数无
Headers
Authorization:上一步复制出来的token
Content Type:application/x-www-form-urlencoded
看图7_4所示
这样请求的结果有两种情况
1.返回403或者500表示身份验证失败或者服务端的代码报错
2.返回200说明token的身份验证成功了
8.其他
JWTAuthorizationFilter类中调用了getAuthentication
方法,里面实现了一个通过原来的token新建一个新的token的方法
具体实现见如下代码
String token = tokenHeader.replace(JwtTokenUtils.TOKEN_PREFIX, "");
String username = JwtTokenUtils.getUsername(token);
String role = JwtTokenUtils.getUserRole(token);
if (username != null){
return new UsernamePasswordAuthenticationToken(username, null,
Collections.singleton(new SimpleGrantedAuthority(role))
);
}
总体来讲看着书本磕磕绊绊的完成了jwt的实际使用,对整体的验证登录流程有个基本的认识。虽说处理各种版本和报错提示有点儿坎坷,但结果还算是比较满意的,关于权限和角色的细节和其它操作需求未来慢慢补充。
学习的过程还真是嚼一路辛苦,饮一路汗水啊。
朋友圈大概主题都是:来自上海的航班正在逐渐点亮小岛~~
虽然有些抱怨,但是我们也知道闭关只能导致落后,举个不恰当的例子,清政府闭关那么多年,最后不还是被坚船利炮给打开了国门[手动狗头]
海南也有确诊病例了,有小区被管控了,有居家办公的前同事了,有同事路过封控区要求主动去做核酸了。期待疫情早日结束,还世界一份太平!!!
2022年04月01日 海口 复兴城
网友评论