spring security oauth2如何進(jìn)行接口單元測試

前言

之前在項目中一直都是手動測試接口, 流程一般是手動獲取token之后添加到header里(或者是使用工具的環(huán)境變量等等), 然后測試; 使用junit直接測試方法好用, 但也有它的局限性, 像一些參數(shù)的校驗(如果你的參數(shù)校驗是使用javax的validation)和spring security用戶的獲取就無法測試; 因為我在v2ex上的一篇帖子, 我開始琢磨一些單測的東西, 一開始就發(fā)現(xiàn)有一個阻礙, 每次測試接口都需要自己手動獲取token的話就沒辦法將測試自動化; 在網(wǎng)上找了一下相關(guān)的資料發(fā)現(xiàn)對于spring security oauth2的測試資料不太多, 但是還是找到了Stack Overflow上的一個答案, 基于這位前輩在15年的分享, 有了這篇文章;

其實這里大家也可能會說, 可以使用腳本或者工具自動的使用賬號獲取token, 然后自動化測試啊; 但是這個方式在我的項目中行不通, 因為目前我的這個系統(tǒng)中有一種用戶是微信小程序用戶, 登錄方式為小程序用戶使用code到后端換取token, 換取token的過程中我會使用用戶的code從微信那里獲取openid; 所以這部分用戶我沒辦法使用腳本來獲取token;

思路

首先我最先想到的是有沒有一個測試框架對spring security oauth2做了適配, 然后我就可以直接使用, 那就美滋滋了; 但是我找了一天, 未果, 如果大家知道的話請不吝賜教;

那既然沒有現(xiàn)成的測試框架可以使用的話我們就得自己搞點事情了; 首先我們上面說到問題的根源其實就是在獲取token的過程需要我們手動來獲取, 那我們就這個問題往下探討; 如果我們可以自動獲取token就問題就解決了, 可我上面說過, 這個token我沒辦法通過腳本來獲取, 怎么辦呢? 那就直接從spring security入手, 我們在調(diào)用接口之前先往spring security的context里面直接放入用戶(沒有經(jīng)過系統(tǒng)里面的用戶校驗邏輯哦); 然后用該用戶的token調(diào)用接口就可以了嘛; 話不多說, 動手;

show me some code

我們使用mockmvc測試接口(但其實思路有了之后用什么client都可以), 先寫一下如何往spring security的context里面放入用戶的代碼;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.stereotype.Component;
import org.springframework.test.web.servlet.request.RequestPostProcessor;

import java.util.Collections;

/**
 * @author sunhao
 * @date create in 2019-12-05 14:08:31
 */
@Component
public class TokenFactory {

    @Autowired
    private ClientDetailsService clientDetailsService;

    @Autowired
    private AuthenticationFactory authenticationFactory;

    @Qualifier("defaultAuthorizationServerTokenServices")
    @Autowired
    private AuthorizationServerTokenServices authorizationServerTokenServices;

    public RequestPostProcessor token(String username) {

        return request -> {

            ClientDetails clientDetails = getClientDetails();

            OAuth2AccessToken oAuth2AccessToken = getAccessToken(authenticationFactory.getAuthentication(username), clientDetails);

            // 關(guān)鍵是這里, 將token放入header
            request.addHeader("Authorization", String.format("bearer %s", oAuth2AccessToken.getValue()));
            return request;
        };
    }

    private ClientDetails getClientDetails() {

        return clientDetailsService.loadClientByClientId("atom");
    }

    private OAuth2AccessToken getAccessToken(Authentication authentication, ClientDetails clientDetails) {

        TokenRequest tokenRequest = new TokenRequest(Collections.emptyMap(), clientDetails.getClientId(), clientDetails.getScope(), "diy");
        OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
        OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);
        return authorizationServerTokenServices.createAccessToken(oAuth2Authentication);
    }

}

上面的代碼中, AuthenticationFactory是需要我們自己實現(xiàn)的, 其他的流程是spring security oauth2獲取token的方式; 接著來貼一下AuthenticationFactory的實現(xiàn)方式;

import com.atom.projects.mall.entity.Admin;
import com.atom.projects.mall.entity.Employee;
import com.atom.projects.mall.entity.MiniProCustomer;
import com.atom.projects.mall.enums.CustomerGender;
import com.atom.projects.mall.security.AtomAuthenticationToken;
import org.springframework.stereotype.Component;

import static org.springframework.security.core.authority.AuthorityUtils.commaSeparatedStringToAuthorityList;

/**
 * @author sunhao
 * @date create in 2019-12-05 14:15:08
 */
@Component
public class AuthenticationFactory {

    private final AtomAuthenticationToken admin;
    private final AtomAuthenticationToken employeeAdmin;
    private final AtomAuthenticationToken employeeCommon;
    private final AtomAuthenticationToken customer;

    public AuthenticationFactory() {

        Admin admin = new Admin();
        admin.setId(1L);
        admin.setUsername("admin");
        admin.setPassword("password");
        this.admin = new AtomAuthenticationToken(admin, null, commaSeparatedStringToAuthorityList("admin"));

        MiniProCustomer customer = new MiniProCustomer();
        customer.setId(2L);
        customer.setMerchantId(1L);
        customer.setOpenId("openId");
        customer.setAvatarUrl("merchantId");
        customer.setNickname("nickname");
        customer.setPhoneNumber("13888888888");
        customer.setCountry("china");
        customer.setProvince("yunnan");
        customer.setCity("kunming");
        customer.setGender(CustomerGender.MALE);
        this.customer = new AtomAuthenticationToken(customer, null, commaSeparatedStringToAuthorityList("customer"));
    }

    public AtomAuthenticationToken getAuthentication(String username) {

        if ("admin".equals(username)) {
            return this.admin;
        }

        if ("customer".equals(username)) {
            return this.customer;
        }

        throw new RuntimeException("用戶不存在");
    }
}

可以看到, 我在構(gòu)造方法里面實例化了我系統(tǒng)里面的兩種用戶, getAuthentication這個方法根據(jù)傳入的用戶名返回相應(yīng)的Authentication; component單例注入;

寫到這里其實我們的token就準(zhǔn)備好了, 來嘗試一下咯

public class AuthControllerTest extends MallApplicationTests {

    @Autowired
    private MockMvc mockMvc;
    
    @Autowired
    private TokenFactory tokenFactory;

    @Test
    public void testEmployeeAuth() throws Exception {

        //獲取token
        RequestPostProcessor token = tokenFactory.token("admin");
        
        MvcResult mvcResult = mockMvc.perform(
            MockMvcRequestBuilders.get("/auth/employee").with(token) // 設(shè)置token
        ).andReturn();
        
        int status = mvcResult.getResponse().getStatus();
        System.out.println("status = " + status);
        String contentAsString = mvcResult.getResponse().getContentAsString();
        System.out.println("contentAsString = " + contentAsString);
    }
    
}

有一些注解寫在了MallApplicationTests里面, 這樣所有的測試類只要繼承就可以使用了, 不用每次都寫;

@RunWith(SpringRunner.class)
@SpringBootTest(
    webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT
)
@AutoConfigureMockMvc
public class MallApplicationTests {

    @Test
    public void contextLoads() {

    }

}

OK了, 這樣就可以不用手動獲取token測接口了;

總結(jié)

這篇文章主要是講了一種思路, 具體代碼里面的實現(xiàn)可以有其他不同的方式, 這段時間在學(xué)習(xí)使用mockmvc我就用了這種方式; 一起折騰吧;

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末趣些,一起剝皮案震驚了整個濱河市酒甸,隨后出現(xiàn)的幾起案子脊凰,更是在濱河造成了極大的恐慌,老刑警劉巖彻况,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機经宏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來熄捍,“玉大人烛恤,你說我怎么就攤上這事母怜∮嗟ⅲ” “怎么了?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵苹熏,是天一觀的道長碟贾。 經(jīng)常有香客問我,道長轨域,這世上最難降的妖魔是什么袱耽? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮干发,結(jié)果婚禮上朱巨,老公的妹妹穿的比我還像新娘。我一直安慰自己枉长,他們只是感情好冀续,可當(dāng)我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著必峰,像睡著了一般洪唐。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上吼蚁,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天凭需,我揣著相機與錄音,去河邊找鬼。 笑死粒蜈,一個胖子當(dāng)著我的面吹牛顺献,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播枯怖,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼滚澜,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了嫁怀?” 一聲冷哼從身側(cè)響起设捐,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎塘淑,沒想到半個月后萝招,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡存捺,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年槐沼,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捌治。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡岗钩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出肖油,到底是詐尸還是另有隱情兼吓,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布森枪,位于F島的核電站视搏,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏县袱。R本人自食惡果不足惜浑娜,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望式散。 院中可真熱鬧筋遭,春花似錦、人聲如沸暴拄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽揍移。三九已至次和,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間那伐,已是汗流浹背踏施。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工石蔗, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人畅形。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓养距,卻偏偏與公主長得像,于是被迫代替她去往敵國和親日熬。 傳聞我的和親對象是個殘疾皇子棍厌,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容