案例簡述
簡述:
- 允許內存、數據庫、JWT等方式存儲令牌
- 允許 JWT 方式驗證令牌
- 允許從內存、數據庫中讀取客戶端詳情
- 封裝配置類,簡化配置,通過注解方式定制使用何種令牌存儲方式、客戶端詳情獲取方式,可使用 JWT 方式存儲令牌,從數據庫中獲取客戶端詳情
- 提供完整單元測試
- 較為詳細的代碼注釋
允許從授權服務器(適用於 JDBC、內存存儲令牌)驗證令牌該代碼尚未完善,僅供參考- 數據庫 Schema : https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql
- Demo Git 地址:https://gitee.com/LinYuanTongXue/OAuth2-Demo
Demo 流程:
- 使用 OAuth2 密碼授權方式提供令牌
- 資源服務器1(也為客戶端)提供登錄接口,資源所有者(用戶)通過將個人賬號密碼提供給 資源服務器1,資源服務器1 通過該信息向授權服務器獲取令牌
- 資源服務器1(也為客戶端)通過令牌(其中包含了客戶端、用戶等信息)訪問自身受保護的資源(需要權限才能查看的資源)
- 資源服務器2(也可資源服務器)不包含登錄接口,但其提供了某些受保護的資源(需要資源服務器1帶着訪問令牌才能訪問)
- 資源服務器1(也為客戶端)通過令牌向 資源服務器2(資源服務器) 請求其受保護的資源
使用
- 授權服務器通過繼承 AuthServerConfig 抽象類來配置授權服務器
- 資源服務器、客戶端通過繼承 ResServerConfig 抽象類來配置資源服務器
- Web 權限配置可繼承 AbstractSecurityConfig 抽象類來簡化配置
- 授權服務器通過在啟動類添加以下注解來設置令牌存儲、客戶端信息獲取方式
- @EnableAuthJWTTokenStore:使用 JWT 存儲令牌
- @EnableDBClientDetailsService:通過數據庫獲取客戶端詳情
- @EnableDBTokenStore:通過數據庫存儲令牌
- 資源服務器通過在啟動類添加以下注解來設置令牌解析方式
- @EnableResJWTTokenStore:使用 JWT 解析令牌
@EnableRemoteTokenService:通過授權服務器驗證令牌該代碼尚未完善,僅供參考
項目結構
- 下圖是 Demo 項目結構,使用了 Maven 之間的繼承關系,並添加了熱部署,不了解的可以查看下 Git 上的 Demo 源碼
- oauth2-config:該包中定義了一些通用的類,例如授權服務器、資源服務器配置類,服務繼承該類來簡化配置
- authentication-server:授權服務器
- resource1-server:資源服務器1(也為客戶端)
- resource2-server:資源服務器2(也為資源服務器)

bd1je.png
代碼
oauth2-config(通用配置類庫)
權限枚舉常量
/** * @author: 林塬 * @date: 2018/1/20 * @description: 權限常量 */ public enum AuthoritiesEnum { ADMIN("ROLE_ADMIN"), USER("ROLE_USER"), ANONYMOUS("ROLE_ANONYMOUS"); private String role; AuthoritiesEnum(String role) { this.role = role; } public String getRole() { return role; } }
授權服務器 JWT 方式存儲令牌
/** * @author: 林塬 * @date: 2018/1/20 * @description: 授權服務器 TokenStore 配置類,使用 JWT RSA 非對稱加密 */ public class AuthJWTTokenStore { @Bean("keyProp") public KeyProperties keyProperties(){ return new KeyProperties(); } @Resource(name = "keyProp") private KeyProperties keyProperties; @Bean public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) { return new JwtTokenStore(jwtAccessTokenConverter); } @Bean public JwtAccessTokenConverter jwtAccessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); KeyPair keyPair = new KeyStoreKeyFactory (keyProperties.getKeyStore().getLocation(), keyProperties.getKeyStore().getSecret().toCharArray()) .getKeyPair(keyProperties.getKeyStore().getAlias()); converter.setKeyPair(keyPair); return converter; } }
資源服務器 JWT 方式解析令牌
/** * @author: 林塬 * @date: 2018/1/20 * @description: 資源服務器 TokenStore 配置類,使用 JWT RSA 非對稱加密 */ public class ResJWTTokenStore { private static final String PUBLIC_KEY = "pubkey.txt"; @Autowired private ResourceServerProperties resourceServerProperties; @Bean public TokenStore tokenStore(JwtAccessTokenConverter jwtAccessTokenConverter) { return new JwtTokenStore(jwtAccessTokenConverter); } @Bean public JwtAccessTokenConverter jwtAccessTokenConverter() { JwtAccessTokenConverter converter = new JwtAccessTokenConverter(); converter.setVerifierKey(getPubKey()); return converter; } /** * 獲取非對稱加密公鑰 Key * @return 公鑰 Key */ private String getPubKey() { Resource resource = new ClassPathResource(ResJWTTokenStore.PUBLIC_KEY); try (BufferedReader br = new BufferedReader(new InputStreamReader(resource.getInputStream()))) { return br.lines().collect(Collectors.joining("\n")); } catch (IOException ioe) { return getKeyFromAuthorizationServer(); } } /** * 通過訪問授權服務器獲取非對稱加密公鑰 Key * @return 公鑰 Key */ private String getKeyFromAuthorizationServer() { ObjectMapper objectMapper = new ObjectMapper(); String pubKey = new RestTemplate().getForObject(resourceServerProperties.getJwt().getKeyUri(), String.class); try { Map map = objectMapper.readValue(pubKey, Map.class); return map.get("value").toString(); } catch (IOException e) { e.printStackTrace(); } return null; } }
數據庫方式存儲令牌
/** * @author: 林塬 * @date: 2018/1/22 * @description: 使用數據庫存取令牌 */ public class DBTokenStore { @Autowired private DataSource dataSource; @Bean public TokenStore tokenStore(){ return new JdbcTokenStore(dataSource); } }
數據庫方式加載客戶端詳情
/** * @author: 林塬 * @date: 2018/1/22 * @description: 通過數據庫加載客戶端詳情 */ public class DBClientDetailsService { @Autowired private DataSource dataSource; @Bean public ClientDetailsService clientDetailsService(){ return new JdbcClientDetailsService(dataSource); } }
授權服務器解析令牌
/** * @author: 林塬 * @date: 2018/1/22 * @description: 通過訪問遠程授權服務器 check_token 端點驗證令牌 */ public class RemoteTokenService { @Autowired private OAuth2ClientProperties oAuth2ClientProperties; @Resource(name = "authServerProp") private AuthorizationServerProperties authorizationServerProperties; @Bean(name = "authServerProp") public AuthorizationServerProperties authorizationServerProperties(){ return new AuthorizationServerProperties(); } @Bean public ResourceServerTokenServices tokenServices() { RemoteTokenServices remoteTokenServices = new RemoteTokenServices(); remoteTokenServices.setCheckTokenEndpointUrl(authorizationServerProperties.getCheckTokenAccess()); remoteTokenServices.setClientId(oAuth2ClientProperties.getClientId()); remoteTokenServices.setClientSecret(oAuth2ClientProperties.getClientSecret()); remoteTokenServices.setAccessTokenConverter(accessTokenConverter()); return remoteTokenServices; } @Bean public AccessTokenConverter accessTokenConverter() { return new DefaultAccessTokenConverter(); } }
WebSecurity 權限類
/** * @author: 林塬 * @date: 2018/1/20 * @description: Web 權限配置類 */ @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public abstract class AbstractSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private AuthenticationManagerBuilder authenticationManagerBuilder; @Autowired private UserDetailsService userDetailsService; @PostConstruct public void init() { try { authenticationManagerBuilder .userDetailsService(userDetailsService) .passwordEncoder(passwordEncoder()); } catch (Exception e) { throw new BeanInitializationException("Security configuration failed", e); } } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity http) throws Exception { http .exceptionHandling() .and() .csrf() .disable() .headers() .frameOptions() .disable() .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers("/swagger-ui/index.html").hasAuthority(AuthoritiesEnum.ADMIN.getRole()); }
授權服務器配置類
/** * @author: 林塬 * @date: 2018/1/20 * @description: OAuth2 授權服務器配置類 */ @EnableAuthorizationServer public abstract class AuthServerConfig extends AuthorizationServerConfigurerAdapter { @Autowired private ApplicationContext applicationContext; @Autowired private TokenStore tokenStore; @Autowired private AuthenticationManager authenticationManager; @Autowired private UserDetailsService userDetailsService; @Autowired(required = false) private JdbcClientDetailsService jdbcClientDetailsService; //令牌失效時間 public int accessTokenValiditySeconds; //刷新令牌失效時間 public int refreshTokenValiditySeconds; //是否可以重用刷新令牌 public boolean isReuseRefreshToken; //是否支持刷新令牌 public boolean isSupportRefreshToken; public AuthServerConfig(int accessTokenValiditySeconds, int refreshTokenValiditySeconds, boolean isReuseRefreshToken, boolean isSupportRefreshToken) { this.accessTokenValiditySeconds = accessTokenValiditySeconds; this.refreshTokenValiditySeconds = refreshTokenValiditySeconds; this.isReuseRefreshToken = isReuseRefreshToken; this.isSupportRefreshToken = isSupportRefreshToken; } /** * 配置授權服務器端點,如令牌存儲,令牌自定義,用戶批准和授權類型,不包括端點安全配置 * @param endpoints * @throws Exception */ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { Collection<TokenEnhancer> tokenEnhancers = applicationContext.getBeansOfType(TokenEnhancer.class).values(); TokenEnhancerChain tokenEnhancerChain=new TokenEnhancerChain(); tokenEnhancerChain.setTokenEnhancers(new ArrayList<>(tokenEnhancers)); DefaultTokenServices defaultTokenServices = new DefaultTokenServices(); defaultTokenServices.setReuseRefreshToken(isReuseRefreshToken); defaultTokenServices.setSupportRefreshToken(isSupportRefreshToken); defaultTokenServices.setTokenStore(tokenStore); defaultTokenServices.setAccessTokenValiditySeconds(accessTokenValiditySeconds); defaultTokenServices.setRefreshTokenValiditySeconds(refreshTokenValiditySeconds); defaultTokenServices.setTokenEnhancer(tokenEnhancerChain); //若通過 JDBC 存儲令牌 if (Objects.nonNull(jdbcClientDetailsService)){ defaultTokenServices.setClientDetailsService(jdbcClientDetailsService); } endpoints .authenticationManager(authenticationManager) .userDetailsService(userDetailsService) .tokenServices(defaultTokenServices); } /** * 配置授權服務器端點的安全 * @param oauthServer * @throws Exception */ @Override public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception { oauthServer .tokenKeyAccess("permitAll()") .checkTokenAccess("permitAll()") .allowFormAuthenticationForClients(); } }
資源服務器配置類
/** * @author: 林塬 * @date: 2018/1/20 * @description: OAuth2 資源服務器配置類 */ @EnableResourceServer @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public abstract class ResServerConfig extends ResourceServerConfigurerAdapter { @Autowired(required = false) private RemoteTokenServices remoteTokenServices; @Autowired private OAuth2ClientProperties oAuth2ClientProperties; @Bean @Qualifier("authorizationHeaderRequestMatcher") public RequestMatcher authorizationHeaderRequestMatcher() { return new RequestHeaderRequestMatcher("Authorization"); } @Override public void configure(HttpSecurity http) throws Exception { http .csrf() .disable() .exceptionHandling() .and() .headers() .frameOptions() .disable() .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .requestMatcher(authorizationHeaderRequestMatcher()); } @Override public void configure(ResourceServerSecurityConfigurer resources) throws Exception { super.configure(resources); resources.resourceId(oAuth2ClientProperties.getClientId()); if (Objects.nonNull(remoteTokenServices)) { resources.tokenServices(remoteTokenServices); } } }
注解
/** * @author: 林塬 * @date: 2018/1/22 * @description: 在啟動類上添加該注解來----開啟 JWT 令牌存儲(授權服務器-非對稱加密) */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Import(AuthJWTTokenStore.class) public @interface EnableAuthJWTTokenStore { }
/** * @author: 林塬 * @date: 2018/1/22 * @description: 在啟動類上添加該注解來----開啟從數據庫加載客戶端詳情 */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Import(DBClientDetailsService.class) public @interface EnableDBClientDetailsService { }
/** * @author: 林塬 * @date: 2018/1/22 * @description: 在啟動類上添加該注解來----開啟通過數據庫存儲令牌 * 數據庫 schema :https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Import(DBTokenStore.class) public @interface EnableDBTokenStore { }
/** * @author: 林塬 * @date: 2018/1/22 * @description: 在啟動類上添加該注解來----開啟通過授權服務器驗證訪問令牌(適用於 JDBC、內存存儲令牌) */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Import(RemoteTokenService.class) public @interface EnableRemoteTokenService { }
/** * @author: 林塬 * @date: 2018/1/22 * @description: 在啟動類上添加該注解來----開啟 JWT 令牌存儲(資源服務器-非對稱加密) */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Import(ResJWTTokenStore.class) public @interface EnableResJWTTokenStore { }
authentication-server(授權服務器)
授權服務器配置
/** * @author: 林塬 * @date: 2018/1/10 * @description: OAuth2 授權服務器配置 */ @Configuration public class AuthorizationServerConfig extends AuthServerConfig { /** * 調用父類構造函數,設置令牌失效日期等信息 */ public AuthorizationServerConfig() { super((int)TimeUnit.DAYS.toSeconds(1), 0, false, false); } /** * 配置客戶端詳情 * @param clients * @throws Exception */ @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { super.configure(clients); clients.inMemory() // 使用內存存儲客戶端信息 .withClient("resource1") // client_id .secret("secret") // client_secret .authorizedGrantTypes("authorization_code","password") // 該client允許的授權類型 .scopes("read") // 允許的授權范圍 .autoApprove(true); //登錄后繞過批准詢問(/oauth/confirm_access) } }
WebSecurity 權限配置
/** * @author: 林塬 * @date: 2018/1/19 * @description: 權限配置 */ @Configuration public class WebSecurityConfig extends AbstractSecurityConfig { }
UserDetailsService 實現
/** * @author: 林塬 * @date: 2018/1/9 * @description: 用戶信息獲取 */ @Service public class UserDetailsService implements org.springframework.security.core.userdetails.UserDetailsService { /** * 通過 Username 加載用戶詳情 * @param username 用戶名 * @return UserDetails * @throws UsernameNotFoundException */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { if (username.equals("linyuan")) { PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); String password = passwordEncoder.encode("123456"); UserDetails userDetails = new User("linyuan", password, AuthorityUtils.commaSeparatedStringToAuthorityList(AuthoritiesEnum.USER.getRole())); return userDetails; } return null; } }
啟動類
@SpringBootApplication @EnableAuthJWTTokenStore // 使用 JWT 存儲令牌 //@EnableDBClientDetailsService //從 JDBC 加載客戶端詳情,需配置在啟動類上,若在子類上會出現順序問題,導致 Bean 創建失敗 public class AuthenticationServerApplication { public static void main(String[] args) { SpringApplication.run(AuthenticationServerApplication.class, args); } }
微服務配置
server:
port: 9005
encrypt:
key-store:
location: mytest.jks
secret: mypass
alias: mytest
# 若從數據庫中獲取客戶端信息則需配置數據庫源
#spring:
# datasource:
# driver-class-name: org.h2.Driver
# url: jdbc:h2:mem:test
# username: sa
# h2:
# console:
# enabled: true
單元測試
/** * @author: 林塬 * @date: 2018/1/16 * @description: 令牌單元測試 */ @Slf4j @RunWith(SpringRunner.class) @SpringBootTest(classes = AuthenticationServerApplication.class) @AutoConfigureMockMvc public class TokenControllerTest { @Autowired private MockMvc mockMvc; /** * 密碼授權模式獲取令牌 * * @throws Exception */ @Test public void getToken() throws Exception { MultiValueMap<String, String> map = new LinkedMultiValueMap<>(); map.put("username", Collections.singletonList("linyuan")); map.put("password", Collections.singletonList("123456")); map.put("grant_type", Collections.singletonList("password")); map.put("scope", Collections.singletonList("read")); int status = this.mockMvc.perform( post("/oauth/token") .header("Authorization", "Basic " + Base64.getEncoder().encodeToString("resource1:secret".getBytes())) .params(map) .contentType(MediaType.MULTIPART_FORM_DATA) .accept(MediaType.APPLICATION_JSON) ).andDo(print()).andReturn().getResponse().getStatus(); switch (status) { case HttpStatus.SC_OK: log.info("密碼授權模式獲取令牌---------------->成功(200)"); break; case HttpStatus.SC_UNAUTHORIZED: log.info("密碼授權模式獲取令牌---------------->失敗(401---沒有權限,請檢查驗證信息,賬號是否存在、客戶端信息)"); break; case HttpStatus.SC_BAD_REQUEST: log.info("密碼授權模式獲取令牌---------------->失敗(400---請求失敗,請檢查密碼是否正確)"); break; default: log.info("密碼授權模式獲取令牌---------------->失敗({}---未知結果)",status); break; } Assert.assertEquals(status,HttpStatus.SC_OK); } }
resource2-server(資源服務器2)
資源服務器配置
/** * @author: 林塬 * @date: 2018/1/11 * @description: 資源服務器訪問權限配置 */ @Configuration public class WebSecurityConfig extends ResServerConfig{ @Override public void configure(HttpSecurity http) throws Exception { super.configure(http); http.authorizeRequests() //訪問受保護資源 /res 的要求:客戶端 Scope 為 read,用戶本身角色為 USER .antMatchers("/res") .access("#oauth2.hasScope('read') and hasRole('USER')"); } }
受保護資源
/** * @author: 林塬 * @date: 2018/1/16 * @description: 資源服務器2-資源接口 */ @RestController public class ResController { @GetMapping("/res") public ResponseEntity<String> res(){ return ResponseEntity.ok("<h1>這是資源服務器2的受保護的資源</h1>"); } }
啟動類
@SpringBootApplication @EnableResJWTTokenStore //OAuth2 使用 JWT 解析令牌 public class Resource2ServerApplication { public static void main(String[] args) { SpringApplication.run(Resource2ServerApplication.class, args); } }
微服務配置
server: port: 9006 security: oauth2: resource: jwt: key-uri: http://localhost:9005/oauth/token_key
resource1-server(資源服務器1(客戶端))
資源服務器訪問權限配置
/** * @author: 林塬 * @date: 2018/1/18 * @description: 資源服務器訪問權限配置 */ @Configuration public class WebSecurityConfig extends ResServerConfig { @Override public void configure(HttpSecurity http) throws Exception { super.configure(http); http .authorizeRequests() .antMatchers("/login").permitAll() .antMatchers("/res","/res2/res") .access("#oauth2.hasScope('read') and hasRole('USER')"); } }
用戶登錄數據傳輸對象
/** * @author: 林塬 * @date: 2018/1/15 * @description: 用戶登錄數據傳輸對象 */ @Data public class LoginDTO implements Serializable { @NotBlank(message = "用戶名不能為空") private String username; @NotBlank(message = "密碼不能為空") private String password; }
資源服務器受保護資源
/** * @author: 林塬 * @date: 2018/1/16 * @description: 資源服務器1-資源接口 */ @RestController @AllArgsConstructor public class ResController { private RestTemplate restTemplate; @GetMapping("/res") public ResponseEntity<String> res(){ return ResponseEntity.ok("<h1>這是資源服務器1的受保護的資源</h1>"); } /** * 訪問資源服務器2-資源接口 * @param httpReq * @return */ @GetMapping("/res2/res") public ResponseEntity<String> remoteRes(HttpServletRequest httpReq){ //HttpEntity HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.set("Authorization",httpReq.getHeader("Authorization")); HttpEntity httpEntity = new HttpEntity(httpHeaders); //請求資源服務器2的資源 return restTemplate.exchange("http://localhost:9006/res",HttpMethod.GET,httpEntity,String.class); } }
令牌管理接口
/** * @author: 林塬 * @date: 2018/1/16 * @description: 令牌管理接口 */ @RestController @AllArgsConstructor public class TokenController { private OAuth2ClientProperties oAuth2ClientProperties; private OAuth2ProtectedResourceDetails oAuth2ProtectedResourceDetails; private RestTemplate restTemplate; /** * 通過密碼授權方式向授權服務器獲取令牌 * @param loginDTO * @param bindingResult * @return * @throws Exception */ @PostMapping(value = "/login") public ResponseEntity<OAuth2AccessToken> login(@RequestBody @Valid LoginDTO loginDTO, BindingResult bindingResult) throws Exception{ if (bindingResult.hasErrors()) { throw new Exception("登錄信息格式錯誤"); } else { //Http Basic 驗證 String clientAndSecret = oAuth2ClientProperties.getClientId()+":"+oAuth2ClientProperties.getClientSecret(); //這里需要注意為 Basic 而非 Bearer clientAndSecret = "Basic "+Base64.getEncoder().encodeToString(clientAndSecret.getBytes()); HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.set("Authorization",clientAndSecret); //授權請求信息 MultiValueMap<String, String> map = new LinkedMultiValueMap<>(); map.put("username", Collections.singletonList(loginDTO.getUsername())); map.put("password", Collections.singletonList(loginDTO.getPassword())); map.put("grant_type", Collections.singletonList(oAuth2ProtectedResourceDetails.getGrantType())); map.put("scope", oAuth2ProtectedResourceDetails.getScope()); //HttpEntity HttpEntity httpEntity = new HttpEntity(map,httpHeaders); //獲取 Token return restTemplate.exchange(oAuth2ProtectedResourceDetails.getAccessTokenUri(), HttpMethod.POST,httpEntity,OAuth2AccessToken.class); } } }
啟動類
@SpringBootApplication @EnableResJWTTokenStore //OAuth2 使用 JWT 解析令牌 public class Resource1ServerApplication { @Bean public RestTemplate restTemplate(){ return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(Resource1ServerApplication.class, args); } }
微服務配置
server: port: 9007 security: oauth2: client: clientId: resource1 clientSecret: secret userAuthorizationUri: http://localhost:9005/oauth/authorize grant-type: password scope: read access-token-uri: http://localhost:9005/oauth/token resource: jwt: key-uri: http://localhost:9005/oauth/token_key basic: enabled: false
單元測試
/** * @author: 林塬 * @date: 2018/1/19 * @description: 資源獲取單元測試 */ @Slf4j @RunWith(SpringRunner.class) @SpringBootTest(classes = Resource1ServerApplication.class) @AutoConfigureMockMvc public class ResControllerTest { @Autowired private MockMvc mockMvc; private ObjectMapper objectMapper = new ObjectMapper(); private OAuth2AccessToken oAuth2AccessToken; /** * 獲取令牌 * @throws Exception */ @Before public void getToken() throws Exception { LoginDTO loginDTO = new LoginDTO(); loginDTO.setUsername("linyuan"); loginDTO.setPassword("123456"); byte[] body = this.mockMvc.perform( post("/login") .content(objectMapper.writeValueAsBytes(loginDTO)) .contentType(MediaType.APPLICATION_JSON) //請求數據的格式 .accept(MediaType.APPLICATION_JSON) //接收返回數據的格式 ).andExpect(status().isOk()) .andReturn().getResponse().getContentAsByteArray(); oAuth2AccessToken = objectMapper.readValue(body,OAuth2AccessToken.class); } /** * 測試訪問本地受保護資源 * @throws Exception */ @Test public void testGetLocalRes() throws Exception{ int status = this.mockMvc.perform( get("/res") .header("Authorization",OAuth2AccessToken.BEARER_TYPE+" "+oAuth2AccessToken.getValue()) .accept(MediaType.APPLICATION_JSON) ).andDo(print()).andReturn().getResponse().getStatus(); printStatus(status); Assert.assertEquals(status,HttpStatus.SC_OK); } /** * 測試訪問資源服務器2受保護資源 * @throws Exception */ @Test public void testGetRes2lRes() throws Exception{ int status = this.mockMvc.perform( get("/res2/res") .header("Authorization",OAuth2AccessToken.BEARER_TYPE+" "+oAuth2AccessToken.getValue