本篇示例的結(jié)構(gòu)如圖所示:

授權(quán)、資源結(jié)構(gòu)圖.jpg
- 客戶端向授權(quán)服務(wù)器請求令牌,授權(quán)服務(wù)器經(jīng)過認證以后,將令牌返回給客戶端。
- 客戶端攜帶第1步返回的令牌,向資源服務(wù)器請求資源。
- 資源服務(wù)器向授權(quán)服務(wù)器驗證令牌的合法性和有效性。
- 若驗證成功,資源服務(wù)器向客戶端返回受限資源。
1、搭建授權(quán)服務(wù)器
授權(quán)服務(wù)器是在上一篇搭建Spring Cloud Security單體應(yīng)用的基礎(chǔ)上完成的。
pom.xml添加依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
Eureka服務(wù)注冊與發(fā)現(xiàn)中心的搭建參考服務(wù)注冊與發(fā)現(xiàn):Eureka
application.yml添加Eureka Client配置
eureka:
instance:
instance-id: ${spring.application.name}:${vcap.application.instance_id:${spring. application.instance_id:${random.value}}}
client:
service-url:
default-zone: http://localhost:8761/eureka/
新建AuthorizationServerConfig
package com.ljessie.controlsecurity.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
//開啟授權(quán)服務(wù)器
@EnableAuthorizationServer
@Configuration
public class AuthorizationServerConfig
extends AuthorizationServerConfigurerAdapter
{
@Autowired
AuthenticationManager manager;
@Autowired
PasswordEncoder passwordEncoder;
/**
* 用來指定哪些客戶端可以來請求授權(quán)
* 配置客戶端詳情信息
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//暫時使用內(nèi)存方式
//配置一個客戶端,既可以通過授權(quán)碼類型獲取令牌,也可以通過密碼類型獲取令牌
clients.inMemory().withClient("client") //客戶端ID
.authorizedGrantTypes("authorization_code","password", "refresh_token") //客戶端可以使用的授權(quán)類型
.scopes("all") //允許請求范圍
.secret(passwordEncoder.encode("secret")) //客戶端密鑰
.redirectUris("http://localhost:8768");//回調(diào)地址
}
/**
* 令牌訪問端點
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.tokenStore(new InMemoryTokenStore())
.accessTokenConverter(accessTokenConverter())
.authenticationManager(manager)
.reuseRefreshTokens(false);
}
/**
* 配置JWT轉(zhuǎn)換器
* @return
*/
@Bean
public JwtAccessTokenConverter accessTokenConverter(){
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("secret");
return converter;
}
/**
* 令牌端點的安全策略
* @param security
* @throws Exception
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security
//允許所有人請求令牌
.tokenKeyAccess("permitAll()")
//已驗證的客戶端才能請求check_token端點
.checkTokenAccess("isAuthenticated()")
//允許表單認證,申請令牌
.allowFormAuthenticationForClients();
}
}
2、搭建資源服務(wù)器
首先按照服務(wù)注冊與發(fā)現(xiàn):Eureka中的contro-record搭建Eureka Client
pom.xml然后添加security和oauth依賴
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
新建TestController,配置一個受限資源接口test_resource
package com.ljessie.controluser.controller;
import com.ljessie.controluser.entity.User;
import com.ljessie.controluser.service.impl.UserServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class TestController {
@RequestMapping("test_resource")
public String testResource(){
return "返回受限資源";
}
}
新建ResourceServerConfig
package com.ljessie.controluser.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
import java.io.IOException;
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
@Autowired
RestTemplate restTemplate;
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources
.tokenStore(new JwtTokenStore(accessTokenConverter()))
.stateless(true);
//配置RemoteTokenServices,用于向認證服務(wù)器驗證令牌
RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
remoteTokenServices.setAccessTokenConverter(accessTokenConverter());
restTemplate.setErrorHandler(new DefaultResponseErrorHandler(){
/**
* 忽略400異常
* @param response
* @param statusCode
* @throws IOException
*/
@Override
protected void handleError(ClientHttpResponse response, HttpStatus statusCode) throws IOException {
if(response.getRawStatusCode() != 400){
super.handleError(response);
}
}
});
remoteTokenServices.setRestTemplate(restTemplate);
remoteTokenServices.setCheckTokenEndpointUrl("http://authorizationServer/oauth/check_token");
remoteTokenServices.setClientId("client");
remoteTokenServices.setClientSecret("secret");
resources.tokenServices(remoteTokenServices)
.stateless(true);
}
@Override
public void configure(HttpSecurity http) throws Exception {
//配置資源服務(wù)器的攔截規(guī)則
http
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and().requestMatchers().anyRequest()
.and().anonymous()
//test_resource接口必須經(jīng)過認證才可以訪問
.and().authorizeRequests().antMatchers("/test_resource").authenticated()
.and()
.exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
}
/**
* 配置JWT轉(zhuǎn)換器
* @return
*/
@Bean
public JwtAccessTokenConverter accessTokenConverter(){
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("secret");
return converter;
}
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
@EnableResourceServer注解開啟資源服務(wù),configure(ResourceServerSecurityConfigurer resources)方法添加授權(quán)服務(wù)器相關(guān)配置,configure(HttpSecurity http)配置了哪些資源需要授權(quán)才能訪問,accessTokenConverter()配置JWT轉(zhuǎn)換器解析令牌。
3、測試授權(quán)
依次啟動control-eureka-server、control-security和control-user。
使用postman訪問:http://localhost:8762/test_resource,返回未授權(quán)。

未授權(quán).jpg
訪問請求令牌接口,請求方式POST:http://localhost:8766/oauth/token?username=066111&password=123456&grant_type=password&scope=all&client_id=client&client_secret=secret

返回令牌.jpg
請求令牌的接口是Spring Cloud Security自帶的,不需要我們自己手寫。
然后復(fù)制access_token,再次訪問首先資源接口:

返回資源.jpg