oauth2.0+jwt 源码探究之旅

2023-05-16

oauth2.0协议是一种对外开放式协议,主要用于第三方登录授权。

例如:在豆瓣官网点击用qq登录

以及微信的授权都是基于oauth2.0协议做的。

 

oauth2.0的认证流程

(A)用户打开客户端,客户端要求用户给予授权。

(B)用户同意给予客户端授权。

(C)客户端使用上一步获得的授权(一般是Code),向认证服务器申请令牌TOKEN。

(D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌。

(E)客户端使用令牌,向资源服务器申请获取资源(用户信息等)。

(F)资源服务器确认令牌无误,同意向客户端开放资源。

主要分为

1:Authorization serve:授权服务器。

2:client 客户端

3:Resource server:资源服务器,即服务提供商存放用户生成的资源的服务器。它与认证服务器,可以是同一台服务器,也可以是不同的服务器。

oauth2.0的五种协议:

授权码模式(authorization code)

简化模式(implicit)

密码模式(resource owner password credentials)

客户端模式(client credentials)

扩展模式(Extension)

 

1 授权码模式

 

2 简化模式

不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过"授权码"这个步骤。

所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证

步骤如下:

(A)客户端将用户导向认证服务器。

(B)用户决定是否给于客户端授权。

(C)若用户授权,认证服务器将用户导向客户端指定的"重定向URI",并在URI的Hash部分包含了访问令牌。

(D)浏览器向资源服务器发出请求,其中不包括上一步收到的Hash值。

(E)资源服务器返回一个网页,其中包含的代码可以获取Hash值中的令牌。

(F)浏览器执行上一步获得的脚本,提取出令牌。

(G)浏览器将令牌发给客户端。

 3 密码模式

(A)用户向客户端提供用户名和密码。

(B)客户端将用户名和密码发给认证服务器,向后者请求令牌。

(C)认证服务器确认无误后,向客户端提供访问令牌。

4客户端模式

指客户端以自己的名义,而不以用户的名义,向"服务提供商"进行认证。严格地说,客户端模式并不属于OAuth框架所要解决的问题。在这种模式中,用户直接向客户端注册,客户端以自己的名义要求"服务提供商"提供服务,其实不存在授权问题。

(A)客户端向认证服务器进行身份认证,并要求一个访问令牌。

(B)认证服务器确认无误后,向客户端提供访问令牌。

 

jwt介绍

jwt 主要由3个部分组成

3.1 JWT头

JWT头部分是一个描述JWT元数据的JSON对象,通常如下所示。

{

"alg": "HS256",

"typ": "JWT"

}

3.2 有效载荷

有效载荷部分,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。 JWT指定七个默认字段供选择。

iss:发行人

exp:到期时间

sub:主题

aud:用户

nbf:在此之前不可用

iat:发布时间

jti:JWT ID用于标识该JWT

除以上默认字段外,我们还可以自定义私有字段,如下例:

{

"sub": "1234567890",

"name": "chongchong",

"admin": true

}

请注意,默认情况下JWT是未加密的,任何人都可以解读其内容,因此不要构建隐私信息字段,存放保密信息,以防止信息泄露。

JSON对象也使用Base64 URL算法转换为字符串保存。

3.3签名哈希

签名哈希部分是对上面两部分数据签名,通过指定的算法生成哈希,以确保数据不会被篡改。

首先,需要指定一个密码(secret)。该密码仅仅为保存在服务器中,并且不能向用户公开。然后,使用标头中指定的签名算法(默认情况下为HMAC SHA256)根据以下公式生成签名。

HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload),

secret)

在计算出签名哈希后,JWT头,有效载荷和签名哈希的三个部分组合成一个字符串,每个部分用"."分隔,就构成整个JWT对象。

jwt和普通token的区别,主要是普通token的加密解密都是由我们自己定义,但是jwt是基于一种加密标准,统一了token的各自加密,大家遵守的一种标准。

 

oauth2.0和jwt实现 登录验证

 1 :资源服务器的配置


@Configuration
@EnableResourceServer
public class OAuth2ResourceServer extends
ResourceServerConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated().and()
.requestMatchers().antMatchers("/api/**");
}
}



application.properties配置
server.port=8081
security.oauth2.resource.jwt.key-value=123456




2 :授权服务器的配置

@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServer extends AuthorizationServerConfigurerAdapter{

@Autowired
private AuthenticationManager authenticationManager;

@Value("${security.oauth2.jwt.signingKey}")
private String signingKey;

@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter=new JwtAccessTokenConverter();
jwtAccessTokenConverter.setSigningKey(signingKey);
return jwtAccessTokenConverter;
}
@Bean
public JwtTokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer authorizationServerEndpointsConfigurer) throws Exception{
authorizationServerEndpointsConfigurer
.authenticationManager(authenticationManager)
.tokenStore(tokenStore())
.accessTokenConverter(accessTokenConverter());
}
@Override
public void configure(ClientDetailsServiceConfigurer clients)throws Exception{
clients.inMemory().withClient("clientapp")
.secret("112233")
.scopes("read_userinfo")
.authorizedGrantTypes(
"password",
"authorization_code",
"refresh_token");
}
}

application.properties配置
security.user.name=zhu

security.user.password=xiang
#jwt的密钥
security.oauth2.jwt.signingKey=123456

这样就配置好了。
获取token,访问授权服务器
http://localhost:8080/oauth/token?grant_type=password&username=zhu&password=xiang&scope=read_userinfo

 


1 进入  /oauth/token ,到  

TokenEndpoint  

public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
if (!(principal instanceof Authentication)) {
throw new InsufficientAuthenticationException("There is no client authentication. Try adding an appropriate authentication filter.");
} else {
String clientId = this.getClientId(principal);
//获取客户端信息 InMemoryClientDetailsService和JdbcClientDetailsService 是根据启动时候加载授权服务的时候clients.inMemory().withClient("clientapp") 决定加载哪个,我这边明显就是从内存获取那个

ClientDetails authenticatedClient = this.getClientDetailsService().loadClientByClientId(clientId);
TokenRequest tokenRequest = this.getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);
if (clientId != null && !clientId.equals("") && !clientId.equals(tokenRequest.getClientId())) {
throw new InvalidClientException("Given client ID does not match authenticated client");
} else {
if (authenticatedClient != null) {
this.oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient);
}

if (!StringUtils.hasText(tokenRequest.getGrantType())) {
throw new InvalidRequestException("Missing grant type");
} else if (tokenRequest.getGrantType().equals("implicit")) {
throw new InvalidGrantException("Implicit grant type not supported from token endpoint");
} else {
if (this.isAuthCodeRequest(parameters) && !tokenRequest.getScope().isEmpty()) {
this.logger.debug("Clearing scope of incoming token request");
tokenRequest.setScope(Collections.emptySet());
}

if (this.isRefreshTokenRequest(parameters)) {
tokenRequest.setScope(OAuth2Utils.parseParameterList((String)parameters.get("scope")));
}
//校验账号密码以及生成token,我们这边由于是从内存中校验,所以就不需要重写userdetailService,否则是加载数据库的话 需要重写
//在DefaultTokenServices OAuth2AccessToken accessToken = this.createAccessToken(authentication, refreshToken); 生成token
//存储token this.tokenStore.storeAccessToken(accessToken, authentication); 分为四种模式 1 redis,2 jdbc 3 内存 4 jwt 其实jwt并不需要存储的地方,因为它本身就是一种算法加密来的,通过再次加密就可以获取到,所以说它的tokenstore是为空的

                OAuth2AccessToken token = this.getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
if (token == null) {
throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());
} else {
return this.getResponse(token);
}
}
}
}

获取到token 后
访问 http://localhost:8081/api/userinfo

校验token


OAuth2AuthenticationProcessingFilter 会拦截url请求  

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
boolean debug = logger.isDebugEnabled();
HttpServletRequest request = (HttpServletRequest)req;
HttpServletResponse response = (HttpServletResponse)res;

try {
Authentication authentication = this.tokenExtractor.extract(request);
if (authentication == null) {
if (this.stateless && this.isAuthenticated()) {
if (debug) {
logger.debug("Clearing security context.");
}

SecurityContextHolder.clearContext();
}

if (debug) {
logger.debug("No token in request, will continue chain.");
}
} else {
request.setAttribute(OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE, authentication.getPrincipal());
if (authentication instanceof AbstractAuthenticationToken) {
AbstractAuthenticationToken needsDetails = (AbstractAuthenticationToken)authentication;
needsDetails.setDetails(this.authenticationDetailsSource.buildDetails(request));
}
//检查token
Authentication authResult = this.authenticationManager.authenticate(authentication);
if (debug) {
logger.debug("Authentication success: " + authResult);
}

this.eventPublisher.publishAuthenticationSuccess(authResult);
SecurityContextHolder.getContext().setAuthentication(authResult);
}
} catch (OAuth2Exception var9) {
SecurityContextHolder.clearContext();
if (debug) {
logger.debug("Authentication request failed: " + var9);
}

this.eventPublisher.publishAuthenticationFailure(new BadCredentialsException(var9.getMessage(), var9), new PreAuthenticatedAuthenticationToken("access-token", "N/A"));
this.authenticationEntryPoint.commence(request, response, new InsufficientAuthenticationException(var9.getMessage(), var9));
return;
}

chain.doFilter(request, response);

}

OAuth2AuthenticationManager类  

public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (authentication == null) {
throw new InvalidTokenException("Invalid token (token not found)");
} else {
String token = (String)authentication.getPrincipal();
。 //查找token验证,
OAuth2Authentication auth = this.tokenServices.loadAuthentication(token);
if (auth == null) {
throw new InvalidTokenException("Invalid token: " + token);
} else {
Collection<String> resourceIds = auth.getOAuth2Request().getResourceIds();
if (this.resourceId != null && resourceIds != null && !resourceIds.isEmpty() && !resourceIds.contains(this.resourceId)) {
throw new OAuth2AccessDeniedException("Invalid token does not contain resource id (" + this.resourceId + ")");
} else {
this.checkClientDetails(auth);
if (authentication.getDetails() instanceof OAuth2AuthenticationDetails) {
OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails)authentication.getDetails();
if (!details.equals(auth.getDetails())) {
details.setDecodedDetails(auth.getDetails());
}
}

auth.setDetails(authentication.getDetails());
auth.setAuthenticated(true);
return auth;
}
}
}
}

DefaultTokenServices类  

public OAuth2Authentication loadAuthentication(String accessTokenValue) throws AuthenticationException, InvalidTokenException {
OAuth2AccessToken accessToken = this.tokenStore.readAccessToken(accessTokenValue);
if (accessToken == null) {
throw new InvalidTokenException("Invalid access token: " + accessTokenValue);
} else if (accessToken.isExpired()) {
this.tokenStore.removeAccessToken(accessToken);
throw new InvalidTokenException("Access token expired: " + accessTokenValue);
} else {
//这边从之前储存的地方获取,也有4种,本次是从jwt中获取

        OAuth2Authentication result = this.tokenStore.readAuthentication(accessToken);
if (result == null) {
throw new InvalidTokenException("Invalid access token: " + accessTokenValue);
} else {
if (this.clientDetailsService != null) {
String clientId = result.getOAuth2Request().getClientId();

try {
this.clientDetailsService.loadClientByClientId(clientId);
} catch (ClientRegistrationException var6) {
throw new InvalidTokenException("Client not valid: " + clientId, var6);
}
}

return result;
}
}
}







   

 

转载于:https://www.cnblogs.com/zhuxiang/p/11055336.html

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

oauth2.0+jwt 源码探究之旅 的相关文章

随机推荐

  • 233

    include lt bits stdc 43 43 h gt define reg register int define il inline define fi first define se second define mk a b
  • 再探容斥好题——ROOK

    这个时候考过 xff1a 安师大附中集训 Day2 当时看shadowice1984的做法 xff0c 但是没有亲自写 xff0c xff0c xff0c 雅礼集训考试的时候鼓捣半天 xff0c 被卡常到80pts xff0c 要跑9s 卡
  • CF908G New Year and Original Order

    CF908G New Year and Original Order gzz讲过 xff0c 可我到今天还是不会 有点trick的数位DP 比较显然的思路是 xff0c 考虑所有数中排序后每一位的贡献 cnt i x 表示S 1 S x 第
  • 本地在不安装Oracle的情况下安装PLSQL客户端

    本文解决问题 xff1a 通常在本地安装PLSQL后 xff0c 如果本地没有安装Oracle数据库的话 xff0c PLSQL是不能使用的 xff0c 输入远程数据库登录信息会提示 xff1a Oracle Client没有正确安装 这个
  • Ubuntu的中文乱码问题

    目标 xff1a 使系统 服务器支持中文 xff0c 能够正常显示 1 首先 xff0c 安装中文支持包language pack zh hans xff1a sudo apt get install language pack zh ha
  • Python argparse 处理命令行小结

    Python argparse 处理命令行小结 1 关于argparse 是python的一个命令行解析包 xff0c 主要用于处理命令行参数 2 基本用法 test py是测试文件 xff0c 其内容如下 import argparse
  • 分布式系统心跳协议的设计

    分布式系统心跳协议的设计 应用层心跳必不可少 xff1a 1 操作系统崩溃导致机器重启 没有机会发送 FIN 分节 2 服务器硬件故障导致机器重启 也没有机会发送 FIN 分节 3 并发连接数很高时 操作系统或进程如果重启 可能没有机会断开
  • malloc vs memset

    malloc vs memset OS内存分配过程如下 xff1a 用户态程序使用malloc接口 xff0c 分配虚拟地址 用户程序访问该虚拟地址 xff0c 比如memset 硬件 xff08 MMU xff09 需要将虚拟地址转换为物
  • c++ rvo vs std::move

    c 43 43 rvo vs std move To summarize RVO is a compiler optimization technique while std move is just an rvalue cast whic
  • linux du

    1 显示当前目录及子目录每个文件占用的块数量 du 2 显示当前目录占用的块数量 du s 3 查看当前目录占用空间啊大小 xff08 空间大小 61 块数量 块大小 xff09 du sh 4 查看当前目录各个文件占用空间大小 du sh
  • python绘制散点图入门

    import matplotlib pyplot as plt x values 61 list range 1 1001 y values 61 x 2 for x in x values plt scatter x values y v
  • linux 配置 l2tp-client

    1 安装yum源 yum install epel release 1 安装xl2tpd和ppp yum y install xl2tpd ppp 3 配置xl2tpd conf xff0c 原始的xl2tpd conf里面有 lns de
  • ros开启快速转发模式

    RB CCR设备开启FastTrack ip firewall filter add chain 61 forward action 61 fasttrack connection connection state 61 establish
  • ROS的脚本多拨

    ros设置单网卡拨多ADSL 使用vrrp 有些版本有问题 xff0c 使用5 2破解版本测试成功 1 创建100个vrrp 并绑定到wan口下 for i from 1 to 100 do 61 interface vrrp add co
  • zabbix4.0 相关的拓扑图及centos的虚拟配置

    zabbix的拓扑图相关资料 https blog 51cto com qicheng0211 1591073 zabbix配合 grafana zabbix 安装好后 参考 grafana 官方文档 https grafana com g
  • 通过TCP协议发送DNS请求

    通过TCP协议发送DNS请求的方法 文章出处 xff1a http www bingtech net wordpress 2011 04 233 下载dnsapi dll文件 然后到Google xff0c 搜索 替换系统文件 replac
  • PLC实现积分的计算方法

    以电机运行转速来计算电机运行圈数为例 在PLC中定义定时器中断 xff0c 中断时间设置为200ms 转载于 https www cnblogs com chenpan6227 p 11558647 html
  • PLC中相关量的斜坡控制

    转载于 https www cnblogs com chenpan6227 p 11558750 html
  • PLC中m法计算电机转速

    转载于 https www cnblogs com chenpan6227 p 11558715 html
  • oauth2.0+jwt 源码探究之旅

    oauth2 0协议是一种对外开放式协议 xff0c 主要用于第三方登录授权 例如 xff1a 在豆瓣官网点击用qq登录 以及微信的授权都是基于oauth2 0协议做的 oauth2 0的认证流程 xff08 A xff09 用户打开客户端