美文网首页Java
spring security oauth2如何进行接口单元测试

spring security oauth2如何进行接口单元测试

作者: 蔺荆门 | 来源:发表于2019-12-05 15:45 被阅读0次

    前言

    之前在项目中一直都是手动测试接口, 流程一般是手动获取token之后添加到header里(或者是使用工具的环境变量等等), 然后测试; 使用junit直接测试方法好用, 但也有它的局限性, 像一些参数的校验(如果你的参数校验是使用javax的validation)和spring security用户的获取就无法测试; 因为我在v2ex上的一篇帖子, 我开始琢磨一些单测的东西, 一开始就发现有一个阻碍, 每次测试接口都需要自己手动获取token的话就没办法将测试自动化; 在网上找了一下相关的资料发现对于spring security oauth2的测试资料不太多, 但是还是找到了Stack Overflow上的一个答案, 基于这位前辈在15年的分享, 有了这篇文章;

    其实这里大家也可能会说, 可以使用脚本或者工具自动的使用账号获取token, 然后自动化测试啊; 但是这个方式在我的项目中行不通, 因为目前我的这个系统中有一种用户是微信小程序用户, 登录方式为小程序用户使用code到后端换取token, 换取token的过程中我会使用用户的code从微信那里获取openid; 所以这部分用户我没办法使用脚本来获取token;

    思路

    首先我最先想到的是有没有一个测试框架对spring security oauth2做了适配, 然后我就可以直接使用, 那就美滋滋了; 但是我找了一天, 未果, 如果大家知道的话请不吝赐教;

    那既然没有现成的测试框架可以使用的话我们就得自己搞点事情了; 首先我们上面说到问题的根源其实就是在获取token的过程需要我们手动来获取, 那我们就这个问题往下探讨; 如果我们可以自动获取token就问题就解决了, 可我上面说过, 这个token我没办法通过脚本来获取, 怎么办呢? 那就直接从spring security入手, 我们在调用接口之前先往spring security的context里面直接放入用户(没有经过系统里面的用户校验逻辑哦); 然后用该用户的token调用接口就可以了嘛; 话不多说, 动手;

    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);
    
                // 关键是这里, 将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是需要我们自己实现的, 其他的流程是spring security oauth2获取token的方式; 接着来贴一下AuthenticationFactory的实现方式;

    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("用户不存在");
        }
    }
    

    可以看到, 我在构造方法里面实例化了我系统里面的两种用户, getAuthentication这个方法根据传入的用户名返回相应的Authentication; component单例注入;

    写到这里其实我们的token就准备好了, 来尝试一下咯

    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) // 设置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测接口了;

    总结

    这篇文章主要是讲了一种思路, 具体代码里面的实现可以有其他不同的方式, 这段时间在学习使用mockmvc我就用了这种方式; 一起折腾吧;

    相关文章

      网友评论

        本文标题:spring security oauth2如何进行接口单元测试

        本文链接:https://www.haomeiwen.com/subject/dvidgctx.html