10分钟搞定OAuth2

  • 第二部分我们称其为载荷(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进阶篇知识点

image

Kafka高级篇知识点

image

44个Kafka知识点(基础+进阶+高级)解析如下

image

由于篇幅有限,小编已将上面介绍的**《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文档**

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值