注意,只要网管zuul设置就可以了,如果底下的服务配置和网关都设置,导致跨域失败。
这种跨域问题,经常出现在前后端分离。
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
final CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true); // 允许cookies跨域
config.addAllowedOrigin("*");// #允许向该服务器提交请求的URI,*表示全部允许,在SpringMVC中,如果设成*,会自动转成当前请求头中的Origin
config.addAllowedHeader("*");// #允许访问的头信息,*表示全部
config.setMaxAge(18000L);// 预检请求的缓存时间(秒),即在这个时间段里,对于相同的跨域请求不会再预检了
config.addAllowedMethod("*");// 允许提交请求的方法,*表示全部允许
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
引入pom
org.springframework.cloud
spring-cloud-starter-security
org.springframework.cloud
spring-cloud-starter-oauth2
配置授权服务器,实现一个授权服务,需要授权类型(grantType),不同授权类型视为不同的客户端,提供不同的获取令牌(Token)的方式,每一个客户端都可以通过明确的配置以及权限来实现不同的授权访问机制。
配置类中,需要重写几个方法,来配置授权服务器
客户端的详情,clientDetails
ClientDetailsServiceConfigurer可以用内存或JDBC方式实现获取已注册的客户端详情。
clientId:客户端标识ID
secret:客户端安全码
scope:客户端访问范围,默认为空则拥有全部范围
authorizedGrantTypes:客户端使用的授权类型,默认为空
authorities:客户端可使用的权限
/**
* 我们将client信息存储到oauth_client_details表里
* 并将数据缓存到redis
*
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(redisClientDetailsService);
redisClientDetailsService.loadAllClientToCache();
}
ClientDetailsServiceConfigurer:配置客户端详细服务(ClientDetailsService)。客户端的相信信息在这里初始化(访问者的信息)。这里可以写死,可以从数据库。我们是通过redis缓存,可以增加时效性。
这个需要按官方给的表结构在数据库构建,来保存客户端信息,设置你自己的账号,密码
后面获得token权限访问需要加账号
http://localhost:8771/auth/oauth/token?username=wangxi&password=111111&grant_type=password&scope=app&client_id=system&client_secret=111111
我们自定义redisClientDetailsService,继承JdbcClientDetailsService。
对于redisClientDetailsService,需要重写几个方法,这些方法都是对于客户端详细信息的处理。
需要将客户端详细信息存在数据库 和 redis
@Autowired
private StringRedisTemplate stringRedisTemplate;
//配置数据源
public RedisClientDetailsService(DataSource dataSource) {
super(dataSource);
}
@Override
public ClientDetails loadClientByClientId(String clientId) throws InvalidClientException {
ClientDetails clientDetails = null;
// 先从redis获取
String value = (String) stringRedisTemplate.boundHashOps(CACHE_CLIENT_KEY).get(clientId);
if (StringUtils.isBlank(value)) {
clientDetails = cacheAndGetClient(clientId);
} else {
clientDetails = JSONObject.parseObject(value, BaseClientDetails.class);
}
return clientDetails;
}
/**
* 缓存client并返回client
*
* @param clientId
*/
private ClientDetails cacheAndGetClient(String clientId) {
// 从数据库读取
ClientDetails clientDetails = super.loadClientByClientId(clientId);
if (clientDetails != null) {// 写入redis缓存
stringRedisTemplate.boundHashOps(CACHE_CLIENT_KEY).put(clientId, JSONObject.toJSONString(clientDetails));
}
return clientDetails;
}
父类提供了从数据库取客户端信息公共方法,我们会需要增加的就是写入redis
还有@Override
public void updateClientDetails(ClientDetails clientDetails) throws NoSuchClientException
以及@Override
public void updateClientDetails(ClientDetails clientDetails) throws NoSuchClientException
@Override
public void removeClientDetails(String clientId) throws NoSuchClientException
这些方法从字面理解就可以了。我们这里要做的就是同步更新redis即可。
AuthorizationServerEndpointsConfigurer:用来配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)。
authenticationManager:认证管理器,当你选择了资源所有者的密码授权类型,需要设置这个属性注入一个authenticationManager对象
userDetailsService:定义自己的userDetailsService验证用户
authorizationCodeServices:设置收取码服务,AuthorizationCodeServices 的实例。用于authorization_code授权码类型
对象有一个 pathMapping() 方法用来配置端点的 URL
下面是一些默认的端点 URL:
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
//默认的认证管理器
endpoints.authenticationManager(this.authenticationManager);
//设置令牌,生成token方式和存储
endpoints.tokenStore(tokenStore());
//redis存储授权码,就是将授权码,存在redis中
endpoints.authorizationCodeServices(redisAuthorizationCodeServices);
if (storeWithJwt) {
endpoints.accessTokenConverter(accessTokenConverter());
} else {
endpoints.tokenEnhancer((accessToken, authentication) -> {
addLoginUserInfo(accessToken, authentication);
return accessToken;
});
}
}
/**
* 令牌存储
*/
@Bean
public TokenStore tokenStore() {
//配置文件,设置是否用jwt生成token,并存入数据库
//对于jwt,我们需要转换器来自定义
if (storeWithJwt) {
return new JwtTokenStore(accessTokenConverter());
}
//使用Redis来存令牌,下面的RandomAuthenticationKeyGenerator,则是生成一个uuid唯一字符,set到redis中
RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory);
redisTokenStore.setAuthenticationKeyGenerator(new RandomAuthenticationKeyGenerator());
return redisTokenStore;
}
/**
* Jwt资源令牌转换器
只是扩展可以不看
* 参数access_token.store-jwt为true时用到
*
* @return accessTokenConverter
*/
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter() {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
OAuth2AccessToken oAuth2AccessToken = super.enhance(accessToken, authentication);
addLoginUserInfo(oAuth2AccessToken, authentication); // 2018.07.13 将当前用户信息追加到登陆后返回数据里
return oAuth2AccessToken;
}
};
DefaultAccessTokenConverter defaultAccessTokenConverter = (DefaultAccessTokenConverter) jwtAccessTokenConverter
.getAccessTokenConverter();
DefaultUserAuthenticationConverter userAuthenticationConverter = new DefaultUserAuthenticationConverter();
userAuthenticationConverter.setUserDetailsService(userDetailsService);
defaultAccessTokenConverter.setUserTokenConverter(userAuthenticationConverter);
jwtAccessTokenConverter.setSigningKey(signingKey);
return jwtAccessTokenConverter;
}
AuthorizationServerSecurityConfigurer:用来配置令牌端点(Token Endpoint)的安全约束
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.allowFormAuthenticationForClients(); // 允许表单形式的认证
}
/**
* 我们将client信息存储到oauth_client_details表里
* 并将数据缓存到redis
*
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(redisClientDetailsService);
redisClientDetailsService.loadAllClientToCache();
}
资源服务器:resource-server
要访问资源服务器受保护的资源需要携带令牌(授权服务器获得)
客户端之前也是资源服务器,各个服务相互访问需要携带令牌。
注意:认证中心,我们编写
其他服务端,我们访问认证中心,只要返回用户信息 不是null则认为认证成功