什么是jwt顶伞,即 json web token廉油。JWT是一種用于雙方之間傳遞安全信息的簡潔的蛙婴、URL安全的表述性聲明規(guī)范粗井。也是一種token,但是和token有一些不同街图。
jwt優(yōu)點:
- 自包含
- 防篡改
- 可自定義擴展
JWT的結構
JWT包含了使用.分割的三部分:
- Header 頭部
- Payload 負載
- Signature 簽名
比如:eyJpc3MiOiJKb2huIFd1IEpXVCIsImlhdCI6MTQ0MTU5MzUwMiwiZXhwIjoxNDQxNTk0NzIyLCJhdWQiOiJ3d3cuZXhhbXBsZS5jb20iLCJzdWIiOiJqcm9ja2V0QGV4YW1wbGUuY29tIiwiZnJvbV91c2VyIjoiQiIsInRhcmdldF91c2VyIjoiQSJ9
三個新東西
- 什么是自包含浇衬?
字符串里包含用戶信息。 - 什么是防篡改餐济?
簽名用于驗證消息的發(fā)送者以及消息是沒有經(jīng)過篡改的耘擂。 - 可擴展是什么意思?
你可以在Payload部分加入自己想加入的json字符串
那我們既然token的實現(xiàn)方式那么優(yōu)秀絮姆,為什么還要有jwt呢醉冤,這就需要了解他們的生成機制秩霍。
token生成的其實就是一個UUID,和業(yè)務沒有絲毫的關系蚁阳,這樣帶來最大的問題铃绒,就是需要人工持久化處理token(像處理分布式下的sessionId一樣)。但是jwt就不需要螺捐,因為自包含颠悬,所以token里有身份驗證信息,不需要做后臺持久化處理定血,前端每次請求被保護的資源時請求頭里帶上該token就可以實現(xiàn)赔癌。
好了開始正文。
本文建立在第三節(jié)的基礎上澜沟。這里是順風車
1. 新增JwtTokenConfig
@Configuration
public class JwtTokenConfig{
@Bean
public TokenStore jwtTokenStore(){
return new JwtTokenStore(jwtAccessTokenConverter());
}
/**
* token生成處理:指定簽名
*/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(){
JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
accessTokenConverter.setSigningKey("internet_plus");
return accessTokenConverter;
}
}
jwtTokenStore方法返回一個TokenStore對象的子對象JwtTokenStore灾票,供給認證服務器取來給授權服務器端點配置器,通俗點就是讓MyAuthorizationServerConfig能注入到值茫虽。
jwtAccessTokenConverter方法是根據(jù)簽名生成JwtToken刊苍,同樣也需要在MyAuthorizationServerConfig類里注入。
修改MyAuthorizationServerConfig 的 configure方法
/**
* 認證服務器
* Created by Fant.J.
*/
@Configuration
@EnableAuthorizationServer
public class MyAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private TokenStore tokenStore;
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
super.configure(security);
}
/**
* 客戶端配置(給誰發(fā)令牌)
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient(ConsParams.Auth.GET_CLIENT_ID)
.secret(ConsParams.Auth.GET_SECRET)
//有效時間 2小時
.accessTokenValiditySeconds(ConsParams.Auth.GET_TOKEN_VALIDITY_SECONDS)
//密碼授權模式和刷新令牌
.authorizedGrantTypes(ConsParams.Auth.GE_TAUTHORIZED_GRANT_TYPES)
.scopes(ConsParams.Auth.GE_TSCOPES);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.tokenStore(tokenStore)
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService)
.accessTokenConverter(jwtAccessTokenConverter);
}
}
}
和第三節(jié)不同的是 endpoints添加了accessTokenConverter屬性濒析,它規(guī)定了token生成器是jwtAccessTokenConverter班缰,并按照我們設置的簽名來生成。
啟動項目
給/oauth/token 發(fā)送post請求獲取token
請求頭:Authorization:Basic +clientid:secret 的base64加密字符串 (認證服務器中設置的client信息)
請求參數(shù):username password (用戶登陸賬號密碼)
2. JWT擴展
在前文我們介紹過jwt的可擴展性悼枢,一般情況下實現(xiàn)jwt就可以了,但是有一些特殊需求脾拆,比如你想在返回的token中附帶一些別的信息馒索,這就需要我們對jwt進行擴展。
2.1 修改JwtTokenConfig
新增jwtTokenEnhancer方法
@Configuration
public class JwtTokenConfig{
@Bean
public TokenStore jwtTokenStore(){
return new JwtTokenStore(jwtAccessTokenConverter());
}
/**
* token生成處理:指定簽名
*/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(){
JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
accessTokenConverter.setSigningKey("internet_plus");
return accessTokenConverter;
}
@Bean
public TokenEnhancer jwtTokenEnhancer(){
return new JwtTokenEnhancer();
}
}
jwtTokenEnhancer方法 返回一個JwtTokenEnhancer并交給bean工廠名船。
2.2 增加JwtTokenEnhancer類
/**
* Jwt token 擴展
* Created by Fant.J.
*/
public class JwtTokenEnhancer implements TokenEnhancer {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
Map<String,Object> info = new HashMap<>();
info.put("provider","Fant.J");
//設置附加信息
((DefaultOAuth2AccessToken)oAuth2AccessToken).setAdditionalInformation(info);
return oAuth2AccessToken;
}
}
重寫TokenEnhancer的enhance方法绰上,根據(jù)需求擴展jwt。
2.3 修改MyAuthorizationServerConfig類
/**
* 認證服務器
* Created by Fant.J.
*/
@Configuration
@EnableAuthorizationServer
public class MyAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private TokenStore tokenStore;
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Autowired
private TokenEnhancer jwtTokenEnhancer;
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
super.configure(security);
}
/**
* 客戶端配置(給誰發(fā)令牌)
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient(ConsParams.Auth.GET_CLIENT_ID)
.secret(ConsParams.Auth.GET_SECRET)
//有效時間 2小時
.accessTokenValiditySeconds(ConsParams.Auth.GET_TOKEN_VALIDITY_SECONDS)
//密碼授權模式和刷新令牌
.authorizedGrantTypes(ConsParams.Auth.GE_TAUTHORIZED_GRANT_TYPES)
.scopes(ConsParams.Auth.GE_TSCOPES);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.tokenStore(tokenStore)
.authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
List<TokenEnhancer> enhancerList = new ArrayList<>();
enhancerList.add(jwtTokenEnhancer);
enhancerList.add(jwtAccessTokenConverter);
enhancerChain.setTokenEnhancers(enhancerList);
endpoints
.tokenEnhancer(enhancerChain)
.accessTokenConverter(jwtAccessTokenConverter);
}
}
endpoints的tokenEnhancer方法需要我們提供一個token增強器鏈對象TokenEnhancerChain渠驼,所以我們需要在鏈中加入我們重寫的TokenEnhancer和jwtAccessTokenConverter蜈块,然后放入endpoints。
啟動項目
和上文獲取token方式相同迷扇。
下圖紅框便是我添加的擴展百揭。
解析jwt,獲取擴展信息
如果我們需要獲取jwt本來就有的信息蜓席,我們直接請求方法中吧Authentication當做參數(shù)器一,就可以獲取到jwt原始信息。
如果我們需要獲取jwt擴展的信息厨内,即我們自定義添加的信息祈秕,我們需要做這幾個操作:
- pom導入依賴
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency>
- UserController.java
@GetMapping("/me")
public ServerResponse getCurrentUser(Authentication user, HttpServletRequest request) throws UnsupportedEncodingException {
String s = user.getPrincipal().toString();
String name = user.getName();
String header = request.getHeader("Authorization");
String token = StringUtils.substringAfter(header,"bearer ");
Claims body = Jwts.parser().setSigningKey(ConsParams.Auth.GET_SIGNING_KEY.getBytes("UTF-8"))
.parseClaimsJws(token).getBody();
String username = (String) body.get("username");
log.info("解析token獲取到的username為{}",username);
log.info("從Authentication里獲取到的username為{}",s);
log.info("從Authentication里獲取到的username為{}",name);
return ServerResponse.createBySuccess(user);
}
介紹下我的所有文集:
流行框架
SpringCloud
springboot
nginx
redis
底層實現(xiàn)原理:
Java NIO教程
Java reflection 反射詳解
Java并發(fā)學習筆錄
Java Servlet教程
jdbc組件詳解
Java NIO教程
Java語言/版本 研究