文章
分布式系统认证解决方案SpringSecurityOAuth2.0(一)认证授权
分布式系统认证解决方案SpringSecurityOAuth2.0(二)分布式系统认证流程分析与实现
分布式系统认证解决方案SpringSecurityOAuth2.0(三)资源服务器使用Redis令牌、JWT令牌认证及RSA非对称加密算法
分布式系统认证解决方案SpringSecurityOAuth2.0(四)整合网关认证授权
一、简介
网关整合OAuth2.0有两种思路:
第一种是网关只做请求转发;
第二种是认证服务器生成JWT令牌,请求统一在网关层验证,判断权限等操作,在转发至其他微服务。
API网关在认证授权体系中主要负责两件事:
- 作为OAuth2.0的资源服务器角色,实现接入方的权限拦截。
- 令牌解析并转发当前登录用户信息(明文)给微服务。
微服务拿到明文token(明文token中包含登录用户的身份和权限信息)后也需要做两件事:
- 用户授权拦截(校验用户是否有权限访问该资源)。
- 将用户信息存储进当前线程的上下文(有利于后续业务逻辑随时回去当前用户信息)。
二、第一种:网关只做请求转发
这里我们使用SpringCloudGateWay
作为网关服务。
搭建Gateway网关服务可以参考之间的文章:
SpringCloud微服务API网关Gateway的使用和配置(一)路由转发、断言谓词
SpringCloud微服务API网关Gateway的使用和配置(二)过滤器
SpringCloud微服务API网关Gateway的使用和配置(三)动态路由
路由配置
在网关服务的application.yml
配置文件添加认证服务的转发:
# oauth2认证授权服务
- id: oauth2
uri: lb://springboot-security-oauth2
predicates:
- Path=/oauth2/**
# 订单服务
- id: order
uri: lb://springcloud-alibaba-order
predicates:
- Path=/order/**
网关服务的所有以/oauth2
开头的请求都会转发到Oauth2认证服务。
安全配置
/**
* @author :LiuShihao
* @date :Created in 2021/8/18 11:49 上午
* @desc :
* 从Spring Cloud Gateway文档中:
* Spring Cloud Gateway需要Spring Boot和Spring Webflux提供的Netty运行时。它不能在传统的Servlet容器中工作,也不能在构建为WAR时工作。
* 扩展WebSecurityConfigurerAdapter是为了基于servlet的应用程序。
* 您应该为 reactive 应用程序配置Spring Security。
*/
@Configuration
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class WebSecurityConfig {
@Bean
public BCryptPasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http.csrf().disable();
http
.authorizeExchange()
.anyExchange().permitAll()
.and()
.formLogin()
;
return http.build();
}
}
订单服务资源
@PreAuthorize("hasAnyAuthority('ROLE_ADMIN')")
@GetMapping("/test")
public ResultObject decreaseAccount(){
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Object principal = authentication.getPrincipal();
System.out.println("principal:"+principal);
authentication.getAuthorities().stream().forEach(x->{
System.out.println("authorization:"+x.getAuthority());
});
// String username = principal.toString();
String username = authentication.getName();
return new ResultObject(true, StatusCode.OK, SystemConstants.QUERY_SUCCESS,username+"访问Order服务资源");
}
转发路由
请求网关服务申请令牌,网关服务转发到认证服务:
请求网关服务校验令牌,网关服务转发到认证服务:
不携带令牌访问网关服务转发至Order服务:
带上令牌访问网关服务转发至Order服务:
令牌的校验在微服务端进行。
三、第二种:网关解析令牌
依赖
<!--SpringSecurity 权限-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- spring-security-jwt -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-jwt</artifactId>
<version>1.1.0.RELEASE</version>
</dependency>
路由配置
application.yml
配置同上
安全配置
WebSecurityConfig
同上
网关服务创建拦截器校验令牌
拦截校验流程:
- 首先拦截所有请求进行认证,
- 如果请求是
/oauth
请求认证服务申请令牌的,则放行。 - 校验请求头信息Authorization信息。
- 解析JWT令牌。
- 将解析后的用户认证信息添加到请求头中(可选)。
package com.lsh.gateway.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.security.jwt.Jwt;
import org.springframework.security.jwt.JwtHelper;
import org.springframework.security.jwt.crypto.sign.RsaVerifier;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Date;
import java.util.List;
/**
* @author :LiuShihao
* @date :Created in 2021/8/25 1:40 下午
* @desc :
*/
@Slf4j
@Component
public class AuthorizationFilter implements GlobalFilter, Ordered {
/**存放公钥的文件名 */
public String first_public = "first_public.txt";
public String second_public = "second_public.txt";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("----------------------Gateway Global Filter Start---------------------------------");
ServerHttpRequest request = exchange.getRequest();
String uri = request.getURI().toString();
log.info("URI: "+uri+" ; TIME : "+new Date().toString());
if (uri.contains("/oauth/")){
// 请求认证服务 放行
log.info("----------------------Gateway Global Filter 请求OAuth认证服务:放行!---------------------------------");
return chain.filter(exchange);
}
HttpHeaders headers = request.getHeaders();
//获取请求头中的认证信息
List<String> authorization = headers.get("Authorization");
if (authorization == null || authorization.isEmpty() || authorization.size()==0 || authorization.get(0).length()<8){
//没有认证信息 不允许访问,禁止访问
ServerHttpResponse response = exchange.getResponse();
//返回401 未授权
response.setStatusCode(HttpStatus.UNAUTHORIZED);
log.info("----------------------Gateway Global Filter End: 没有令牌!----------------------------");
return exchange.getResponse().setComplete();
}
// Bearer token
String access_token = authorization.get(0);
//注意:此处的JWT令牌是从access_token值第7位开始取值(不包括第7位)
String jwtToken = access_token.substring(7);
//公钥(读取public.txt的公钥信息)
Resource resource = new ClassPathResource(first_public);
String publicKey = null;
try {
publicKey = inputStream2String(resource.getInputStream());
} catch (final IOException e) {
throw new RuntimeException(e);
}
String claims =null;
try{
//解析 token 令牌
Jwt jwt = JwtHelper.decodeAndVerify(jwtToken, new RsaVerifier(publicKey));
//获取Jwt原始内容 载荷
claims = jwt.getClaims();
System.out.println(claims);
}catch (Exception e){
log.error("令牌解析失败:"+e.getMessage());
ServerHttpResponse response = exchange.getResponse();
//返回401 未授权
response.setStatusCode(HttpStatus.UNAUTHORIZED);
log.info("----------------------Gateway Global Filter End:令牌解析失败!----------------------------");
return exchange.getResponse().setComplete();
}
//添加请求头 信息
ServerHttpRequest newRequest = request.mutate().header("username", claims).build();
ServerWebExchange newExchange = exchange.mutate().request(newRequest).build();
Mono<Void> filter = chain.filter(newExchange);
log.info("----------------------Gateway Global Filter 令牌解析成功:放行!---------------------------------");
return filter;
}
/**
* 过滤器有一个优先级的问题,这个值越小,优先级越高
* @return
*/
@Override
public int getOrder() {
return -1;
}
public String inputStream2String(InputStream is) throws IOException {
BufferedReader in = new BufferedReader(new InputStreamReader(is));
StringBuffer buffer = new StringBuffer();
String line = "";
while ((line = in.readLine()) != null) {
buffer.append(line);
}
return buffer.toString();
}
}
JWT令牌使用了RSA非对称加密算法,所以以上代码涉及到了公钥信息。
订单服务创建拦截器查看请求头令牌
/**
* @author :LiuShihao
* @date :Created in 2021/7/19 2:21 下午
* @desc :拦截器 打印请求路径
*/
@Component
public class URIInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("URIInterceptor - RequestURL:"+request.getRequestURL());
String jsonToken = request.getHeader("username");
if (jsonToken != null && !"".equals(jsonToken)){
JSONObject object = JSON.parseObject(jsonToken);
System.out.println("URIInterceptor:username:"+object.toString());
}
return true;
}
}
测试
请求订单服务: