一、微服务是什么

由来

微服务架构风格是一种使用一套小服务来开发单个应用的方式途径,每个服务运行在自己的进程中,并使用轻量级机制通信,通常是 HTTP API,这些服务基于业务能力构建,并能够通过自动化部署机制来独立部署,这些服务使用不同的编程语言实现,以及不同数据存储技术,并保持最低限度的集中式管理。

微服务优势

  • 微服务每个模块就相当于一个单独的项目,代码量明显减少,遇到问题也相对来说比较好解决。
  • 微服务每个模块都可以使用不同的存储方式(比如有的用 redis,有的用 mysql等),数据库也是单个模块对应自己的数据库。
  • 微服务每个模块都可以使用不同的开发技术,开发模式更灵活

微服务本质

微服务,关键其实不仅仅是微服务本身,而是系统要提供一套基础的架构,这种架构使得微服务可以独立的部署、运行、升级,不仅如此,这个系统架构还让微服务与微服务之间在结构上“松耦合”,而在功能上则表现为一个统一的整体。这种所谓的“统一的整体”表现出来的是统一风格的界面,统一的权限管理,统一的安全策略,统一的上线过程,统一的日志和审计方法,统一的调度方式,统一的访问入口等等。

微服务的目的是有效的拆分应用,实现敏捷开发和部署

二、微服务认证与授权实现思路

认证授权过程分析

  • 基于 Session,那么 Spring-security 会对 cookie 里的 sessionid 进行解析,找到服务器存储的 session 信息,然后判断当前用户是否符合请求的要求。
  • 如果是 token,则是解析出 token,然后将当前请求加入到 Spring-security 管理的权限信息中去
image-20211128182639456

如果系统的模块众多,每个模块都需要进行授权与认证,所以我们选择基于 token 的形式进行授权与认证,用户根据用户名密码认证成功,然后获取当前用户角色的一系列权限值,并以用户名为 key,权限列表为 value 的形式存入 redis 缓存中,根据用户名相关信息生成 token 返回,浏览器将 token 记录到 cookie 中,每次调用 api 接口都默认将 token 携带到 header 请求头中,Spring-security 解析 header 头获取 token 信息,解析 token 获取当前用户名,根据用户名就可以从 redis 中获取权限列表,这样 Spring-security 就能够判断当前请求是否有权限访问

权限管理数据模型

image-20211128183700817

jwt 介绍

1、访问令牌的类型

image-20211128210203594

2、JWT 的组成

一个 JWT

1
2
eyJhbGci0iJIUzI1NiIsInR5cCI6IkpxVcJ9. eyJzdWIi0iIxMjMENTY30DkwIiwibmFtZSI6IkpvaG4gRG91IiwiaWFBIjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
JWT 头

JWT 头部分是一个描述 JWT 元数据的 JSON 对象

1
2
3
4
{
"alg": "HS256",
"typ": "JWT"
}

在上面的代码中,alg 属性表示签名使用的算法,默认为 HMAC SHA256(写为 HS256);typ 属性表示令牌的类型,JWT 令牌统一写为 JWT。最后,使用 Base64 URL 算法将上述JSON 对象转换为字符串保存。

有效载荷

有效载荷部分,是 JWT 的主体内容部分,也是一个 JSON 对象,包含需要传递的数据。 JWT指定七个默认字段供选择。

1
2
3
4
5
6
7
iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID 用于标识该 JWT

我们还可以自定义私有字段

1
2
3
4
5
{
"sub": "1234567890",
"name": "Helen",
"admin": true
}

请注意,默认情况下 JWT 是未加密的,任何人都可以解读其内容,因此不要构建隐私信息字段,存放保密信息,以防止信息泄露。

JSON 对象也使用 Base64 URL 算法转换为字符串保存。

签名哈希

签名哈希部分是对上面两部分数据签名,通过指定的算法生成哈希,以确保数据不会被篡改。
首先,需要指定一个密码(secret)。该密码仅仅为保存在服务器中,并且不能向用户公开。然后,使用标头中指定的签名算法(默认情况下为 HMAC SHA256)根据以下公式生成签名。

1
HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(claims), secret)

在计算出签名哈希后,JWT 头,有效载荷和签名哈希的三个部分组合成一个字符串,每个部分用”.”分隔,就构成整个 JWT 对象。

Base64URL 算法

JWT 头和有效载荷序列化的算法都用到了 Base64URL。该算法和常见 Base64 算
法类似,稍有差别。

作为令牌的 JWT 可以放在 URL 中(例如 api.example/?token=xxx)。 Base64 中用的三个字符是“+”,”/“和”=”,由于在 URL 中有特殊含义,因此 Base64URL 中对他们做了替换:“=”去掉,”+”用”-“替换,”/“用”_”替换,这就是 Base64URL 算法。

三、项目创建

1.项目工程划分

1
2
3
4
5
6
7
8
9
1. acl_parent 管理依赖
2.
2.1 common
service_base 工具类
spring_security 权限配置
2.2 infrastructure
api_gateway: 网关
2.3 service
service_acl 权限管理微服务模块

image-20211212153916188

2.依赖

  1. redis
  2. nacos: 注册中心,将网关服务和权限管理服务注册

3.common的编写

service_base

1
2
3
4
5
6
7
8
9
10
11
12
1. exceptionhandler
GlobalExceptionHandler:全局异常处理
GuliException: 自定义异常
2. handler
MyMetaObjectHandler mybatisplus
3. utils
MD5 :md5加密
R: 统一返回对象
responseUtil: 返回工具类

4. RedisConfig: redis配置类
5. SwaggerConfig: swagger配置类

spring_security

  1. 工具类
1
2
3
4
5
security
DefaultPasswordEncoder 密码处理
TokenLogoutHandler 退出处理器
TokenManager token生成工具类
UnauthorizedEntryPoint 未授权统一处理类
  1. filter
1
2
3
filter
TokenAuthenticationFilter 授权过滤
TokenLoginFilter 认证过滤器
  1. 核心配置类
1
2
config
TokenWebSecurityConfig 核心配置类
1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling()
.authenticationEntryPoint(new UnauthorizedEntryPoint()) //没有权限访问的
.and().csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and().logout().logoutUrl("/admin/acl/index/logout")
.addLogoutHandler(new TokenLogoutHandler(tokenManager, redisTemplate))
.and()
.addFilter(new TokenLoginFilter(tokenManager, redisTemplate, authenticationManager()))
.addFilter(new TokenAuthFilter(tokenManager, redisTemplate ,authenticationManager()));
}

gateway

解决跨域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Configuration
public class CorsConfig {

//解决跨域
@Bean
public CorsWebFilter corsWebFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedMethod("*");
config.addAllowedOrigin("*");
config.addAllowedHeader("*");

UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
source.registerCorsConfiguration("/**",config);

return new CorsWebFilter(source);
}
}

service_acl

查询用户和权限列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {

@Autowired
private UserService userService;

@Autowired
private PermissionService permissionService;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//根据用户名查询数据
User user = userService.selectByUsername(username);
//判断
if(user == null) {
throw new UsernameNotFoundException("用户不存在");
}
com.lq.security.entity.User curUser = new com.lq.security.entity.User();
BeanUtils.copyProperties(user,curUser);

//根据用户查询用户权限列表
List<String> permissionValueList = permissionService.selectPermissionValueByUserId(user.getId());
SecurityUser securityUser = new SecurityUser();
securityUser.setCurrentUserInfo(curUser);
securityUser.setPermissionValueList(permissionValueList);
return securityUser;
}
}