在此感谢原作者的分享。https://blog.csdn.net/w1054993544/article/details/78932614
1.架构图
技术团队通过一段时间的积累后,我们打算对往后的一些新项目采用Spring Cloud技术栈来实现。大概微服务的架构如下:
2.Eureka服务注册与发现
•服务注册中心:服务的注册与发现。
3.OAUTH2认证服务器
我这里采用Security-OAuth2.0 密码模式实现
3.1 oauth2 server 配置
我采取了数据库和redis两种方式来存储token,可以方便切换,生成环境下建议使用redis方式。
3.1.1AuthorizationServerConfig
package com.microservice.skeleton.auth.config;
import com.microservice.skeleton.auth.error.MssWebResponseExceptionTranslator;
import com.microservice.skeleton.auth.service.impl.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
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.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
import javax.sql.DataSource;
/**
* Created by karp
* Time:11:02
* ProjectName:Mirco-Service-Skeleton
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private DataSource dataSource;
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Bean
RedisTokenStore redisTokenStore(){
return new RedisTokenStore(redisConnectionFactory);
}
//token存储数据库
// @Bean
// public JdbcTokenStore jdbcTokenStore(){
// return new JdbcTokenStore(dataSource);
// }
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(clientDetails());
}
@Bean
public ClientDetailsService clientDetails() {
return new JdbcClientDetailsService(dataSource);
}
@Bean
public WebResponseExceptionTranslator webResponseExceptionTranslator(){
return new MssWebResponseExceptionTranslator();
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
endpoints.tokenStore(redisTokenStore())
.userDetailsService(userDetailsService)
.authenticationManager(authenticationManager);
endpoints.tokenServices(defaultTokenServices());
endpoints.exceptionTranslator(webResponseExceptionTranslator());//认证异常翻译
}
/**
* 注意,自定义TokenServices的时候,需要设置@Primary,否则报错,
* @return
*/
@Primary
@Bean
public DefaultTokenServices defaultTokenServices(){
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(redisTokenStore());
tokenServices.setSupportRefreshToken(true);
tokenServices.setClientDetailsService(clientDetails());
tokenServices.setAccessTokenValiditySeconds(60*60*12); // token有效期自定义设置,默认12小时
tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7);//默认30天,这里修改
return tokenServices;
}
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll()");
security .checkTokenAccess("isAuthenticated()");
security.allowFormAuthenticationForClients();
}
}
3.1.2WebSecurityConfig:
package com.microservice.skeleton.auth.config;
import com.microservice.skeleton.auth.service.impl.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* Created by karp
* Time:16:42
* ProjectName:Mirco-Service-Skeleton
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().fullyAuthenticated()
.antMatchers("/oauth/token").permitAll()
.and()
.csrf().disable();
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**", "/js/**", "/plugins/**", "/favicon.ico");
}
}
permitAll() 很关键:其中antMatchers("/oauth/token").permitAll() 给获取token请求授权。
3.2 UserDetailsServiceImpl实现
package com.microservice.skeleton.auth.service.impl;
import ch.qos.logback.core.net.SyslogOutputStream;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.microservice.skeleton.auth.service.PermissionService;
import com.microservice.skeleton.auth.service.RoleService;
import com.microservice.skeleton.auth.service.UserService;
import com.microservice.skeleton.common.util.StatusCode;
import com.microservice.skeleton.common.vo.MenuVo;
import com.microservice.skeleton.common.vo.Result;
import com.microservice.skeleton.common.vo.RoleVo;
import com.microservice.skeleton.common.vo.UserVo;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Created by karp
* Time:16:37
* ProjectName:Mirco-Service-Skeleton
*/
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserService userService;
@Autowired
private RoleService roleService;
@Autowired
private PermissionService permissionService;
private ObjectMapper objectMapper = new ObjectMapper();
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Result userResult = userService.findByUsername(username);
if (userResult.getCode() != StatusCode.SUCCESS_CODE) {
throw new UsernameNotFoundException("用户:" + username + ",不存在!");
}
Set grantedAuthorities = new HashSet<>();
boolean enabled = true; // 可用性 :true:可用 false:不可用
boolean accountNonExpired = true; // 过期性 :true:没过期 false:过期
boolean credentialsNonExpired = true; // 有效性 :true:凭证有效 false:凭证无效
boolean accountNonLocked = true; // 锁定性 :true:未锁定 false:已锁定
UserVo userVo = new UserVo();
BeanUtils.copyProperties(userResult.getData(),userVo);
Result> roleResult = roleService.getRoleByUserId(userVo.getId());
if (roleResult.getCode() == StatusCode.SUCCESS_CODE){
List roleVoList = roleResult.getData();
for (RoleVo role:roleVoList){
//角色必须是ROLE_开头,可以在数据库中设置
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_"+role.getValue());
System.out.println("grantedAuthority---角色------->ROLE_"+role.getValue());
grantedAuthorities.add(grantedAuthority);
//获取权限
Result> perResult = permissionService.getRolePermission(role.getId());
if (perResult.getCode() == StatusCode.SUCCESS_CODE){
List permissionList = perResult.getData();
for (MenuVo menu:permissionList
) {
GrantedAuthority authority = new SimpleGrantedAuthority(menu.getUrl());
grantedAuthorities.add(authority);
System.out.println("grantedAuthority---菜单------->"+menu.getUrl());
}
}
}
}
User user = new User(userVo.getUsername(), userVo.getPassword(),
enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, grantedAuthorities);
return user;
}
}
3.3 mss-oauth yml文件配置
server:
port: 9060
spring:
application:
name: authcenter
jpa:
show-sql: true
datasource:
url: jdbc:mysql://localhost:4306/nsinformation?useUnicode=true&characterEncoding=utf-8
username: nsinformation
password: ***
druid:
driver-class-name: com.mysql.jdbc.Driver
redis:
host: ***
port: 6489
password: ***
eureka:
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${server.port}
##续约更新时间间隔设置5秒,m默认30s
lease-renewal-interval-in-seconds: 1
##续约到期时间10秒,默认是90秒
lease-expiration-duration-in-seconds: 2
client:
service-url:
defaultZone: http://mss-eureka:9010/eureka/ #目前是单节点,多节点可以在后面追加
logging:
config: classpath:logback.xml
level:
org:
springframework:
web: info
###feign 默认关闭熔断,请看HystrixFeignConfiguration
feign:
hystrix:
enabled: false
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 60000
ribbon:
ReadTimeout: 30000
ConnectTimeout: 60000
MaxAutoRetries: 0
MaxAutoRetriesNextServer: 1
ActiveConnectionsLimit: 2147483647
4.mss-config配置中心
4.1配置中心serve端
server.port = 8001
server.tomcat.uri-encoding = UTF-8
spring.application.name=configServer
spring.profiles.active=subversion
spring.cloud.config.server.svn.uri=http://***:1080/AgriculturalWaterInformatization/trunk/Micro-Service-Skeleton/configfiletest/
spring.cloud.config.server.svn.username=***
spring.cloud.config.server.svn.password=***
spring.cloud.config.server.default-label={application}
spring.cloud.config.enabled=true
4.2配置中心客户端
5.Zuul网关(mss-gateway)
5.1开启支持Sso
@Configuration
@EnableOAuth2Sso
public class SecurityConfig extends WebSecurityConfigurerAdapter{
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
}
}
5.2配置文件
网关把/uaa/**请求都转发到oauth2.0中心,oauth2.0中心对所有的请求需要认证授权,如果不需要授权,那么在WebSecurityConfig设置permitall就可以了。
spring:
application:
name: mss-gateway
server:
port: 9030
eureka:
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${server.port}
lease-renewal-interval-in-seconds: 1 # 心跳检测检测与续约时间
lease-expiration-duration-in-seconds: 2 # 测试时将值设置设置小些,保证服务关闭后注册中心能及时踢出服
client:
service-url:
defaultZone: http://mss-eureka:9010/eureka/
zuul:
host:
connect-timeout-millis: 10000
socket-timeout-millis: 60000
routes:
uaa:
path: /uaa/**
strip-prefix: true
sensitiveHeaders:
serviceId: authcenter
upms:
path: /upms/**
strip-prefix: true
sensitiveHeaders:
serviceId: mss-upms
management:
security:
enabled: false
security:
oauth2:
client:
access-token-uri: http://localhost:9030/uaa/oauth/token #网关的地址
user-authorization-uri: http://localhost:9030/uaa/oauth/authorize
resource:
user-info-uri: http://localhost:9060/user #安全策略里获取用户信息。
prefer-token-info: false
# resource:
# user-info-uri: http://localhost:9060/upms/user
# prefer-token-info: false
##############end#####################
####超时配置####
ribbon:
ReadTimeout: 20000
ConnectTimeout: 20000
MaxAutoRetries: 1
MaxAutoRetriesNextServer: 2
ActiveConnectionsLimit: 2147483647
eureka:
enabled: true
hystrix:
command:
default:
execution:
timeout:
enabled: true
isolation:
thread:
timeoutInMilliseconds: 600000
###超时配置###
6.mss-transaction 消息总线
这里采用rabbitmq来实现,配置文件如下:
server:
port: 9070
spring:
application:
name: mss-transaction
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://***:4306/nsinformation?useUnicode=true&characterEncoding=utf-8
username: nsinformation
password: ***
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
publisher-confirms: true
virtual-host: /
eureka:
instance:
prefer-ip-address: true
instance-id: ${spring.cloud.client.ip-address}:${server.port}
##续约更新时间间隔设置5秒,m默认30s
lease-renewal-interval-in-seconds: 1
##续约到期时间10秒,默认是90秒
lease-expiration-duration-in-seconds: 2
client:
service-url:
defaultZone: http://mss-eureka:9010/eureka/
7.展示
7.1分别启动
register、auth-center、mss-gateway、mss-upms
7.2获取token
http://localhost:9030/uaa/oauth/token
用postman 设置basic Auth(可在数据库oauth_client_details表中设置)
请求参数为:
grant_type:password
username:admin
password:123456
7.3刷新token
7.4获取资源(调用其他接口)
http://localhost:9030/upms/user/test**](http://localhost:9030/upms/user/test)
注意:****heard****头部添加:****Authorization 值:bearer +token
总结
由于刚开始学习spring cloud以及对spring security不太了解,对oauth2.0规范也进行了查缺补漏,基本框架已经搭建完毕。
源码地址
源码地址
源码地址