※超省錢租車方案
商務出差、學生出遊、旅遊渡假、臨時用車!GO 神州租賃有限公司!合法經營、合法連鎖、合法租賃小客車!
PS:此文章為系列文章,建議從第一篇開始閱讀。
為什麼使用JWT令牌
在上面的資源服務器中,通過配置,我們了解到,當我們程序是前後端分離時,在拿着token去獲取資源時,程序會先去調用遠程認證服務器的端點去驗證解析token,這樣毫無疑問,當訪問量過大的時候,對認證服務器的壓力可想而知,所以為了解決上面的問題,我們採用JWT令牌格式,可以優化上面的問題。
令牌採用JWT格式即可解決上邊的問題,用戶認證通過會得到一個JWT令牌,JWT令牌中已經包括了用戶相關的信息,客戶端只需要攜帶JWT訪問資源服務,資源服務根據事先約定的算法自行完成令牌校驗,無需每次都請求認證服務完成授權。
改造認證服務器
- 修改TokenConfig類,如下:
@Configuration
public class TokenConfigure {
private static final String SIGNING_KEY = "dimples";
@Bean
public TokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
//對稱秘鑰,資源服務器使用該秘鑰來驗證
converter.setSigningKey(SIGNING_KEY);
return converter;
}
}
- 修改認證服務器的配置
private TokenStore tokenStore;
private ClientDetailsService clientDetailsService;
private JwtAccessTokenConverter jwtAccessTokenConverter;
//通過構造方法注入
...
/**
* 令牌管理服務
*
* @return TokenServices
*/
@Bean
public AuthorizationServerTokenServices tokenServices() {
DefaultTokenServices services = new DefaultTokenServices();
// 客戶端詳情服務
services.setClientDetailsService(clientDetailsService);
// 支持令牌刷新
services.setSupportRefreshToken(true);
// 令牌存儲策略
services.setTokenStore(tokenStore);
// 配置令牌增強 JWT
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
tokenEnhancerChain.setTokenEnhancers(Collections.singletonList(jwtAccessTokenConverter));
services.setTokenEnhancer(tokenEnhancerChain);
// 令牌默認有效期2小時(如果客戶端設置了會覆蓋該值)
services.setAccessTokenValiditySeconds(7200);
// 刷新令牌默認有效期2天
services.setRefreshTokenValiditySeconds(259200);
return services;
}
- 最後別忘了在pom中添加JWT的依賴,否則項目將會報錯
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.1.0.RELEASE</version>
</dependency>
測試結果如下:
可以使用OAuth的/oauth/check_token端點來解析驗證一下該token
但是我們需要明白一點的是,這種令牌還是存儲在內存中的,後期我們如何將其存儲到redis中是我們研究的方向。
改造資源服務器
當我們使用了JWT令牌以後,由於在JWT令牌中我們存儲了相應的用戶信息和權限,這時我們可以直接在資源服務器中直接去解析對應令牌,就不用每次都去請求認證服務器端點,加大認證服務器的壓力,下面我們開始改造資源服務器:
- 將上面認證服務器中寫的TokenConfigure類拷貝一份到資源服務器
- 在資源服務器中屏蔽調之前的資源服務器令牌解析服務( tokenService() )
- 注入TokenConfigure類,然後配置到ResourceServerSecurityConfigurer里
完整的配置如下:
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class DimplesResourceServerConfigurerAdapter extends ResourceServerConfigurerAdapter {
public static final String RESOURCE_ID = "dimples";
private TokenStore tokenStore;
@Autowired
public DimplesResourceServerConfigurerAdapter(TokenStore tokenStore) {
this.tokenStore = tokenStore;
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
resources.resourceId(RESOURCE_ID)
.tokenServices(tokenService())
.stateless(true);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
// 配置客戶端權限scope
.antMatchers("/**").access("#oauth2.hasScope('all')")
.and().csrf().disable()
// 關閉session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
然後重啟服務,重新獲取令牌,然後訪問之前的測試接口:
待解決的問題
在此處的JWT的配置中,我們獲取的令牌信息還是存在內存中的,這樣不利於我們程序的擴展。那麼我們如何將生產的令牌去存儲到數據庫中或者存儲到redis中呢?請關注後續的文章。
※Google地圖已可更新顯示潭子電動車充電站設置地點!!
日本、大陸,發現這些先進的國家已經早就讓電動車優先上路,而且先進國家空氣品質相當好,電動車節能減碳可以減少空污
使用非對稱加密
在上面的jwt加密中,我們的JWT簽名是寫死的字符串,可能我們的項目為了安全考慮,需要使用非對稱的加密,我們該怎麼配置呢?
首先獲取加密文件的私鑰和公鑰,需要先安裝安裝OpenSSL工具,參考鏈接【https://blog.csdn.net/qq_39081974/article/details/81059022】
- 生成JKS Java KeyStore文件
命令行執行:keytool -genkeypair -alias dimples -keyalg RSA -keypass dimples -keystore dimples.jks -storepass dimples
將生成一個名為medical.jks的文件,其中包含我們的密鑰 – 公鑰和私鑰。 還要確保keypass和storepass是一樣的
- 導出公鑰
keytool -list -rfc –keystore dimples.jks | openssl x509 -inform pem -pubkey
或 keytool -importkeystore -srckeystore dimples.jks -destkeystore dimples.jks -deststoretype pkcs12
將其複製到我們的資源服務器 src/main/resources/public.txt 中
- 配置認證服務器(TokenConfigure)
/**
* 配置jwt生成token的轉換
* 使用RSA Sign Key 進行加密
*
* @return JwtAccessTokenConverter
*/
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("medical.jks"), "medical".toCharArray());
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setKeyPair(keyStoreKeyFactory.getKeyPair("medical"));
return converter;
}
- 配置資源服務器(TokenConfigure)
/**
* 配置jwt生成token的轉換
*
* @return JwtAccessTokenConverter
*/
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
Resource resource = new ClassPathResource("public.txt");
String publicKey;
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(resource.getInputStream()))) {
publicKey = bufferedReader.lines().collect(Collectors.joining("\n"));
} catch (final IOException e) {
throw new RuntimeException(e);
}
converter.setSigningKey(publicKey);
return converter;
}
擴展JWT的存儲信息
當我們使用如上的配置獲取Token后,將access_token中的內容複製到https://jwt.io/網站解析下:
可以看到在jwt中只是保存了我們的user_name,那麼我們怎麼去擴展呢?讓其可以保存我們需要的用戶詳細信息,這裡有兩種方案:
- 實現TokenEnhancer(Token增強器)
public class JWTokenEnhancer implements TokenEnhancer {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
Map<String, Object> info = new HashMap<>();
info.put("other", "hello world");
((DefaultOAuth2AccessToken) oAuth2AccessToken).setAdditionalInformation(info);
return oAuth2AccessToken;
}
}
然後在TokenConfigure 中配置該Bean:
@Configuration
public class TokenConfigure {
......
@Bean
public TokenEnhancer tokenEnhancer() {
return new JWTokenEnhancer();
}
}
最後在認證服務器里配置該增強器:
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private TokenEnhancer tokenEnhancer;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
.....
TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
List<TokenEnhancer> enhancers = new ArrayList<>();
enhancers.add(tokenEnhancer);
enhancers.add(jwtAccessTokenConverter);
enhancerChain.setTokenEnhancers(enhancers);
endpoints.tokenEnhancer(enhancerChain);
}
......
}
比如存入json數據內容作為username的內容。相比較而言,方案二比較簡單還不用破壞UserDetails的結構
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//登錄賬號
System.out.println("username="+username);
//根據賬號去數據庫查詢...
UserDto user = userDao.getUserByUsername(username);
if(user == null){
return null;
}
//查詢用戶權限
List<String> permissions = userDao.findPermissionsByUserId(user.getId());
String[] perarray = new String[permissions.size()];
permissions.toArray(perarray);
//創建userDetails
//這裏將user轉為json,將整體user存入userDetails
String principal = JSON.toJSONString(user);
UserDetails userDetails = User.withUsername(principal).password(user.getPassword()).authorities(perarray).build();
return userDetails;
}
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
有別於一般網頁架設公司,除了模組化的架站軟體,我們的營業主軸還包含:資料庫程式開發、網站建置、網頁設計、電子商務專案開發、系統整合、APP設計建置、專業網路行銷。