本篇示例的结构如图所示:

授权、资源结构图.jpg

客户端向授权服务器请求令牌,授权服务器经过认证以后,将令牌返回给客户端。

客户端携带第1步返回的令牌,向资源服务器请求资源。

资源服务器向授权服务器验证令牌的合法性和有效性。

若验证成功,资源服务器向客户端返回受限资源。

1、搭建授权服务器

授权服务器是在上一篇搭建Spring Cloud Security单体应用的基础上完成的。

pom.xml添加依赖

org.springframework.cloud
spring-cloud-starter-netflix-eureka-client

Eureka服务注册与发现中心的搭建参考服务注册与发现: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;
//开启授权服务器
@EnableAuthorizationServer
@Configuration
public class AuthorizationServerConfig
extends AuthorizationServerConfigurerAdapter
{
@Autowired
AuthenticationManager manager;
@Autowired
PasswordEncoder passwordEncoder;
/**
* 用来指定哪些客户端可以来请求授权
* 配置客户端详情信息
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//暂时使用内存方式
//配置一个客户端,既可以通过授权码类型获取令牌,也可以通过密码类型获取令牌
clients.inMemory().withClient("client") //客户端ID
.authorizedGrantTypes("authorization_code","password", "refresh_token") //客户端可以使用的授权类型
.scopes("all") //允许请求范围
.secret(passwordEncoder.encode("secret")) //客户端密钥
.redirectUris("http://localhost:8768");//回调地址
}
/**
* 令牌访问端点
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints
.tokenStore(new InMemoryTokenStore())
.accessTokenConverter(accessTokenConverter())
.authenticationManager(manager)
.reuseRefreshTokens(false);
}
/**
* 配置JWT转换器
* @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、搭建资源服务器

首先按照服务注册与发现:Eureka中的contro-record搭建Eureka Client

pom.xml然后添加security和oauth依赖

org.springframework.cloud
spring-cloud-starter-security
org.springframework.cloud
spring-cloud-starter-oauth2

新建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,用于向认证服务器验证令牌
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 {
//配置资源服务器的拦截规则
http
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and().requestMatchers().anyRequest()
.and().anonymous()
//test_resource接口必须经过认证才可以访问
.and().authorizeRequests().antMatchers("/test_resource").authenticated()
.and()
.exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
}
/**
* 配置JWT转换器
* @return
*/
@Bean
public JwtAccessTokenConverter accessTokenConverter(){
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setSigningKey("secret");
return converter;
}
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}

@EnableResourceServer注解开启资源服务,configure(ResourceServerSecurityConfigurer resources)方法添加授权服务器相关配置,configure(HttpSecurity http)配置了哪些资源需要授权才能访问,accessTokenConverter()配置JWT转换器解析令牌。

3、测试授权

依次启动control-eureka-server、control-security和control-user。

使用postman访问:http://localhost:8762/test_resource,返回未授权。

未授权.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自带的,不需要我们自己手写。

然后复制access_token,再次访问首先资源接口:

返回资源.jpg