不同的微服务一般会有不同的网络地址,而外部客户端可能需要调用多个服务的接口才能完成一个业务需求,如果让客户端直接与各个微服务通信,会有以下的问题:
真实架构
注解@CrossOrgin
写过滤器
网关跨域
spring:
cloud:
gateway:
globalcors:
cors-configurations:
‘[/**]’
allow-credentials: true 允许携带令牌信息
allowed-origins: “"
allowed-headers: "”
allowed-methods: “*”
max-age: 3600
网关可以进行限流,当我们的系统被频繁地请求的时候,就有可能将系统压垮,所以为了解决这个问题,需要在每一个微服务中做限流操作,但是如果有了网关,就可以解决这个问题。
两种算法的区别:两者主要区别在于漏桶算法
能够强行限制数据的传输速率,而令牌桶算法
在能够限制数据的平均传输速率外,还允许某种程度的突发传输。在令牌桶算法
中,只要令牌桶中存在令牌,那么就允许突发地传输数据直到到达用户配置的门限,所以它适合于具有突发性的流量
spring cloud gateway默认使用Redis的RateLimter
限流算法来实现,所以我们要使用首先需要引入Redis的依赖
//根据IP限流
@SpringBootApplication
public class GatewayApplication{
public static void main(String[] args){
SpringApplication.run(GatewayApplication.class, args);
}
@Bean(name = "ipKeyResolver")
public KeyResolver userKeyResolver(){
return new KeyResolver(){
@Override
public Mono<String> resolve(ServerWebExchange exchange){
//获取客户端IP
String hostName = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
sout("hostName: " + hostName);
return Mono.just(hostName);
}
};
}
}
RBAC(基于角色的权限控制 role base access control)是一种设计模式,是用来设计和管理权限相关数据的一种模型,一般要求用户和角色、角色和权限是多对多的关联。具体表结构:
// 保存角色时先删除角色用户的关联表,再逐一添加用户的关联
//controller层
@ApiOperation(value="保存", notes="ID存在修改,不存在添加")
@PostMapping("/save")
public ResponseBean save(T entity) throws Exception {
ResponseBean rm = new ResponseBean();
try{
beforeSave(entity);//保存前处理实体类
service.saveOrUpdate(entity);
rm.setModel(entity);
}catch(Exception e){
e.printStackTrace();
rm.setSuccess(false);
rm.setMsg("保存失败");
}
return rm;
}
//service实现类重写
@Override
@Transactional(readOnly = false)
public boolean saveOrUpdate(Role entity){
RoleDao dao = ((RoleDao) getBaseMapper());
boolean result = super.saveOrUpdate(entity);
dao.deleteUserByRole(entity.getId());//删除角色的用户
//添加角色和权限关系
Role role (Role) entity;
//添加用户和角色关系
if(null != role.getUserIds()){
for(Long userId : role.getUserIds()){
dao.insertUserAndRole(userId, entity.getId());
}
}
return result;
}
单点登录(SSO Sigle Sign On):在多个应用系统中,只需要登录一次,就可以访问其他相互信任的应用系统。
我们在浏览器(Browser)中访问一个应用,这个应用需要登陆,我们填写完用户名和密码后,完成登录认证。这时我们在这个用户的session中标记登录状态为yes(已登录),同时在浏览器(Browser)中写入Cookie是这个用户的唯一标识。下次我们再访问这个应用的时候,请求会带上这个Cookie,服务端会根据这个Cookie找到对应的session,通过session来判断这个用户是否登录。如果不做特殊配置,这个Cookie的名字叫做jsessionid,值在服务端(server)是唯一的(类比去银行办业务)
JSON Web Token(JWT)是一个非常轻巧的规范。这个规范允许我们使用JWT在用户和服务器之间传递安全可靠的信息。一个JWT实际上就是一个字符串,它由三部分组成:头部(Header)、载荷(playload)与签名(signature)
{“type”:“jwt”,“alg”:“HS256”}
载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分
标准中注册的时间(建议但不强制使用)
iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而避免重放攻击
公共的声明:公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息,但不建议添加敏感信息,因为该部分在客户端可解密
私有的声明:私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base是对称解密的,意味着该部分信息可以归类为明文信息。
签名:jwt的第三部分是一个签证信息,这个签证信息由三部分组成:header(base64后的)、payload(base64后的)、secret。这个部分需要base64加密后的header和base64加密后的payload使用,连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。
注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,他就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret,那就意味着客户端是可以自我签发jwt的了
鉴权处理
我们颁发的令牌,会携带用户信息,包括角色,这些用户信息,需要使用RBAC微服务查询获得,然后通过JWT令牌载荷存储,所以这里我们按照spring security规范实现自定义的UserDetailService,获取用户角色权限RBAC数据,存储到令牌
使用自定义的UserDetailService访问RBAC
/**
* @author William
* @create 2022-05-12 20:16
*/
@FeignClient(name = "security-service", fallback = UserClient.UseerClientFallback.class)
public interface UserClient extends UserApi {
@Component
@RequestMapping("/fallback")//这个可以避免容器中requestMapping重复
class UseerClientFallback implements UserClient {
private static final Logger LOGGER = LoggerFactory.getLogger(UseerClientFallback.class);
@Override
public User getByUserName(String userName) {
LOGGER.info("异常发生,进入fallback方法");
return null;
}
@Override
public List<Role> selectRolesByUserId(Long id) {
LOGGER.info("异常发生,进入fallback方法");
return null;
}
}
}
/**
* 自定义UserDetailService
* @author William
* @create 2022-05-12 20:30
*/
@Service
public class UserDetailServiceImpl implements UserDetailsService {
private static final Logger LOGGER = LoggerFactory.getLogger(UserDetailServiceImpl.class);
@Autowired
private UserClient userClient;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//通过feign调用用户微服务
User user = userClient.getByUserName(username);
List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
if(user != null){
LOGGER.debug("current user = " + user);
//获取用户的权限(角色)
List<Role> roles = userClient.selectRolesByUserId(user.getId());
for(Role role : roles){
if(role != null && role.getName() != null){
//spring security要求权限名称ROLE_ADMIN ROLE_MANAGER,有前缀"ROLE_
GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_" + role.getName());
grantedAuthorities.add(grantedAuthority);
}
}
}
return new org.springframework.security.core.userdetails.User(user.getUserName(), user.getPassword(), grantedAuthorities);
}
}
Oauth2-授权服务器配置(代码看PPT)
非对称加密算法需要两个密钥:公钥和私钥。公钥与私钥是一对,如果用公钥对数据进行加密,只有用对应的私钥才能解密。因为加密和解密使用的是两个不同的密钥,所以叫作非对称加密算法。非等称加密算法实现机密信息交换的基本过程是:
上图业务流程如下:(重要)
生成公钥私钥:我们采用JWT私钥颁发令牌,公钥校验令牌,这里先使用keytool工具生成公钥私钥证书
生成密钥证书 下边命令生成密钥证书,采用RSA算法每个证书包含公钥和私钥
创建一个文件夹,在该文件夹下执行如下命令行:
keytool -genkeypair -alias kaikeba -keyalg RSA -keypass kaikeba -keystore kaikeka.jks - storepass kaikeba
keytool是一个java提供的证书管理工具
-alias:密钥的别名
-keyalg: 使用的hash算法
-keypass: 密钥的访问密码
-keystore: 密钥库文件名, xc.keystore保存生成了的证书
-storepass: 密钥库的访问资料
查询证书信息
keytool -list -keystore kaikeba.jks
删除别名
keytool -delete -alias kaikeba -keystore kaikeba.jks
导出公钥
openssl是一个加解密工具包,这里使用openssl来导出公钥信息
再去测试私钥生成令牌,公钥校验令牌
**资源服务器配置:**基本上所有微服务都是资源服务,授权服务器采用私钥加密生成令牌,对外向资源服务器提供公钥,资源服务器使用公钥来校验令牌的合法性。让公钥拷贝到public.key文件中,将此文件拷贝到每一个需要的资源服务工程的classpath下,例如RBAC微服务
登录流程:(重要)