以下是把配置文件放在Git仓库,SpringCloudConfig配置中心拉取,动态刷新
一.for pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!--config-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<!--spring-cloud-bus-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<!-- actuator监控 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- hystrix容错 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!-- 加密标配 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<!--添加 重试机制 的依赖
因网络的抖动等原因导致config-client在启动时候访问config-server没有访问成功从而报错,
希望config-client能重试几次,故重试机制
-->
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--nosql-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<!--cache-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>1.5.13.RELEASE</version>
<configuration>
<!-- 指定程序入口 -->
<mainClass>com.huajie.provider.auth.AuthApplication</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
<!--<execution>-->
<!--<goals>-->
<!--<goal>build-info</goal>-->
<!--</goals>-->
<!--</execution>-->
</executions>
</plugin>
<!-- 添加docker-maven插件 -->
<plugin>
<groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>0.4.13</version>
<configuration>
<imageName>${project.artifactId}:${project.version}</imageName>
<baseImage>java</baseImage>
<entryPoint>["java", "-jar", "/${project.build.finalName}.jar"]</entryPoint>
<!--覆盖已存在的标签 镜像-->
<forceTags>true</forceTags>
<resources>
<resource>
<targetPath>/</targetPath>
<directory>${project.build.directory}</directory>
<include>${project.build.finalName}.jar</include>
</resource>
</resources>
</configuration>
</plugin>
</plugins>
</build>
二 for java file (class)
1. OAuth 授权服务器配置
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
private static final Logger log = LoggerFactory.getLogger(AuthorizationServerConfiguration.class);
// 注入认证管理
@Autowired
AuthenticationManager authenticationManager;
// 方案 一:采用redis缓存服务存储token
@Autowired
RedisConnectionFactory redisConnectionFactory;
// 方案二 使用内存存储token
@Autowired
private TokenStore tokenStore;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
log.info("======================^_^ start client validation ^_^========================");
//自定义验证:实现 ClientDetailsService接口
clients.withClientDetails(new BaseClientDetailService());
//内存中 配置客户端,一个用于password认证一个用于client认证
// clients.inMemory()
// .withClient("client_1")
// .resourceIds("order")
// .authorizedGrantTypes("client_credentials", "refresh_token")
// .scopes("select")
// .authorities("oauth2")
// .secret(finalSecret)
// .and()
// .withClient("client_2")
// .resourceIds("order") // 资源id
// .authorizedGrantTypes("password", "refresh_token")
// .scopes("select")
// .authorities("oauth2")
// .secret(finalSecret)
// .and()
// .withClient("client_code")
// .resourceIds(DEMO_RESOURCE_ID) // 资源id
// .authorizedGrantTypes("authorization_code", "client_credentials", "refresh_token",
// "password", "implicit")
// .scopes("all")
// //.authorities("oauth2")
// .redirectUris("http://www.baidu.com")
// .accessTokenValiditySeconds(1200)
// .refreshTokenValiditySeconds(50000);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
log.info("======================^_^ start generate token ^_^========================");
// 基于方案一,*******redis服务存储token*********
// 样例keys如下:
// 1) "auth_to_access:227ccfa0102c5cbefcc06a8b99bc12fa"
// 2) "uname_to_access:client:admin"
// 3) "access_to_refresh:e711ab59-f49b-4400-a3eb-4af90df67395"
// 4) "client_id_to_access:client"
// 5) "refresh_to_access:c45d5a9e-f3e6-4170-9ae7-b8f57e67277f"
// 6) "refresh:c45d5a9e-f3e6-4170-9ae7-b8f57e67277f"
// 7) "refresh_auth:c45d5a9e-f3e6-4170-9ae7-b8f57e67277f"
// 8) "access:e711ab59-f49b-4400-a3eb-4af90df67395"
// 9) "auth:e711ab59-f49b-4400-a3eb-4af90df67395"
endpoints
.tokenStore(new MyRedisTokenStore(redisConnectionFactory))
.authenticationManager(authenticationManager)
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
// .userDetailsService(userService); //配置userService 这样每次认证的时候会去检验用户是否锁定,有效等
// 基于方案二,**********内存存储token************
// endpoints.tokenStore(tokenStore)
// .authenticationManager(authenticationManager)
// .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
// .userDetailsService(userService); //配置userService 这样每次认证的时候会去检验用户是否锁定,有效等
//配置TokenService参数 设置默认access_token,refresh_token有效时间
DefaultTokenServices tokenService = new DefaultTokenServices();
tokenService.setTokenStore(endpoints.getTokenStore());
tokenService.setSupportRefreshToken(true);
tokenService.setClientDetailsService(endpoints.getClientDetailsService());
tokenService.setTokenEnhancer(endpoints.getTokenEnhancer());
tokenService.setAccessTokenValiditySeconds((int) TimeUnit.HOURS.toSeconds(12)); // 12 小时
tokenService.setRefreshTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(7)); // 7天
tokenService.setReuseRefreshToken(false);
endpoints.tokenServices(tokenService);
}
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
log.info("======================^_^ start validation token ^_^========================");
//允许表单认证
//这里增加拦截器到安全认证链中,实现自定义认证,包括图片验证,短信验证,微信小程序,第三方系统,CAS单点登录
//addTokenEndpointAuthenticationFilter(IntegrationAuthenticationFilter())
//IntegrationAuthenticationFilter 采用 @Component 注入
oauthServer.allowFormAuthenticationForClients()
.tokenKeyAccess("isAuthenticated()")
.checkTokenAccess("permitAll()");
}
//基于方案二:使用内存的tokenStore
@Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
}
2.Spring-Security 配置
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) //启用方法级的权限认证
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private static final Logger log = LoggerFactory.getLogger(SecurityConfiguration.class);
//通过自定义userDetailsService 来实现查询数据库,手机,二维码等多种验证方式
@Bean
@Override
protected UserDetailsService userDetailsService(){
//采用一个自定义的实现UserDetailsService接口的类
//return baseUserDetailService;
//return new BaseUserDetailService();
return new UserDetailsService(){
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("接收到权限认证username:{}",username);
SysUserAuthentication user = null;
// 1.真实开发从数据库进行验证用户,以及其对应权限集role
// TODO: 2018/12/18
// // 根据用户名查找系统用户
// SysUser user = userService.findByUserName(username);
// if (user != null) {
// // 根据存在的系统用户查找权限
// List<Permission> permissions = permissionDao.findByAdminUserId(user.getId());
// // 构建权限集合
// List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
// // 将系统用户数据库菜单权限封装到权限集合
// for (Permission permission : permissions) {
// if (permission != null && permission.getName() != null) {
// GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(permission.getName());
// // 此处将权限信息添加到 GrantedAuthority 对象中,在后面进行全权限验证时会使用GrantedAuthority 对象。
// grantedAuthorities.add(grantedAuthority);
// }
// }
// // 返回userdetails类型User
// return new User(user.getUsername(), user.getPassword(), grantedAuthorities);
// } else {
// throw new UsernameNotFoundException(username + " do not exist!");
// }
//2.以下是测试使用
if("admin".equals(username)) {
// IntegrationAuthentication auth = IntegrationAuthenticationContext.get();
//这里可以通过auth 获取 user 值
//然后根据当前登录方式type 然后创建一个sysuserauthentication 重新设置 username 和 password
//比如使用手机验证码登录的, username就是手机号 password就是6位的验证码{noop}000000
//System.out.println(auth);
List<GrantedAuthority> list = AuthorityUtils.createAuthorityList("admin_role"); //所谓的角色,只是增加ROLE_前缀
user = new SysUserAuthentication();
user.setUsername(username);
user.setPassword("{noop}123456");
user.setAuthorities(list);
user.setAccountNonExpired(true);
user.setAccountNonLocked(true);
user.setCredentialsNonExpired(true);
user.setEnabled(true);
}
//返回UserDetails的实现user不为空,则验证通过
return user;
}
};
/*
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
String finalPassword = "{bcrypt}"+bCryptPasswordEncoder.encode("123456");
manager.createUser(User.withUsername("user_1").password(finalPassword).authorities("USER").build());
finalPassword = "{noop}123456";
manager.createUser(User.withUsername("user_2").password(finalPassword).authorities("USER").build());
return manager;
*/
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// http.authorizeRequests()
// .antMatchers("/", "/index.html", "/oauth/**").permitAll() //允许访问
// .anyRequest().authenticated() //其他地址的访问需要验证权限
// .and()
// .formLogin()
// .loginPage("/login.html") //登录页
// .failureUrl("/login-error.html").permitAll()
// .and()
// .logout()
// .logoutSuccessUrl("/index.html");
http.authorizeRequests().anyRequest().fullyAuthenticated();
http.formLogin().loginPage("/login").failureUrl("/login?code=").permitAll();
http.logout().permitAll();
http.authorizeRequests().antMatchers("/oauth/authorize","/oauth/**").permitAll();
}
/**
* 用户验证
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
super.configure(auth);
}
/**
* Spring Boot 2 配置,这里要bean 注入
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
AuthenticationManager manager = super.authenticationManagerBean();
return manager;
}
/**
* 加密方式
*/
@Bean
PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
}
3.OAuth 资源服务器配置
@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
private static final Logger log = LoggerFactory.getLogger(ResourceServerConfiguration.class);
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
log.info("=====================^_^ start resources Safety certification ^_^===================");
// resourceId 用于分配给可授予的clientId
// stateless 标记以指示在这些资源上仅允许基于令牌的身份验证
//resources.resourceId("order").stateless(true);
resources.stateless(true);
// authenticationEntryPoint 认证异常流程处理返回
// tokenExtractor token获取方式,默认BearerTokenExtractor
// 从header获取token为空则从request.getParameter("access_token")
// .authenticationEntryPoint(authenticationEntryPoint).tokenExtractor(unicomTokenExtractor);
}
@Override
public void configure(HttpSecurity http) throws Exception {
log.info("======================^_^ start http Safety certification ^_^========================");
// Since we want the protected resources to be accessible in the UI as well we need
// session creation to be allowed (it's disabled by default in 2.0.6)
http
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.requestMatchers().anyRequest()
.and()
.anonymous()
.and()
// 配置order访问控制,必须认证过后才可以访问
// .authorizeRequests()
// .antMatchers("/order/**").authenticated();
// 配置访问控制,必须具有admin_role权限才可以访问资源
// .authorizeRequests()
// .antMatchers("/order/**").hasAuthority("admin_role");
// .antMatchers("/order/**").hasAnyRole("admin");
// 匹配不需要资源认证路径
.authorizeRequests()
.antMatchers("/swagger-ui.html", "/swagger-resources/**",
"/v2/api-docs/**", "/validatorUrl", "/valid", "/webjar/**"
).permitAll().and()
// 所有的需要权限才能访问
.authorizeRequests().anyRequest().fullyAuthenticated();
}
}
4.采用redis存储token,配置redisfactory
@Configuration
@RefreshScope
public class RedisConfiguration {
private final static Logger log = LoggerFactory.getLogger(RedisConfiguration.class);
@Value("${spring.redis.jedis.pool.max-idle}")
private Integer maxIdle;
@Value("${spring.redis.jedis.pool.min-idle}")
private String minIdle;
@Value("${spring.redis.jedis.pool.max-active}")
private String maxActive;
@Value("${spring.redis.jedis.pool.max-wait}")
private Integer maxWait;
@Value("${spring.redis.jedis.pool.maxTotal}")
private Integer maxTotal;
@Value("${spring.redis.jedis.pool.minEvictableIdleTimeMillis}")
private Integer minEvictableIdleTimeMillis;
@Value("${spring.redis.jedis.pool.numTestsPerEvictionRun}")
private Integer numTestsPerEvictionRun;
@Value("${spring.redis.jedis.pool.timeBetweenEvictionRunsMillis}")
private long timeBetweenEvictionRunsMillis;
@Value("${spring.redis.jedis.pool.testOnBorrow}")
private boolean testOnBorrow;
@Value("${spring.redis.jedis.pool.testWhileIdle}")
private boolean testWhileIdle;
@Value("${spring.redis.host}")
private String hostname;
@Value("${spring.redis.password}")
private String password;
@Value("${spring.redis.port}")
private Integer port;
@Value("${spring.redis.timeout}")
private Integer timeout;
@Bean
public JedisPoolConfig jedisPoolConfig(){
log.info(">>************************* start of [ redis link pool configuration ] of global configuration ");
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
// 最大空闲数
log.info(">>========================maxIdle:{}======================<<",maxIdle);
jedisPoolConfig.setMaxIdle(maxIdle);
// 连接池的最大数据库连接数
log.info(">>========================maxTotal:{}======================<<",maxTotal);
jedisPoolConfig.setMaxTotal(maxTotal);
// 最大建立连接等待时间
jedisPoolConfig.setMaxWaitMillis(maxWait);
// 逐出连接的最小空闲时间 默认1800000毫秒(30分钟)
jedisPoolConfig.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
// 每次逐出检查时 逐出的最大数目 如果为负数就是 : 1/abs(n), 默认3
jedisPoolConfig.setNumTestsPerEvictionRun(numTestsPerEvictionRun);
// 逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1
jedisPoolConfig.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
// 是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个
jedisPoolConfig.setTestOnBorrow(testOnBorrow);
// 在空闲时检查有效性, 默认false
jedisPoolConfig.setTestWhileIdle(testWhileIdle);
log.info("<<************************* end of [ redis link pool configuration ] of global configuration ");
return jedisPoolConfig;
}
@Bean
@Primary
public JedisConnectionFactory jedisConnectionFactory(JedisPoolConfig jedisPoolConfig){
log.info(">>************************* start of [ redis connection factory configuration ] of global configuration ");
JedisConnectionFactory JedisConnectionFactory = new JedisConnectionFactory(jedisPoolConfig);
//连接池
JedisConnectionFactory.setPoolConfig(jedisPoolConfig);
//IP地址
log.info(">>========================hostname:{}======================<<",hostname);
JedisConnectionFactory.setHostName(hostname);
//端口号
log.info(">>===========================port:{}==========================<<",port);
JedisConnectionFactory.setPort(port);
//如果Redis设置有密码
JedisConnectionFactory.setPassword(password);
//客户端超时时间单位是毫秒
JedisConnectionFactory.setTimeout(timeout);
log.info(">>************************* end of [ redis connection factory configuration ] of global configuration ");
return JedisConnectionFactory;
}
@Bean
public RedisTemplate<String, Object> redisTemplate(JedisConnectionFactory factory){
// 解决 org.springframework.data.redis.serializer.SerializationException: Could not read JSON
// 序列化方式不一样,旧的是通过StringRedisSerializer进行序列化的,springboot是通过Jackson2JsonRedisSerializer进行序列化
RedisTemplate redisTemplate = new StringRedisTemplate(factory);
StringRedisSerializer stringRedisSerializer =new StringRedisSerializer();
redisTemplate.setValueSerializer(stringRedisSerializer);
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
redisTemplate.setHashValueSerializer(stringRedisSerializer);
redisTemplate.afterPropertiesSet();
// 开启事务
redisTemplate.setEnableTransactionSupport(true);
redisTemplate.setConnectionFactory(factory);
return redisTemplate;
}
}
@Configuration
@EnableCaching
public class CacheConfiguration {
@Autowired
private JedisConnectionFactory factory;
/**
* @author enzo
* @date 2018/10/14 下午3:16
* @todo cache manager
* @param
* @throws
* @return
* @remark
*/
@Bean
public CacheManager cacheManager(){
return RedisCacheManager
.RedisCacheManagerBuilder
.fromConnectionFactory(factory)
.build();
}
}
5.重写tokenStore .因为最新版中RedisTokenStore的set已经被弃用了, * 会报:nested exception is java.lang.NoSuchMethodError: org.springframework.data.redis.connection.RedisConnection.set([B[B)V * 所以自定义一个,代码和RedisTokenStore一样, * 只是把所有conn.set(…)都换成conn..stringCommands().set(…)
@Component
public class MyRedisTokenStore implements TokenStore {
private static final String ACCESS = "access:";
private static final String AUTH_TO_ACCESS = "auth_to_access:";
private static final String AUTH = "auth:";
private static final String REFRESH_AUTH = "refresh_auth:";
private static final String ACCESS_TO_REFRESH = "access_to_refresh:";
private static final String REFRESH = "refresh:";
private static final String REFRESH_TO_ACCESS = "refresh_to_access:";
private static final String CLIENT_ID_TO_ACCESS = "client_id_to_access:";
private static final String UNAME_TO_ACCESS = "uname_to_access:";
private final RedisConnectionFactory connectionFactory;
private AuthenticationKeyGenerator authenticationKeyGenerator = new DefaultAuthenticationKeyGenerator();
private RedisTokenStoreSerializationStrategy serializationStrategy = new JdkSerializationStrategy();
private String prefix = "";
public MyRedisTokenStore(RedisConnectionFactory connectionFactory) {
this.connectionFactory = connectionFactory;
}
public void setAuthenticationKeyGenerator(AuthenticationKeyGenerator authenticationKeyGenerator) {
this.authenticationKeyGenerator = authenticationKeyGenerator;
}
public void setSerializationStrategy(RedisTokenStoreSerializationStrategy serializationStrategy) {
this.serializationStrategy = serializationStrategy;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
private RedisConnection getConnection() {
return connectionFactory.getConnection();
}
private byte[] serialize(Object object) {
return serializationStrategy.serialize(object);
}
private byte[] serializeKey(String object) {
return serialize(prefix + object);
}
private OAuth2AccessToken deserializeAccessToken(byte[] bytes) {
return serializationStrategy.deserialize(bytes, OAuth2AccessToken.class);
}
private OAuth2Authentication deserializeAuthentication(byte[] bytes) {
return serializationStrategy.deserialize(bytes, OAuth2Authentication.class);
}
private OAuth2RefreshToken deserializeRefreshToken(byte[] bytes) {
return serializationStrategy.deserialize(bytes, OAuth2RefreshToken.class);
}
private byte[] serialize(String string) {
return serializationStrategy.serialize(string);
}
private String deserializeString(byte[] bytes) {
return serializationStrategy.deserializeString(bytes);
}
@Override
public OAuth2AccessToken getAccessToken(OAuth2Authentication authentication) {
String key = authenticationKeyGenerator.extractKey(authentication);
byte[] serializedKey = serializeKey(AUTH_TO_ACCESS + key);
byte[] bytes = null;
RedisConnection conn = getConnection();
try {
bytes = conn.get(serializedKey);
} finally {
conn.close();
}
OAuth2AccessToken accessToken = deserializeAccessToken(bytes);
if (accessToken != null
&& !key.equals(authenticationKeyGenerator.extractKey(readAuthentication(accessToken.getValue())))) {
// Keep the stores consistent (maybe the same user is
// represented by this authentication but the details have
// changed)
storeAccessToken(accessToken, authentication);
}
return accessToken;
}
@Override
public OAuth2Authentication readAuthentication(OAuth2AccessToken token) {
return readAuthentication(token.getValue());
}
@Override
public OAuth2Authentication readAuthentication(String token) {
byte[] bytes = null;
RedisConnection conn = getConnection();
try {
bytes = conn.get(serializeKey(AUTH + token));
} finally {
conn.close();
}
OAuth2Authentication auth = deserializeAuthentication(bytes);
return auth;
}
@Override
public OAuth2Authentication readAuthenticationForRefreshToken(OAuth2RefreshToken token) {
return readAuthenticationForRefreshToken(token.getValue());
}
public OAuth2Authentication readAuthenticationForRefreshToken(String token) {
RedisConnection conn = getConnection();
try {
byte[] bytes = conn.get(serializeKey(REFRESH_AUTH + token));
OAuth2Authentication auth = deserializeAuthentication(bytes);
return auth;
} finally {
conn.close();
}
}
@Override
public void storeAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
byte[] serializedAccessToken = serialize(token);
byte[] serializedAuth = serialize(authentication);
byte[] accessKey = serializeKey(ACCESS + token.getValue());
byte[] authKey = serializeKey(AUTH + token.getValue());
byte[] authToAccessKey = serializeKey(AUTH_TO_ACCESS + authenticationKeyGenerator.extractKey(authentication));
byte[] approvalKey = serializeKey(UNAME_TO_ACCESS + getApprovalKey(authentication));
byte[] clientId = serializeKey(CLIENT_ID_TO_ACCESS + authentication.getOAuth2Request().getClientId());
RedisConnection conn = getConnection();
try {
conn.openPipeline();
conn.stringCommands().set(accessKey, serializedAccessToken);
conn.stringCommands().set(authKey, serializedAuth);
conn.stringCommands().set(authToAccessKey, serializedAccessToken);
if (!authentication.isClientOnly()) {
conn.rPush(approvalKey, serializedAccessToken);
}
conn.rPush(clientId, serializedAccessToken);
if (token.getExpiration() != null) {
int seconds = token.getExpiresIn();
conn.expire(accessKey, seconds);
conn.expire(authKey, seconds);
conn.expire(authToAccessKey, seconds);
conn.expire(clientId, seconds);
conn.expire(approvalKey, seconds);
}
OAuth2RefreshToken refreshToken = token.getRefreshToken();
if (refreshToken != null && refreshToken.getValue() != null) {
byte[] refresh = serialize(token.getRefreshToken().getValue());
byte[] auth = serialize(token.getValue());
byte[] refreshToAccessKey = serializeKey(REFRESH_TO_ACCESS + token.getRefreshToken().getValue());
conn.stringCommands().set(refreshToAccessKey, auth);
byte[] accessToRefreshKey = serializeKey(ACCESS_TO_REFRESH + token.getValue());
conn.stringCommands().set(accessToRefreshKey, refresh);
if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
ExpiringOAuth2RefreshToken expiringRefreshToken = (ExpiringOAuth2RefreshToken) refreshToken;
Date expiration = expiringRefreshToken.getExpiration();
if (expiration != null) {
int seconds = Long.valueOf((expiration.getTime() - System.currentTimeMillis()) / 1000L)
.intValue();
conn.expire(refreshToAccessKey, seconds);
conn.expire(accessToRefreshKey, seconds);
}
}
}
conn.closePipeline();
} finally {
conn.close();
}
}
private static String getApprovalKey(OAuth2Authentication authentication) {
String userName = authentication.getUserAuthentication() == null ? ""
: authentication.getUserAuthentication().getName();
return getApprovalKey(authentication.getOAuth2Request().getClientId(), userName);
}
private static String getApprovalKey(String clientId, String userName) {
return clientId + (userName == null ? "" : ":" + userName);
}
@Override
public void removeAccessToken(OAuth2AccessToken accessToken) {
removeAccessToken(accessToken.getValue());
}
@Override
public OAuth2AccessToken readAccessToken(String tokenValue) {
byte[] key = serializeKey(ACCESS + tokenValue);
byte[] bytes = null;
RedisConnection conn = getConnection();
try {
bytes = conn.get(key);
} finally {
conn.close();
}
OAuth2AccessToken accessToken = deserializeAccessToken(bytes);
return accessToken;
}
public void removeAccessToken(String tokenValue) {
byte[] accessKey = serializeKey(ACCESS + tokenValue);
byte[] authKey = serializeKey(AUTH + tokenValue);
byte[] accessToRefreshKey = serializeKey(ACCESS_TO_REFRESH + tokenValue);
RedisConnection conn = getConnection();
try {
conn.openPipeline();
conn.get(accessKey);
conn.get(authKey);
conn.del(accessKey);
conn.del(accessToRefreshKey);
// Don't remove the refresh token - it's up to the caller to do that
conn.del(authKey);
List<Object> results = conn.closePipeline();
byte[] access = (byte[]) results.get(0);
byte[] auth = (byte[]) results.get(1);
OAuth2Authentication authentication = deserializeAuthentication(auth);
if (authentication != null) {
String key = authenticationKeyGenerator.extractKey(authentication);
byte[] authToAccessKey = serializeKey(AUTH_TO_ACCESS + key);
byte[] unameKey = serializeKey(UNAME_TO_ACCESS + getApprovalKey(authentication));
byte[] clientId = serializeKey(CLIENT_ID_TO_ACCESS + authentication.getOAuth2Request().getClientId());
conn.openPipeline();
conn.del(authToAccessKey);
conn.lRem(unameKey, 1, access);
conn.lRem(clientId, 1, access);
conn.del(serialize(ACCESS + key));
conn.closePipeline();
}
} finally {
conn.close();
}
}
@Override
public void storeRefreshToken(OAuth2RefreshToken refreshToken, OAuth2Authentication authentication) {
byte[] refreshKey = serializeKey(REFRESH + refreshToken.getValue());
byte[] refreshAuthKey = serializeKey(REFRESH_AUTH + refreshToken.getValue());
byte[] serializedRefreshToken = serialize(refreshToken);
RedisConnection conn = getConnection();
try {
conn.openPipeline();
conn.stringCommands().set(refreshKey, serializedRefreshToken);
conn.stringCommands().set(refreshAuthKey, serialize(authentication));
if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
ExpiringOAuth2RefreshToken expiringRefreshToken = (ExpiringOAuth2RefreshToken) refreshToken;
Date expiration = expiringRefreshToken.getExpiration();
if (expiration != null) {
int seconds = Long.valueOf((expiration.getTime() - System.currentTimeMillis()) / 1000L)
.intValue();
conn.expire(refreshKey, seconds);
conn.expire(refreshAuthKey, seconds);
}
}
conn.closePipeline();
} finally {
conn.close();
}
}
@Override
public OAuth2RefreshToken readRefreshToken(String tokenValue) {
byte[] key = serializeKey(REFRESH + tokenValue);
byte[] bytes = null;
RedisConnection conn = getConnection();
try {
bytes = conn.get(key);
} finally {
conn.close();
}
OAuth2RefreshToken refreshToken = deserializeRefreshToken(bytes);
return refreshToken;
}
@Override
public void removeRefreshToken(OAuth2RefreshToken refreshToken) {
removeRefreshToken(refreshToken.getValue());
}
public void removeRefreshToken(String tokenValue) {
byte[] refreshKey = serializeKey(REFRESH + tokenValue);
byte[] refreshAuthKey = serializeKey(REFRESH_AUTH + tokenValue);
byte[] refresh2AccessKey = serializeKey(REFRESH_TO_ACCESS + tokenValue);
byte[] access2RefreshKey = serializeKey(ACCESS_TO_REFRESH + tokenValue);
RedisConnection conn = getConnection();
try {
conn.openPipeline();
conn.del(refreshKey);
conn.del(refreshAuthKey);
conn.del(refresh2AccessKey);
conn.del(access2RefreshKey);
conn.closePipeline();
} finally {
conn.close();
}
}
@Override
public void removeAccessTokenUsingRefreshToken(OAuth2RefreshToken refreshToken) {
removeAccessTokenUsingRefreshToken(refreshToken.getValue());
}
private void removeAccessTokenUsingRefreshToken(String refreshToken) {
byte[] key = serializeKey(REFRESH_TO_ACCESS + refreshToken);
List<Object> results = null;
RedisConnection conn = getConnection();
try {
conn.openPipeline();
conn.get(key);
conn.del(key);
results = conn.closePipeline();
} finally {
conn.close();
}
if (results == null) {
return;
}
byte[] bytes = (byte[]) results.get(0);
String accessToken = deserializeString(bytes);
if (accessToken != null) {
removeAccessToken(accessToken);
}
}
@Override
public Collection<OAuth2AccessToken> findTokensByClientIdAndUserName(String clientId, String userName) {
byte[] approvalKey = serializeKey(UNAME_TO_ACCESS + getApprovalKey(clientId, userName));
List<byte[]> byteList = null;
RedisConnection conn = getConnection();
try {
byteList = conn.lRange(approvalKey, 0, -1);
} finally {
conn.close();
}
if (byteList == null || byteList.size() == 0) {
return ImmutableSet.<OAuth2AccessToken>of();
}
List<OAuth2AccessToken> accessTokens = new ArrayList<OAuth2AccessToken>(byteList.size());
for (byte[] bytes : byteList) {
OAuth2AccessToken accessToken = deserializeAccessToken(bytes);
accessTokens.add(accessToken);
}
return Collections.<OAuth2AccessToken> unmodifiableCollection(accessTokens);
}
@Override
public Collection<OAuth2AccessToken> findTokensByClientId(String clientId) {
byte[] key = serializeKey(CLIENT_ID_TO_ACCESS + clientId);
List<byte[]> byteList = null;
RedisConnection conn = getConnection();
try {
byteList = conn.lRange(key, 0, -1);
} finally {
conn.close();
}
if (byteList == null || byteList.size() == 0) {
return Collections.<OAuth2AccessToken> emptySet();
}
List<OAuth2AccessToken> accessTokens = new ArrayList<OAuth2AccessToken>(byteList.size());
for (byte[] bytes : byteList) {
OAuth2AccessToken accessToken = deserializeAccessToken(bytes);
accessTokens.add(accessToken);
}
return Collections.<OAuth2AccessToken> unmodifiableCollection(accessTokens);
}
}
6.自定义客户端认证
@Component
public class BaseClientDetailService implements ClientDetailsService {
private static final Logger log = LoggerFactory.getLogger(BaseClientDetailService.class);
@Override
public ClientDetails loadClientByClientId(String clientId) throws ClientRegistrationException {
log.info("接收到clientId:{}", clientId);
BaseClientDetails client = null;
// 真实开发从数据库进行查询client,scope 对应权限集
// TODO: 2018/12/19
//以下是测试使用
if ("client".equals(clientId)) {
client = new BaseClientDetails();
client.setClientId(clientId);
client.setClientSecret("{noop}123456");
//client.setResourceIds(Arrays.asList("order"));
client.setAuthorizedGrantTypes(Arrays.asList("authorization_code",
"client_credentials", "refresh_token", "password", "implicit"));
//不同的client可以通过 一个scope 对应 权限集 // client.setScope(Arrays.asList("all", "select")); // client.setAuthorities(AuthorityUtils.createAuthorityList("admin_role")); client.setAccessTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(1)); //1天 client.setRefreshTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(1)); //1天
}
// 不存在该client
if (client == null) {
throw new NoSuchClientException("No client width requested id: " + clientId);
}
log.info("客户端认证结束:{}", client.toString());
return client;
}
}
7.配置文件bootstrap.yml
spring:
application:
name: hsdfas-auth
profiles:
active: test
cloud:
config:
uri: http://xxxx:xx
fail-fast: true
username: xx
password: xx
retry:
initial-interval: 2000 #初始重试间隔时间,默认1000ms
max-interval: 10000 #最大间隔时间,默认2000ms
multiplier: 2 #间隔乘数,默认1.1
max-attempts: 10 #配置重试次数,默认为6
#Spring-cloud-bus 并且在需要更新的配置类上加@RefreshScope注解 访问http://你需要刷新的服务-ip:port/actuator/bus-refresh/进行刷新
#刷新部分微服务的配置,此时可通过/actuator/bus-refresh/{destination}端点的 destination(微服务的 ApplicationContext ID) 参数来定位要刷新的应用程序
bus:
trace:
enabled: true
enabled: true
# 暴露微服务健康信息
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
8.远程仓库配置文件
server:
port: 8096
spring:
application:
name: xxxx
zipkin: #链路追踪服务url
enabled: true
base-url: http://localhost:8101
redis: # REDIS (RedisProperties)
database: 0 # Redis数据库索引(默认为0)
host: localhost # Redis服务器地址
port: 6379 # Redis服务器连接端口
password: # Redis服务器连接密码(默认为空)
timeout: 3000 # 连接超时时间(毫秒)
jedis:
pool:
max-active: 200 # 连接池最大连接数(使用负值表示没有限制)默认 8
max-wait: 1000 # 连接池最大阻塞等待时间(使用负值表示没有限制)默认 -1
max-idle: 20 # 连接池中的最大空闲连接 默认 8
min-idle: 5 # 连接池中的最小空闲连接 默认 0
eureka:
client:
serviceUrl:
#defaultZone: http://localhost:8090/eureka
defaultZone: http://xx:xx@localhost:8090/eureka
instance:
instance-id: ${spring.cloud.client.ip-address}:${server.port}
prefer-ip-address: true
logging:
level:
per.lx: DEBUG
org.springframework.security: DEBUG
以上config eureka均开启了加密认证