spring security oauth 源码解析(jwt角度)

spring security oauth 源码解析(jwt角度)

jwt

JWT(JSON Web Token)

jwt 的结构

jwt 包含三部分

  • Header
  • Payload
  • Signature

基本呈现为 aaaaa.xxxxx.zzzzz

Header: 通常由两部分组成:令牌的类型,即 JWT,以及正在使用的签名算法。

example:

{
  "alg": "HS256",
  "typ": "JWT"
}

然后这个json字符串被Base64Url方式进行编码,行程jwt的第一部分 aaaaa。

Payload:该部分是自定义的,没有强制要求。但IANA JSON Web Token Registry里面为我们定义了一些公共的命名可以参考。

比如jti。

example:

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

然后对有效负载进行Base64Url编码以形成 JSON Web 令牌的第二部分 xxxxx。

Signature: 签名,是用头中指定的签名算法,将 base64Url后的第一部分和第二部分,使用私钥进行签名的部分。然后就行程了第三部分zzzzz。

参见

JWT.IO

载体公共命名空间

加密及验签

参考支付系统中如何应用加密方式一文

spring security oauth 生成 jwt

首先调用获取token的接口/oauth/token。

//org.springframework.security.oauth2.provider.endpoint.TokenEndpoint#postAccessToken
Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {

		//...
    	//获取客户端信息
		ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);
		//构造一个token request
		TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);

		//...一些必要的校验工作
    	//调用TokenGranter 生成OAuth2AccessToken
		OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);
		if (token == null) {
			throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());
		}

		return getResponse(token);

	}

也就是说我们token 的生成逻辑在该方法里面,我们以ResouceOwnerPasswordTokenGrante token 生成器进行展开分析。

在这里插入图片描述

public abstract class AbstractTokenGranter implements TokenGranter {
	
	public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {
		String clientId = tokenRequest.getClientId();
		ClientDetails client = clientDetailsService.loadClientByClientId(clientId);
        //验证该客户端是都支持 该授权类型
		validateGrantType(grantType, client);
        //调用方法获取token
		return getAccessToken(client, tokenRequest);

	}
    protected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) {
        //通过 tokenServices进行 token 的创建
		return tokenServices.createAccessToken(getOAuth2Authentication(client, tokenRequest));
	}

}
public interface AuthorizationServerTokenServices {
	//会根据传入的 OAuth2Authentication 去创建token
	OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException;
}

那先来看一下OAuth2Authentication 是如何获取的。

public class ResourceOwnerPasswordTokenGranter extends AbstractTokenGranter {

	private static final String GRANT_TYPE = "password";

	@Override
	protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {

		Map<String, String> parameters = new LinkedHashMap<String, String>(tokenRequest.getRequestParameters());
		String username = parameters.get("username");
		String password = parameters.get("password");
		// Protect from downstream leaks of password
		parameters.remove("password");

		Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);
		((AbstractAuthenticationToken) userAuth).setDetails(parameters);
		try {
            //调用正常的认证流程去进行认证,该方法最终会调用 DaoAuthenticationProvider,进行authenticatie,这里我们就不详细展开了
			userAuth = authenticationManager.authenticate(userAuth);
		}
		catch (AccountStatusException ase) {
			throw new InvalidGrantException(ase.getMessage());
		}
		catch (BadCredentialsException e) {
			// If the username/password are wrong the spec says we should send 400/invalid grant
			throw new InvalidGrantException(e.getMessage());
		}
		if (userAuth == null || !userAuth.isAuthenticated()) {
			throw new InvalidGrantException("Could not authenticate user: " + username);
		}
		
		OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest);		
		//这里我们看到OAuth2Authentication,是由Authentication, 和 request 进行构建的
        return new OAuth2Authentication(storedOAuth2Request, userAuth);
	}
}

有了 OAuth2Authentication ,我们在看看 是如何创建 Token 的。

public class DefaultTokenServices implements AuthorizationServerTokenServices, ResourceServerTokenServices,
      ConsumerTokenServices, InitializingBean {
   @Transactional
   public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {

      OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
      OAuth2RefreshToken refreshToken = null;
      if (existingAccessToken != null) {
            return existingAccessToken;
      }

    //忽略掉 refreshToken
      if (refreshToken == null) {
         refreshToken = createRefreshToken(authentication);
      }
	//创建token
      OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
      tokenStore.storeAccessToken(accessToken, authentication);
      // In case it was modified
      refreshToken = accessToken.getRefreshToken();
      if (refreshToken != null) {
         tokenStore.storeRefreshToken(refreshToken, authentication);
      }
      return accessToken;

   }
          
   private OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken) {
		DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(UUID.randomUUID().toString());
       //获取有效期
		int validitySeconds = getAccessTokenValiditySeconds(authentication.getOAuth2Request());
		if (validitySeconds > 0) {
			token.setExpiration(new Date(System.currentTimeMillis() + (validitySeconds * 1000L)));
		}
		token.setRefreshToken(refreshToken);
		token.setScope(authentication.getOAuth2Request().getScope());
		// accessTokenEnhancer.enhance(token, authentication) 获取token,本来 我们构建的token 是能直接用的,但是这提供一个能力给定制 Token, 比如jwt
		return accessTokenEnhancer != null ? accessTokenEnhancer.enhance(token, authentication) : token;
	}
}
          

在这里插入图片描述

//JwtAccessTokenConverter  进行转化
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
		DefaultOAuth2AccessToken result = new DefaultOAuth2AccessToken(accessToken);
		Map<String, Object> info = new LinkedHashMap<String, Object>(accessToken.getAdditionalInformation());
		String tokenId = result.getValue();
		if (!info.containsKey(TOKEN_ID)) {
			info.put(TOKEN_ID, tokenId);
		}
		else {
			tokenId = (String) info.get(TOKEN_ID);
		}
    	
		result.setAdditionalInformation(info);
    //我们看到这里面,说一下 这个 value 就是我们生成好的 jwt token 了,也就是关键逻辑在这里面
		result.setValue(encode(result, authentication));
		OAuth2RefreshToken refreshToken = result.getRefreshToken();
		if (refreshToken != null) {
        }
		return result;
	}
protected String encode(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
		String content;
		try {
            //获取jwt 的 payLoad,而这就是给我们一个能力去填充 jwt 的payLoad
			content = objectMapper.formatMap(tokenConverter.convertAccessToken(accessToken, authentication));
		}
		catch (Exception e) {
			throw new IllegalStateException("Cannot convert access token to JSON", e);
		}
    	//这里,具体的 生成jwt 的逻辑就是放在了这个方法里面
		String token = JwtHelper.encode(content, signer).getEncoded();
		return token;
	}
 public static Jwt encode(CharSequence content, Signer signer) {
        return encode(content, signer, Collections.emptyMap());
    }

    public static Jwt encode(CharSequence content, Signer signer, Map<String, String> headers) {
        //head
        JwtHeader header = JwtHeaderHelper.create(signer, headers);
        //payLoad
        byte[] claims = Codecs.utf8Encode(content);
        //生成签名
        byte[] crypto = signer.sign(Codecs.concat(new byte[][]{Codecs.b64UrlEncode(header.bytes()), PERIOD, Codecs.b64UrlEncode(claims)}));
        //jwt 生成
        return new JwtImpl(header, claims, crypto);
    }
public String getEncoded() {
        return Codecs.utf8Decode(this.bytes());
    }

 public byte[] bytes() {
        return Codecs.concat(new byte[][]{Codecs.b64UrlEncode(this.header.bytes()), JwtHelper.PERIOD, Codecs.b64UrlEncode(this.content), JwtHelper.PERIOD, Codecs.b64UrlEncode(this.crypto)});
    }
}

看到 bytes() 方法,我们就看完了jwt 的创建的整个过程。

通过上面的逻辑我们知道 ,payLoad 的设置能力是交给了tokenConverter.convertAccessToken(accessToken, authentication)。

我们接着分析:

//AccessTokenConverter
public interface AccessTokenConverter {

	final String AUD = "aud";

	final String CLIENT_ID = "client_id";

	final String EXP = "exp";

	final String JTI = "jti";
	
	final String GRANT_TYPE = "grant_type";

	final String ATI = "ati";

	final String SCOPE = OAuth2AccessToken.SCOPE;

	final String AUTHORITIES = "authorities";
	//生成 payLoad 的方法
	Map<String, ?> convertAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication);
	
	OAuth2AccessToken extractAccessToken(String value, Map<String, ?> map);
	//根据 payLoad生成OAuth2Authentication的方法
	OAuth2Authentication extractAuthentication(Map<String, ?> map);

}

在这里插入图片描述

//DefaultAccessTokenConverter
public Map<String, ?> convertAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
		Map<String, Object> response = new HashMap<String, Object>();
		OAuth2Request clientToken = authentication.getOAuth2Request();

		if (!authentication.isClientOnly()) {
            //这里调用UserAuthenticationConverter 根据 authentication 获取 PayLoad
			response.putAll(userTokenConverter.convertUserAuthentication(authentication.getUserAuthentication()));
		} else {
			if (clientToken.getAuthorities()!=null && !clientToken.getAuthorities().isEmpty()) {
				response.put(UserAuthenticationConverter.AUTHORITIES,
							 AuthorityUtils.authorityListToSet(clientToken.getAuthorities()));
			}
		}

		if (token.getScope()!=null) {
			response.put(scopeAttribute, token.getScope());
		}
		if (token.getAdditionalInformation().containsKey(JTI)) {
			response.put(JTI, token.getAdditionalInformation().get(JTI));
		}

		if (token.getExpiration() != null) {
			response.put(EXP, token.getExpiration().getTime() / 1000);
		}
		
		if (includeGrantType && authentication.getOAuth2Request().getGrantType()!=null) {
			response.put(GRANT_TYPE, authentication.getOAuth2Request().getGrantType());
		}

		response.putAll(token.getAdditionalInformation());

		response.put(clientIdAttribute, clientToken.getClientId());
		if (clientToken.getResourceIds() != null && !clientToken.getResourceIds().isEmpty()) {
			response.put(AUD, clientToken.getResourceIds());
		}
		return response;
	}

时序图

在这里插入图片描述

spring security oauth 解析jwt

入口点在 OAuth2AuthenticationProcessingFilter 这个 连接器中,我们看一下doFilter() 方法中做了哪些事情。

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException,
			ServletException {
		try {
			//这个方法就是简单将token 值赋值进去
			Authentication authentication = tokenExtractor.extract(request);
			
			if (authentication == null) {
			}
			else {
                //其实这里面 的authenticationManager 是OAuth2AuthenticationManager,具体获取的逻辑在里面
				Authentication authResult = authenticationManager.authenticate(authentication);
			}
		}
		catch (OAuth2Exception failed) {
			//异常处理
		}

		chain.doFilter(request, response);
	}
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		String token = (String) authentication.getPrincipal();
    //调用 tokenServices loadAuthentication方法解析token,并最终获取tokenServices
		OAuth2Authentication auth = tokenServices.loadAuthentication(token);
		return auth;

	}
public OAuth2Authentication loadAuthentication(String accessTokenValue) throws AuthenticationException,
			InvalidTokenException {
		//解析token
         OAuth2AccessToken accessToken = tokenStore.readAccessToken(accessTokenValue);
		//获取授权
		OAuth2Authentication result = tokenStore.readAuthentication(accessToken);
		
		return result;
	}
public OAuth2AccessToken readAccessToken(String tokenValue) {
		OAuth2AccessToken accessToken = convertAccessToken(tokenValue);
		return accessToken;
	}
private OAuth2AccessToken convertAccessToken(String tokenValue) {
		return jwtTokenEnhancer.extractAccessToken(tokenValue, jwtTokenEnhancer.decode(tokenValue));
	}

public OAuth2AccessToken extractAccessToken(String value, Map<String, ?> map) {
		DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(value);
		Map<String, Object> info = new HashMap<String, Object>(map);
		info.remove(EXP);
		info.remove(AUD);
		info.remove(clientIdAttribute);
		info.remove(scopeAttribute);
		if (map.containsKey(EXP)) {
			token.setExpiration(new Date((Long) map.get(EXP) * 1000L));
		}
		if (map.containsKey(JTI)) {
			info.put(JTI, map.get(JTI));
		}
		token.setScope(extractScope(map));
		token.setAdditionalInformation(info);
		return token;
	}

最终会将 token 中 payLoad 的值来创建 OAuth2AccessToken。

现在有了OAuth2AccessToken,剩下的工作就是生成 Authentication

public OAuth2Authentication extractAuthentication(Map<String, ?> map) {
		Map<String, String> parameters = new HashMap<String, String>();
		Set<String> scope = extractScope(map);
    	//该converter 用来生成 Authentication
		Authentication user = userTokenConverter.extractAuthentication(map);
		OAuth2Request request = new OAuth2Request(parameters, clientId, authorities, true, scope, resourceIds, null, null,
				null);
		return new OAuth2Authentication(request, user);
	}
	public Authentication extractAuthentication(Map<String, ?> map) {
		if (map.containsKey(USERNAME)) {
			Object principal = map.get(USERNAME);
			Collection<? extends GrantedAuthority> authorities = getAuthorities(map);
			if (userDetailsService != null) {
                //调用 userDetailsService 获取详情
				UserDetails user = userDetailsService.loadUserByUsername((String) map.get(USERNAME));
				authorities = user.getAuthorities();
				principal = user;
			}
			return new UsernamePasswordAuthenticationToken(principal, "N/A", authorities);
		}
		return null;
	}

涉及接口

获取jwt相关
public interface TokenGranter {
	OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest);
}
public interface AuthorizationServerTokenServices {
	
	OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException;

	OAuth2AccessToken refreshAccessToken(String refreshToken, TokenRequest tokenRequest)
			throws AuthenticationException;

	OAuth2AccessToken getAccessToken(OAuth2Authentication authentication);

}
public interface TokenEnhancer {
	OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication);

}
public interface AccessTokenConverter {

	final String AUD = "aud";

	final String CLIENT_ID = "client_id";

	final String EXP = "exp";

	final String JTI = "jti";
	
	final String GRANT_TYPE = "grant_type";

	final String ATI = "ati";

	final String SCOPE = OAuth2AccessToken.SCOPE;

	final String AUTHORITIES = "authorities";
	//生成 jwt payLoad
	Map<String, ?> convertAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication);
	//根据 payLoad 生成Token
	OAuth2AccessToken extractAccessToken(String value, Map<String, ?> map);
	//根据payLoad获取OAuth2Authentication
	OAuth2Authentication extractAuthentication(Map<String, ?> map);

}
jwt 转化为 Oauth2Authentication相关
public interface TokenExtractor {

	/**
	 * Extract a token value from an incoming request without authentication.
	 */
	Authentication extract(HttpServletRequest request);

}
public interface AuthenticationManager {
	Authentication authenticate(Authentication authentication)
			throws AuthenticationException;
}
public interface ResourceServerTokenServices {

	/**
	 * Load the credentials for the specified access token.
	 */
	OAuth2Authentication loadAuthentication(String accessToken) throws AuthenticationException, InvalidTokenException;

	/**
	 * Retrieve the full access token details from just the value.
	 */
	OAuth2AccessToken readAccessToken(String accessToken);

}
public interface TokenStore {	
	OAuth2AccessToken readAccessToken(String tokenValue);
	OAuth2Authentication readAuthenticationForRefreshToken(OAuth2RefreshToken token);
}
public interface UserAuthenticationConverter {

	final String AUTHORITIES = AccessTokenConverter.AUTHORITIES;

	final String USERNAME = "user_name";
	//根据 authentication 获取 payLoad
	Map<String, ?> convertUserAuthentication(Authentication userAuthentication);

	//根据  payLoad 获取 获取Authentication
	Authentication extractAuthentication(Map<String, ?> map);

}

参考

JWT.IO

PayLoad公共命名空间

支付系统中如何应用加密方式

学习资料推广

我已经将springamqp 源码解析录制为视频上传到bibi,分为六个章节详细介绍了各个模块的具体内容

https://www.bilibili.com/video/BV1hN411Z7fn?share_source=copy_web

感兴趣的小伙伴可以看看
学习一下
录制不易,记得三联哦!

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值