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
感兴趣的小伙伴可以看看
学习一下
录制不易,记得三联哦!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)