Spring Security OAuth2.0认证授权四:分布式系统认证授权
Spring Security OAuth2.0认证授权系列文章
Spring Security OAuth2.0认证授权一:框架搭建和认证测试
Spring Security OAuth2.0认证授权二:搭建资源服务
Spring Security OAuth2.0认证授权三:使用JWT令牌
前面几篇文章讲解了如何从头开始搭建认证服务和资源服务,从颁发普通令牌到颁发jwt令牌,最终完成了jwt令牌的颁发和校验。本篇文章将会讲解分布式环境下如何进行认证和授权。
一、设计思路

一般来说,一个典型的分布式系统架构如上图所示,这里进行一个简单的设计,来完成分布式系统下的认证和授权。
整体设计思路是使用OAuth2.0颁发令牌,使用JWT对令牌签名并颁发JWT令牌给客户端。既然决定使用JWT令牌了,则不需要再调用认证服务器对令牌进行验证了,因为JWT本身就包含了所需要的信息,而且只要验签成功,则可认为令牌可信任且有效。
如上所述,则可以如此设计:
- 用户请求登陆之后认证服务颁发令牌给用户,浏览器将令牌储存下来。
- 浏览器请求资源的的时候携带着令牌,网关拦截请求对令牌验证,验证的方法很简单,不请求认证服务而是直接使用密钥(对称或非对称)验签,只要验证成功则将jwt payload中的信息解析成明文放到请求头中转发请求到资源服务。
- 资源服务拿到明文信息,根据明文信息中的权限信息验证是否有权限访问该资源,有权限则返回资源信息,无权限则返回401。
综上,整体思路就是网关认证,资源服务鉴权。
典型的微服务架构下会有注册中心、网关等服务,接下来会依次介绍和搭建相关服务。
二、注册中心搭建
为了方便程序本地调试方便,这里使用eureka server作为服务注册中心,使用起来也非常简单
1.添加maven依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
2.新建启动类
@SpringBootApplication
@EnableEurekaServer
public class RegisterServer {
public static void main(String[] args) {
SpringApplication.run(RegisterServer.class,args);
}
}
3.新建配置文件
spring:
application:
name: register-server
server:
port: 8765 #启动端口
eureka:
server:
enable-self-preservation: false #关闭服务器自我保护,客户端心跳检测15分钟内错误达到80%服务会保护,导致别人还认为是好用的服务
eviction-interval-timer-in-ms: 10000 #清理间隔(单位毫秒,默认是60*1000)5秒将客户端剔除的服务在服务注册列表中剔除#
shouldUseReadOnlyResponseCache: true #eureka是CAP理论种基于AP策略,为了保证强一致性关闭此切换CP 默认不关闭 false关闭
client:
register-with-eureka: false #false:不作为一个客户端注册到注册中心
fetch-registry: false #为true时,可以启动,但报异常:Cannot execute request on any known server
instance-info-replication-interval-seconds: 10
serviceUrl:
defaultZone: http://localhost:${server.port}/eureka/
instance:
hostname: ${spring.cloud.client.ip-address}
prefer-ip-address: true
instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${spring.application.instance_id:${server.port}}
然后启动启动类,访问浏览器,http://127.0.0.1:8765,出现如下页面即表示已经成功

二、网关搭建
这里选用spring cloud gateway作为网关(不是zuul)
1.添加maven依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--gateway 依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
<version>2.2.5.RELEASE</version>
</dependency>
<!--actuator 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- jwt依赖 -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
</dependency>
</dependencies>
2.新建启动类
@SpringBootApplication
public class GatewayServer {
public static void main(String[] args) {
SpringApplication.run(GatewayServer.class, args);
}
}
3.新建配置文件
server:
port: 8761
spring:
cloud:
gateway:
routes:
- id: resource_server
uri: "lb://resource-server"
predicates:
- Path=/r**
application:
name: gateway-server
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8765/eureka
instance:
prefer-ip-address: true
instance-id: ${spring.application.name}:${spring.cloud.client.ip‐address}:${spring.application.instance_id:${server.port}}
如此,一个网关就已经搭建好了,但是还不具备我们想要的认证功能。
4.添加token全局过滤器
知识点有以下几点:
- 全局过滤器要实现GlobalFilter接口
- 为了实现token过滤器最先被调用,要实现Order接口并将优先级调到最大
- 使用JwtHelper工具类对jwt验签,签名的key必须和认证中心中配置的key保持一致
- 验签成功后将jwt中payload明文信息放到token-info的header值中传递给目标服务
实现代码如下:
@Component
@Slf4j
public class TokenFilter implements GlobalFilter, Ordered {
private static final String BEAR_HEADER = "Bearer ";
/**
* 该值要和auth-server中配置的签名相同
*
* com.kdyzm.spring.security.auth.center.config.TokenConfig#SIGNING_KEY
*/
private static final String SIGNING_KEY = "auth123";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String token = exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
//如果没有token,则直接返回401
if(StringUtils.isEmpty(token)){
return unAuthorized(exchange);
}
//验签并获取PayLoad
String payLoad;
try {
Jwt jwt = JwtHelper.decodeAndVerify(token.replace(BEAR_HEADER,""), new MacSigner(SIGNING_KEY));
payLoad = jwt.getClaims();
} catch (Exception e) {
log.error("验签失败",e);
return unAuthorized(exchange);
}
//将PayLoad数据放到header
ServerHttpRequest.Builder builder = exchange.getRequest().mutate();
builder.header("token-info", payLoad).build();
//继续执行
return chain.filter(exchange.mutate().request(builder.build()).build());
}
private Mono<Void> unAuthorized(ServerWebExchange exchange){
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
/**
* 将该过滤器的优先级设置为最高,因为只要认证不通过,就不能做任何事情
*
* @return
*/
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}
三、资源服务修改
原来资源服务已经集成了OAuth2.0、Spring Security、JWT等组件,根据现在的设计方案,需要删除OAuth2.0和JWT组件,只留下Spring Security组件。
1.移除OAuth2.0、JWT组件
这里要删除maven依赖,同时将相关配置删除
第一步,删除maven依赖,直接将以下两个依赖移除就好
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
</dependency>
第二步,删除相关配置
将ResouceServerConfig、TokenConfig两个类直接删除 即可。
2.添加过滤器
这里需要使用过滤器做,首先写一个过滤器,实现OncePerRequestFilter接口,该过滤器的作用就是获取网关传过来的token-info明文数据,封装成JwtTokenInfo对象,并将该相关信息添加到SpringSecurity上下文以备之后的鉴权使用。
代码实现如下:
@Component
@Slf4j
public class AuthFilterCustom extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
ObjectMapper objectMapper = new ObjectMapper();
String tokenInfo=request.getHeader("token-info");
if(StringUtils.isEmpty(tokenInfo)){
log.info("未找到token信息");
filterChain.doFilter(request,response);
return;
}
JwtTokenInfo jwtTokenInfo = objectMapper.readValue(tokenInfo, JwtTokenInfo.class);
log.info("tokenInfo={}",objectMapper.writeValueAsString(jwtTokenInfo));
List<String> authorities1 = jwtTokenInfo.getAuthorities();
String[] authorities=new String[authorities1.size()];
authorities1.toArray(authorities);
//将用户信息和权限填充 到用户身份token对象中
UsernamePasswordAuthenticationToken authenticationToken
= new UsernamePasswordAuthenticationToken(jwtTokenInfo.getUser_name(),null, AuthorityUtils.createAuthorityList(authorities));
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
//将authenticationToken填充到安全上下文
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request,response);
}
}
3.将过滤器注册到过滤器链
修改WebSecurityConfig类,使用如下方法注册过滤器:
.addFilterAfter(authFilterCustom, BasicAuthenticationFilter.class)//添加过滤器
同时,一定要关闭session功能,否则会出现上下文缓存问题
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);//禁用session
完整代码如下:
@Autowired
private AuthFilterCustom authFilterCustom;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf()
.disable()
.authorizeRequests()
// .antMatchers("/r/r1").hasAuthority("p2")
// .antMatchers("/r/r2").hasAuthority("p2")
.antMatchers("/**").authenticated()//所有的请求必须认证通过
.anyRequest().permitAll()//其它所有请求都可以随意访问
.and()
.addFilterAfter(authFilterCustom, BasicAuthenticationFilter.class)//添加过滤器
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);//禁用session
}
四、其他注意事项
认证服务auth-server以及资源服务resource-server、网关服务gateway-server都要集成eureka client组件
五、测试
测试前需要将各个服务依次启动起来:
- 启动注册中心 register-server:https://gitee.com/kdyzm/spring-security-oauth-study/tree/v5.0.0/register-server
- 启动网关 gateway-server:https://gitee.com/kdyzm/spring-security-oauth-study/tree/v5.0.0/gateway-server
- 启动认证服务 auth-server:https://gitee.com/kdyzm/spring-security-oauth-study/tree/v5.0.0/auth-server
- 启动资源服务 resource-server:https://gitee.com/kdyzm/spring-security-oauth-study/tree/v5.0.0/resource-server
第一步,获取token
这里使用password模式直接获取token,POST请求如下接口:
即可获取token。
第二步,访问资源
通过网关请求资源服务的r1接口,GET请求如下接口:
需要带上Header,key为Authorization,value格式如下:
Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicmVzMSJdLCJ1c2VyX25hbWUiOiJ6aGFuZ3NhbiIsInNjb3BlIjpbIlJPTEVfQURNSU4iLCJST0xFX1VTRVIiLCJST0xFX0FQSSJdLCJleHAiOjE2MTAzNzI5MzUsImF1dGhvcml0aWVzIjpbInAxIiwicDIiXSwianRpIjoiOWQzMzRmZGMtOTcwZC00YmJkLWI2MmMtZDU4MDZkNTgzM2YwIiwiY2xpZW50X2lkIjoiYzEifQ.gZraRNeX-o_jKiH7XQgg3TlUQBpxUcXa2-qR_Treu8U
如果相应结果如下,则表示测试通过
访问资源r1
否则,会返回401状态码。
六、项目源代码
项目源代码:https://gitee.com/kdyzm/spring-security-oauth-study/tree/v5.0.0
我的博客原文地址:https://blog.kdyzm.cn/post/30
Spring Security OAuth2.0认证授权四:分布式系统认证授权的更多相关文章
- Spring Security OAuth2.0认证授权五:用户信息扩展到jwt
历史文章 Spring Security OAuth2.0认证授权一:框架搭建和认证测试 Spring Security OAuth2.0认证授权二:搭建资源服务 Spring Security OA ...
- Spring Security OAuth2.0认证授权六:前后端分离下的登录授权
历史文章 Spring Security OAuth2.0认证授权一:框架搭建和认证测试 Spring Security OAuth2.0认证授权二:搭建资源服务 Spring Security OA ...
- Spring Security OAuth2.0认证授权二:搭建资源服务
在上一篇文章[Spring Security OAuth2.0认证授权一:框架搭建和认证测试](https://www.cnblogs.com/kuangdaoyizhimei/p/14250374. ...
- Spring Security OAuth2.0认证授权三:使用JWT令牌
Spring Security OAuth2.0系列文章: Spring Security OAuth2.0认证授权一:框架搭建和认证测试 Spring Security OAuth2.0认证授权二: ...
- springboot+spring security +oauth2.0 demo搭建(password模式)(认证授权端与资源服务端分离的形式)
项目security_simple(认证授权项目) 1.新建springboot项目 这儿选择springboot版本我选择的是2.0.6 点击finish后完成项目的创建 2.引入maven依赖 ...
- 【Spring Cloud & Alibaba 实战 | 总结篇】Spring Cloud Gateway + Spring Security OAuth2 + JWT 实现微服务统一认证授权和鉴权
一. 前言 hi,大家好~ 好久没更文了,期间主要致力于项目的功能升级和问题修复中,经过一年时间的打磨,[有来]终于迎来v2.0版本,相较于v1.x版本主要完善了OAuth2认证授权.鉴权的逻辑,结合 ...
- 基于spring boot2.0+spring security +oauth2.0+ jwt微服务架构
github地址:https://github.com/hankuikuide/microservice-spring-security-oauth2 项目介绍 该项目是一个演示项目,主要演示了,基于 ...
- Spring Security OAuth2.0 - AuthorizationServer和ResourceServer分离
<Spring Security实现OAuth2.0授权服务 - 基础版>和<Spring Security实现OAuth2.0授权服务 - 进阶版>两篇文章中介绍如何搭建OA ...
- 【OAuth2.0】Spring Security OAuth2.0篇之初识
不吐不快 因为项目需求开始接触OAuth2.0授权协议.断断续续接触了有两周左右的时间.不得不吐槽的,依然是自己的学习习惯问题,总是着急想了解一切,习惯性地钻牛角尖去理解小的细节,而不是从宏观上去掌握 ...
随机推荐
- 再也不怕 JavaScript 报错了,怎么看怎么处理都在这
在开发中,有时,我们花了几个小时写的 JS 代码,在游览器调试一看,控制台一堆红,瞬间一万头草泥马奔腾而来.至此,本文主要记录 JS 常见的一些报错类型,以及常见的报错信息,分析其报错原因,并给予处理 ...
- 7、Spring Cloud Hystrix
1.Spring Cloud Hystrix简介 (1).分布式问题 复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败. 多个微服务之间调用的时候,假设微服务A调 ...
- nginx配置访问本地资源
参考博客:https://www.cnblogs.com/xy51/p/9973326.html 需要访问路径:http://IP:10013/p1upgrade/picfiles/image73b4 ...
- STL——容器(Set & multiset)的概念和特点
1. Set 和 multiset 的概念 set 和 multiset 是一个集合容器,其中 set 所包含的元素是唯一的,集合中的元素按一定的顺序排列.set 采用红黑树变体的数据结构实现,红黑树 ...
- react项目中的一些配置
react中事件优化使用babel插件 npm install babel-plugin-react-scope-binding --save-dev react中绝对路径引入文件:在根目录下增加js ...
- 跨站点脚本编制 - SpringBoot配置XSS过滤器(基于Jsoup)
1. 跨站点脚本编制 风险:可能会窃取或操纵客户会话和 cookie,它们可能用于模仿合法用户,从而使黑客能够以该用户身份查看或变更用户记录以及执行事务. 原因:未对用户输入正确执行危险字符清 ...
- 【JAVA基础】静态方法
/* * 与Python的静态单例比较: * 一样是需要加上static方法才可以直接调用类里面的私有方法 * * Python的类方法上 加上装饰器@staticmethod就可以不用实例化类而直接 ...
- MySQL锁(二)表锁:为什么给小表加字段会导致整个库挂掉?
概述 表级锁是MySQL中锁定粒度最大的一种锁,表示对当前操作的整张表加锁,它实现简单,资源消耗较少,被大部分MySQL引擎支持.最常使用的MYISAM与INNODB都支持表级锁定.表级锁定分为表共享 ...
- js下 Day05、DOM案例
一.简易购物车 效果图: 功能思路分析: 功能一:数量加减 \1. 找到所有的加号按钮,循环绑定点击事件.点击加号时让对应的数量+1 (找清楚加号和数量的关系,让数量标签的内容++) \2. 找到所有 ...
- PHP MySQL 快速导入10万条数据
项目背景 数据来源:所有数据均为外部导入,最大数据量在10w+ 输出数据:导出经过业务处理之后的数据 使用框架:fastadmin 涉及的问题: 1.数据读取 2.数据保存 使用数据:10w+ 解决方 ...