好好學習,天天向上
本文已收錄至我的Github倉庫DayDayUP:github.com/RobodLee/DayDayUP,歡迎Star,更多文章請前往:目錄導航
- 暢購商城(一):環境搭建
- 暢購商城(二):分布式檔案系統FastDFS
- 暢購商城(三):商品管理
- 暢購商城(四):Lua、OpenResty、Canal實作廣告快取與同步
- 暢購商城(五):Elasticsearch實作商品搜索
- 暢購商城(六):商品搜索
- 暢購商城(七):Thymeleaf實作靜態頁
- 暢購商城(八):微服務網關和JWT令牌
- 暢購商城(九):Spring Security Oauth2
- 暢購商城(十):購物車
前言
之前因為沒學過Spring Security和OAuth2.0,所以看這一章的視頻的時候看的一頭霧水,所以花了幾天時間惡補了一下這方面的知識,并且寫了兩篇文章,把這兩部分內容詳細說明了一下,
-
SpringBoot整合Spring Security
-
OAuth2.0分布式系統環境搭建
下面的內容只是針對于這個專案的,前兩篇文章中說過的內容就不再說了,
認證服務介紹
怎么搭建OAuth2.0我之前的文章已經詳細說過了,這里直接將資料里提供的代碼匯入即可,
這里簡單總結一下每個檔案的作用:
-
AuthorizationServerConfig這個是OAuth2.0的認證服務配置,主要有三點,第一是客戶端資訊配置,也就是客戶端需要有哪些條件才可以訪問服務器,比如客戶端id和客戶端密鑰等,可以直接配置到記憶體中,也可以配置從資料庫中讀取;第二是授權服務器端點配置,就是配置認證管理器,令牌存盤方式等;第三個是授權服務器的安全配置,就是配置訪問的限制,比如限制校驗令牌的配置等,
-
CustomUserAuthenticationConverter自定義的UserAuthenticationConverter,繼承自DefaultUserAuthenticationConverter,重寫了convertUserAuthentication方法,默認該方法是獲取authentication中的username和權限資訊,而我們重寫的方法里面還獲取了authentication中的principal,判斷是不是我們自定義的UserJwt,不是的話就呼叫userDetailsService.loadUserByUsername去獲取,然后將userJwt中的name和id獲取出來,添加到回傳的map中,
-
UserDetailsServiceImpl這個是自定義的認證授權類,實作了UserDetailsService介面,并實作了里面的loadUserByUsername()方法,這個方法是根據前端傳進來的用戶名去查出對應的用戶資訊,然后交給后續的過濾器去進行用戶身份的驗證,一般這個方法是從資料庫中查找用戶,但是這里為了測驗就直接new了一個臨時用戶,密碼是 "robod666" ,所以只要前端傳過來的密碼是 “robod666” 就可以正常登錄,
-
WebSecurityConfig這個是Spring Security的安全配置類,主要配置了某些對于某些請求的限制,在這個類中,還往Spring容器中注入了passwordEncoder和authenticationManagerBean供其他類使用,
-
UserLoginController這個是自定義的一個只使用用戶名和密碼進行登錄的簡化的登錄方式,
-
LoginService和LoginServiceImplUserLoginController的Service層,負責添加一些必要的資訊后然后通過RestTemplate模擬瀏覽器向服務器發送請求獲取令牌資訊,
-
AuthToken封裝了Token的相關資訊,令牌資訊,重繪token,jwt短令牌,
-
CookieUtilCookie的工具類,設定Cookie以及根據名稱獲取Cookie資訊,
-
UserJwt用戶資訊,實作了UserDetails介面,驗證用戶時用的就是這個類的物件,
-
changgou.jks密鑰證書,可以使用keytool工具生成,
-
application.yml認證服務的組態檔
………… # 配置資訊,給UserLoginController用的 auth: ttl: 3600 #token存盤到redis的過期時間 clientId: changgou clientSecret: changgou cookieDomain: localhost cookieMaxAge: -1 # 因為采用了非對稱加密,所以這里配置了密鑰的相關資訊 encrypt: key-store: location: classpath:/robod666.jks secret: robod666 alias: robod666 password: robod666 …………
這幾個檔案的作用到這里就介紹完了,
非對稱加密認證
認證流程分析
這個是傳統的認證流程,當我們攜帶令牌去訪問資源服務器的時候,資源服務會將令牌發送到授權服務中驗證令牌時候合法,這樣做的話無形之中增加的服務器的壓力,因為多了一次服務器之間互動的行為,效率低下,
為了提高效率,采用了公鑰私鑰驗證的方式,
授權服務采用私鑰去生成令牌,然后客戶端攜帶令牌向資源服務器發送請求,資源服務器采用公鑰對令牌進行校驗,校驗通過后再進行下一步操作,減少了和授權服務的互動,
公鑰可以保存在任意的服務器中,但是私鑰只能保存在授權服務中,因為有了私鑰后就可以去偽造令牌,降低了安全性,所以采用非對稱加密也是為了提高安全性,
生成密鑰證書
我們可以使用keytool工具來生成密鑰對,在準備好的檔案夾下,打開命令列視窗,執行以下內容:
keytool -genkeypair -alias robod666 -keyalg RSA -keypass robod666 -keystore robod666.jks -storepass robod666
# -alias:密鑰的別名
# -keyalg:使用的hash演算法
# -keypass:密鑰的訪問密碼
# -keystore:密鑰庫檔案名,xc.keystore保存了生成的證書
# -storepass:密鑰庫的訪問密碼
然后界面上會出現幾個問題,答案隨便輸,最后輸入 “y” 即可生成密鑰,
把生成的密鑰證書放在認證服務的resources目錄下即可,
提取公鑰
在安裝好openssl后,在密鑰證書所在的目錄下打開命令列視窗,執行
keytool -list -rfc --keystore robod666.jks | openssl x509 -inform pem -pubkey
這樣就可以將公鑰提取出來,在需要使用到公鑰的微服務的resources目錄下創建一個public.key的檔案,把這段內容合并為一行粘貼進去,
創建及決議令牌
public class JWTTest {
//創建令牌
@Test
public void createJWT() {
ClassPathResource classPathResource = new ClassPathResource("robod666.jks");
KeyStoreKeyFactory keyStoreKeyFactory =
new KeyStoreKeyFactory(classPathResource, "robod666".toCharArray());
KeyPair keyPair = keyStoreKeyFactory.getKeyPair("robod666");
PrivateKey privateKey = keyPair.getPrivate();
Map<String, Object> tokenMap = new HashMap<>();
tokenMap.put("id", "1");
tokenMap.put("name", "robod");
tokenMap.put("roles", "ROLE_VIP,ROLE_USER");
Jwt jwt = JwtHelper.encode(JSON.toJSONString(tokenMap), new RsaSigner((RSAPrivateKey) privateKey));
System.out.println(jwt.getEncoded());
}
//決議令牌
@Test
public void parseJWT() {
String token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlcyI6IlJPTEVfVklQLFJPTEVfVVNFUiIsIm5hbWUiOiJyb2JvZCIsImlkIjoiMSJ9.KkPXXlYTkDCWq2VN5qy6w6FI5TgCbIy-GkQShaYGbfvcZsj0165XsgXSx7Sf5aUpGB-494ds-ZnLxs3oVMZ7_tbu-is1-gZOeQ0G1GLla0ytNkImXabnujgWH2B4bmX4lBLK7d8xTEQ4WoAnydWUusCmPjQDgdFZGHmccJLuYKqQPzru-4go1mFgjEeB7gNu6cLYyQc79bZdF2Mk2OX1Nxpb88sux0QkNAlb1-JuUhmjbUYwMK5l5W5zeNckRJtGy_Zy7OTwXviuRp6uISmWD7p1HYbkKH-ROKCgSu1cqnok0645Uou7Y54Nd8NosIqShuNYbBBo_BHWuyx_lKdk1g";
String publicKey = "-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAiq6KfbXc/viuB6oQ/80cfLSFIr7pX3PmteAQ2/dA+ReMLgULJb+U8Dax3xNpBgLAp+Ei2IMkBFJlJRn/iaYi5eMnCY2vyfHkC69x6OhhCtzWBRxGJkPRjLDU+Obhak2MrDI4zIpzQs2/phjqWXuEPMz7KMd5UhoAFZWLTW1Ih3CP962fuJdV83hj/2uWN/yaAgaLRxRlTw7HHoIEy1dX9prAnqQ/rOl2Igvwi23GNnzMrqlvR9qt1gBI+noHtMv07hkavUT1nmoYnt/pw2+FLMLFEun2gR3DUmqu79QC6trDf3cVfKyRP9A7TBjUEv+Ecrh8JQosQa8GongTzHhmOwIDAQAB-----END PUBLIC KEY-----";
Jwt jwt = JwtHelper.decodeAndVerify(token, new RsaVerifier(publicKey));
String claims = jwt.getClaims();
System.out.println(claims);
}
}
創建令牌的時候,可能會出現以下 java.lang.IllegalStateException 錯誤,
把idea重啟一下即可,
運行createJWT()成功的話,就會出現以下內容,
這就是我們創建的令牌了,
運行parseJWT(),運行成功的話,就可以使用公鑰去決議出令牌中的內容,
賬號密碼登錄
現在我們登錄的時候,除了需要用戶名密碼之外,還需要指定clientId等其它資訊,那我們可不可以只使用賬號密碼就能登錄呢?只需要在服務器中將其它的資訊事先指定好即可,思路就是,前端將用戶名密碼傳入進來,然后服務器端添加好其它的資訊,發送給認證服務去登錄,然后拿到token再回傳給前端,
@Service
public class LoginServiceImpl implements LoginService {
@Autowired
private RestTemplate restTemplate;
@Autowired
private LoadBalancerClient loadBalancerClient;
@Override
public Map login(String username, String password, String clientId, String clientSecret, String grantType) {
String url = loadBalancerClient.choose("user-auth").getUri() +
"/oauth/token";
//請求體
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
body.add("username", username);
body.add("password", password);
body.add("grant_type", grantType);
//請求頭
MultiValueMap<String, String> header = new LinkedMultiValueMap<>();
String authorization = "Basic " +
new String(Base64.getEncoder().encode((clientId + ":" + clientSecret).getBytes()));
header.add("Authorization", authorization);
HttpEntity httpEntity = new HttpEntity(body, header);
ResponseEntity<Map> response = restTemplate.exchange(url, HttpMethod.POST, httpEntity, Map.class);
return response.getBody();
}
}
@Value("${auth.clientId}")
private String clientId;
@Value("${auth.clientSecret}")
private String clientSecret;
//密碼模式 認證.
@RequestMapping("/login")
public Result<Map> login(String username, String password) throws Exception {
String grantType = "password";
Map token = loginService.login(username, password, clientId, clientSecret, grantType);
return new Result<>(true, StatusCode.OK,"令牌生成成功",token);
}
好了,現在我們只用賬號密碼就可以申請到令牌了,
總結
這篇文章主要是說了如何匯入OAuth2.0認證服務,如果不了解的話,理解起來還是很困難的,所以可以先去看看我之前的文章,匯入之后對每個檔案的作用進行了一個簡單的介紹,然后又講了一下采用非對稱加密的時候如何生成公鑰私鑰,最后說了如何實作只用賬號密碼進行登錄,
如果我的文章對你有些幫助,不要忘了點贊,收藏,轉發,關注,要是有什么好的意見歡迎在下方留言,讓我們下期再見!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/61833.html
標籤:Java
