暢購商城(九):Spring Security Oauth2


好好學習,天天向上

本文已收錄至我的Github倉庫DayDayUP:github.com/RobodLee/DayDayUP,歡迎Star,更多文章請前往:目錄導航

前言

之前因為沒學過Spring Security和OAuth2.0,所以看這一章的視頻的時候看的一頭霧水。所以花了幾天時間惡補了一下這方面的知識,並且寫了兩篇文章,把這兩部分內容詳細說明了一下。

下面的內容只是針對於這個項目的,前兩篇文章中說過的內容就不再說了。

認證服務介紹

怎么搭建OAuth2.0我之前的文章已經詳細說過了,這里直接將資料里提供的代碼導入即可。

image

這里簡單總結一下每個文件的作用:

  • AuthorizationServerConfig

    這個是OAuth2.0的認證服務配置。主要有三點,第一是客戶端信息配置,也就是客戶端需要有哪些條件才可以訪問服務器,比如客戶端id和客戶端密鑰等,可以直接配置到內存中,也可以配置從數據庫中讀取;第二是授權服務器端點配置,就是配置認證管理器,令牌存儲方式等;第三個是授權服務器的安全配置,就是配置訪問的限制,比如限制校驗令牌的配置等。

  • CustomUserAuthenticationConverter

    自定義的UserAuthenticationConverter,繼承自DefaultUserAuthenticationConverter,重寫了convertUserAuthentication方法。默認該方法是獲取authentication中的username權限信息。而我們重寫的方法里面還獲取了authentication中的principal,判斷是不是我們自定義的UserJwt,不是的話就調用userDetailsService.loadUserByUsername去獲取,然后將userJwt中的nameid獲取出來,添加到返回的map中。

  • UserDetailsServiceImpl

    這個是自定義的認證授權類,實現了UserDetailsService接口,並實現了里面的loadUserByUsername()方法。這個方法是根據前端傳進來的用戶名去查出對應的用戶信息。然后交給后續的過濾器去進行用戶身份的驗證。一般這個方法是從數據庫中查找用戶,但是這里為了測試就直接new了一個臨時用戶,密碼是 "robod666" ,所以只要前端傳過來的密碼是 “robod666” 就可以正常登錄。

  • WebSecurityConfig

    這個是Spring Security的安全配置類。主要配置了某些對於某些請求的限制。在這個類中,還往Spring容器中注入了passwordEncoderauthenticationManagerBean供其他類使用。

  • UserLoginController

    這個是自定義的一個只使用用戶名和密碼進行登錄的簡化的登錄方式。

  • LoginServiceLoginServiceImpl

    UserLoginController的Service層。負責添加一些必要的信息后然后通過RestTemplate模擬瀏覽器向服務器發送請求獲取令牌信息。

  • AuthToken

    封裝了Token的相關信息。令牌信息,刷新token,jwt短令牌。

  • CookieUtil

    Cookie的工具類。設置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
        …………
    

這幾個文件的作用到這里就介紹完了。

非對稱加密認證

認證流程分析

image

這個是傳統的認證流程,當我們攜帶令牌去訪問資源服務器的時候,資源服務會將令牌發送到授權服務中驗證令牌時候合法。這樣做的話無形之中增加的服務器的壓力,因為多了一次服務器之間交互的行為,效率低下。

為了提高效率,采用了公鑰私鑰驗證的方式。

image

授權服務采用私鑰去生成令牌,然后客戶端攜帶令牌向資源服務器發送請求。資源服務器采用公鑰對令牌進行校驗,校驗通過后再進行下一步操作。減少了和授權服務的交互。

公鑰可以保存在任意的服務器中,但是私鑰只能保存在授權服務中。因為有了私鑰后就可以去偽造令牌,降低了安全性。所以采用非對稱加密也是為了提高安全性。

生成密鑰證書

我們可以使用keytool工具來生成密鑰對。在准備好的文件夾下,打開命令行窗口,執行以下內容:

keytool -genkeypair -alias robod666 -keyalg RSA -keypass robod666 -keystore robod666.jks -storepass robod666
# -alias:密鑰的別名 
# -keyalg:使用的hash算法 
# -keypass:密鑰的訪問密碼
# -keystore:密鑰庫文件名,xc.keystore保存了生成的證書 
# -storepass:密鑰庫的訪問密碼 

然后界面上會出現幾個問題,答案隨便輸,最后輸入 “y” 即可生成密鑰。

image

把生成的密鑰證書放在認證服務的resources目錄下即可。

提取公鑰

在安裝好openssl后,在密鑰證書所在的目錄下打開命令行窗口,執行

keytool -list -rfc --keystore robod666.jks | openssl x509 -inform pem -pubkey

image

這樣就可以將公鑰提取出來。在需要使用到公鑰的微服務的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 錯誤,

image

把idea重啟一下即可。

運行createJWT()成功的話,就會出現以下內容。

image

這就是我們創建的令牌了。

運行parseJWT(),運行成功的話,就可以使用公鑰去解析出令牌中的內容。

image

賬號密碼登錄

現在我們登錄的時候,除了需要用戶名密碼之外,還需要指定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);
}

image

好了,現在我們只用賬號密碼就可以申請到令牌了。

總結

這篇文章主要是說了如何導入OAuth2.0認證服務,如果不了解的話,理解起來還是很困難的,所以可以先去看看我之前的文章,導入之后對每個文件的作用進行了一個簡單的介紹。然后又講了一下采用非對稱加密的時候如何生成公鑰私鑰。最后說了如何實現只用賬號密碼進行登錄。

如果我的文章對你有些幫助,不要忘了點贊收藏轉發關注。要是有什么好的意見歡迎在下方留言。讓我們下期再見!

微信公眾號


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM