-
第二部分我们称其为载荷(payload, 类似于飞机上承载的物品)
-
第三部分是签证(signature).
header
jwt的头部承载两部分信息:
-
声明类型,这里是jwt
-
声明加密的算法 通常直接使用 HMAC SHA256 完整的头部就像下面这样的JSON:
{
‘typ’: ‘JWT’,
‘alg’: ‘HS256’
}
然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分.
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
playload
载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分
-
标准中注册的声明
-
公共的声明
-
私有的声明
标准中注册的声明(建议但不强制使用) :
-
iss: jwt签发者
-
sub: jwt所面向的用户
-
aud: 接收jwt的一方
-
exp: jwt的过期时间,这个过期时间必须要大于签发时间
-
nbf: 定义在什么时间之前,该jwt都是不可用的.
-
iat: jwt的签发时间
-
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
公共的声明 :
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.
私有的声明 :
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。
定义一个payload:
{
“sub”: “1234567890”,
“name”: “John Doe”,
“admin”: true
}
然后将其进行base64加密,得到Jwt的第二部分。
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
signature
jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
-
header (base64后的)
-
payload (base64后的)
-
secret 这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。
// javascript
var encodedString = base64UrlEncode(header) + ‘.’ + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, ‘secret’); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
将这三部分用.连接成一个完整的字符串,构成了最终的jwt:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
二、案例demo
JWT的概念讲完了,接下来就给大家详细的介绍一下代码的具体实现,客户端和服务器调用的流程,可以参照下面过程:
引入JWT和Spring Security依赖
org.springframework.boot
spring-boot-starter-security
io.jsonwebtoken
jjwt
0.9.1
添加Web配置文件,我们需要将除了登陆授权以外的接口,都进行过滤拦截,校验Token的合法性。
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder
// 设置UserDetailsService
.userDetailsService(userDetailsService)
// 使用BCrypt进行密码的hash
.passwordEncoder(passwordEncoder());
}
// 装载BCrypt密码编码器
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {
return new JwtAuthenticationTokenFilter();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// 由于使用的是JWT,我们这里不需要csrf
.csrf().disable()
// 基于token,所以不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, “/**”).permitAll()
// 允许对于网站静态资源的无授权访问
.antMatchers(
HttpMethod.GET,
“/”,
“/*.html”,
“/favicon.ico”,
“/**/*.html”,
“/**/*.css”,
“/**/*.js”
).permitAll()
// 授权接口放通token校验
.antMatchers(“/authority/**/authorization/”).permitAll()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
// 添加JWT filter
httpSecurity.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
// 禁用缓存
httpSecurity.headers().cacheControl();
}
}
Web配置文件中我们可以看到,还需要UserDetailsService和JwtAuthenticationTokenFilter。UserDetailsService是Spring Security内部接口,我们需要实现该接口的loadUserByUsername方法,将查询到username和password返回,具体代码如下所示:
@Slf4j
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private TamadbUserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
TamadbUser userPo = userMapper.getUserBaseInfo(Integer.valueOf(userName));
if (userPo == null) {
log.error(“loadUserByUsername—>userName:{}不存在”, userName);
throw new UsernameNotFoundException(“用户名不存在”);
}
SysUserPo user = new SysUserPo();
user.setUsername(userPo.getId() + “”);
user.setPassword(userPo.getPassword());
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
final String rawPassword = user.getPassword();
user.setPassword(encoder.encode(rawPassword));
return user;
}
}
userMapper.getUserBaseInfo方法就是一个dao,用来查询数据库的用户信息,因为WebSecurityConfig配置文件,对密码配置了BCryptPasswordEncoder加密,但是数据库存储的是md5生成的密码,所以我们需要对密码进行等价加密。
我们接着来看一下JwtAuthenticationTokenFilter过滤器的内容:
@Component
@Log4j2
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired(required = false)
private UserDetailsService userDetailsService;
@Value(“${jwt.header}”)
private String header;
@Value(“${jwt.tokenHead}”)
private String tokenHead;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Override
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
String authHeader = request.getHeader(this.header);
if (authHeader != null && authHeader.startsWith(tokenHead) && authHeader.length() > tokenHead.length() + 1) {
// The part after "Bearer "
final String authToken = authHeader.substring(tokenHead.length() + 1);
String username = jwtTokenUtil.getUsernameFromToken(authToken);
log.info(“checking authentication,username:{},authToken:{}”, username, authToken);
// 校验token是否有效合法
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
// 校验token是否过期
if
必看视频!获取2024年最新Java开发全套学习资料 备注Java
(jwtTokenUtil.validateToken(authToken, userDetails)) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(
request));
log.info("authenticated user " + username + “, setting security context”);
SecurityContextHolder.getContext().setAuthentication(authentication);
} else {
log.error(“token过期,token:{}”, authToken);
}
} else {
log.error(“token失效,无法获取到用户信息,token:{}”, authHeader);
}
}
chain.doFilter(request, response);
}
}
过滤器就做一件事情,获取Http头部的Token信息,然后通过jwtTokenUtil解密Token,获取用户信息,最后检验Token是否过期。
我们最后来看看jwtTokenUtil工具类中,是如何生成、解密Token的。
@Component
@Log4j2
public class JwtTokenUtil implements Serializable {
private static final long serialVersionUID = -3301605591108950415L;
/**
- 用户id
*/
private static final String CLAIM_KEY_USERNAME = “sub”;
/**
- 用户登录信息
*/
private static final String AUTHORITY_USER_DETAIL = “detail”;
/**
- token创建时间
*/
private static final String CLAIM_KEY_CREATED = “created”;
@Value(“${jwt.secret}”)
private String secret;
@Value(“${jwt.expiration.pc.access}”)
private Long pcAccessExpiration;
@Value(“${jwt.expiration.pc.refresh}”)
private Long pcRefreshExpiration;
@Value(“${jwt.expiration.wechat.access}”)
private Long weChatAccessExpiration;
@Value(“${jwt.expiration.wechat.refresh}”)
private Long weChatRefreshExpiration;
/**
-
获取用户token
-
@param token
-
@return
*/
public String getUsernameFromToken(String token) {
String username;
try {
final Claims claims = getClaimsFromToken(token);
username = claims.getSubject();
} catch (Exception e) {
username = null;
}
return username;
}
/**
-
获取用户token
-
@param token
-
@return
*/
public AuthorityUserDto getUserDetailFromToken(String token) {
AuthorityUserDto detail;
try {
final Claims claims = getClaimsFromToken(token);
Object detailObject = claims.get(AUTHORITY_USER_DETAIL);
Gson gson = new Gson();
// 解析json
detail = gson.fromJson(gson.toJson(detailObject), AuthorityUserDto.class);
} catch (Exception e) {
detail = null;
}
return detail;
}
/**
-
获取token的创建时间
-
@param token
-
@return
*/
public Date getCreatedDateFromToken(String token) {
Date created;
try {
final Claims claims = getClaimsFromToken(token);
created = new Date((Long) claims.get(CLAIM_KEY_CREATED));
} catch (Exception e) {
created = null;
}
return created;
}
/**
-
获取token的过期时间
-
@param token
-
@return
*/
public Date getExpirationDateFromToken(String token) {
Date expiration;
try {
final Claims claims = getClaimsFromToken(token);
expiration = claims.getExpiration();
} catch (Exception e) {
expiration = null;
}
return expiration;
}
/**
-
调用jar生成token令牌
-
@param token
-
@return
*/
private Claims getClaimsFromToken(String token) {
Claims claims;
try {
claims = Jwts.parser()
.setSigningKey(secret)
Kafka进阶篇知识点
Kafka高级篇知识点
44个Kafka知识点(基础+进阶+高级)解析如下
由于篇幅有限,小编已将上面介绍的**《Kafka源码解析与实战》、Kafka面试专题解析、复习学习必备44个Kafka知识点(基础+进阶+高级)都整理成册,全部都是PDF文档**
ed;
}
/**
-
获取token的过期时间
-
@param token
-
@return
*/
public Date getExpirationDateFromToken(String token) {
Date expiration;
try {
final Claims claims = getClaimsFromToken(token);
expiration = claims.getExpiration();
} catch (Exception e) {
expiration = null;
}
return expiration;
}
/**
-
调用jar生成token令牌
-
@param token
-
@return
*/
private Claims getClaimsFromToken(String token) {
Claims claims;
try {
claims = Jwts.parser()
.setSigningKey(secret)
Kafka进阶篇知识点
[外链图片转存中…(img-eXlXUmtP-1716375265699)]
Kafka高级篇知识点
[外链图片转存中…(img-6YCwr45A-1716375265700)]
44个Kafka知识点(基础+进阶+高级)解析如下
[外链图片转存中…(img-PMj6m4aB-1716375265700)]
由于篇幅有限,小编已将上面介绍的**《Kafka源码解析与实战》、Kafka面试专题解析、复习学习必备44个Kafka知识点(基础+进阶+高级)都整理成册,全部都是PDF文档**