SpringCloud2.0整合OAuth2.0
阿新 • • 發佈:2019-01-06
以下是把配置檔案放在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:[email protected]: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均開啟了加密認證