Springcloud gateway gateway+authentication service+token mode, entrance layer authentication unified microservice authentication [design practice]

Yin Changqing 2022-08-06 17:28:54 阅读数:524

springcloudgatewaygateway+authenticationgatewayauthentication

目录

背景

实现

gateway

maven配置

yml配置

The login page to intercept the configuration class

白名单配置

token工具类

登录配置类

全局过滤器类

项目启动类


背景

Distributed project of single sign-on is divided into certification service(单点登录服务端)和业务服务(单点登录客户端)两个角色,

When access the business service,Certification service clientSDKCheck whether there is a log intoken,如果没有登录token,Need to carry the current request link is redirected to the certification service,By certification after the certification service redirect business service link,实现单点登录.

gatewaySingle sign-on client functions,Generally if the front and back end project is separation,If the request is not carrying the logintoken,Direct return need certification,Before and after the client is not separate project,Can do a redirect page operation.

本文主要讨论gateway的实现,Certification service need to achieve.

实现

gateway

注册中心、Configuration center withnacos

nacos官网:home

配置可以参考:Springcloud+Druid+Mybatis+Seata+Nacos动态切换多数据源,分布式事务的实现_殷长庆的博客-CSDN博客_seata多数据源切换

maven配置

主要集成nacos注册、配置中心和gateway网关


<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- 转发ws协议请求 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</exclusion>
</exclusions>
</dependency>

yml配置

server:
port: 8888
spring:
profiles:
active: dev
application:
name: luckgateway
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
namespace: luck-cloud
config:
server-addr: 127.0.0.1:8848
file-extension: yaml
namespace: luck-cloud
gateway:
routes:
- id: lucksso
uri: lb://lucksso
predicates:
- Path=/lucksso/**
- id: luckbiz
uri: lb://luckbiz
predicates:
- Path=/luckbiz/**
- id: luckim
uri: lb:ws://luckim
predicates:
- Path=/luckim/**
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,默认false,trueThe gateway forwarding micro service link withoutpath前缀
lower-case-service-id: true #使用小写服务名,默认是大写
secure:
ignore:
urls: #配置白名单路径
- "/actuator/**"
- "/lucksso/**"
- "/resources/**"
page:
urls: #Configuration need login page path
- "/**"

The login page to intercept the configuration class

package com.luck.config;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* The login page to intercept configuration
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Component
@ConfigurationProperties(prefix = "secure.page")
public class PageUrlsConfig {
private List<String> urls;
}

白名单配置

package com.luck.config;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 白名单配置
*/
@Data
@EqualsAndHashCode(callSuper = false)
@Component
@ConfigurationProperties(prefix = "secure.ignore")
public class IgnoreUrlsConfig {
private List<String> urls;
}

token工具类

tokenThe acquisition and calibration tools

package com.luck.config;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpCookie;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.server.ServerWebExchange;
/**
* 登录token工具类
*/
public class LoginTokenUtil {
/** The login service address */
public static final String LOGIN_PATH = "/lucksso/login?servers=";
/** After a successful login callback interface address */
public static final String LOGIN_CALLBACK_PATH = "/sso/login";
/** After the success of the login callback interfacetoken参数名称 */
public static final String LOGIN_CALLBACK_TOKEN = "token";
/** After the success of the login callback interface of callback address parameter name */
public static final String LOGIN_CALLBACK_URL = "url";
/** 登录成功后的token名称 */
public static final String LOGIN_TOKEN_NAME = "luckToken";
/**
* 获取登录token
* @param exchange 上下文
* @return
*/
public static String getLoginToken(ServerWebExchange exchange) {
if (null == exchange) {
return null;
}
ServerHttpRequest request = exchange.getRequest();
String loginToken = request.getHeaders().getFirst(LOGIN_TOKEN_NAME);
if (StringUtils.isBlank(loginToken)) {
Object token = exchange.getAttribute(LOGIN_TOKEN_NAME);
if (null != token) {
loginToken = (String) token;
}
}
if (StringUtils.isBlank(loginToken)) {
loginToken = request.getQueryParams().getFirst(LOGIN_TOKEN_NAME);
}
if (StringUtils.isBlank(loginToken)) {
HttpCookie loginCookie = request.getCookies().getFirst(LOGIN_TOKEN_NAME);
if (null != loginCookie) {
loginToken = loginCookie.getValue();
}
}
return loginToken;
}
/**
* 校验登录token是否有效
* @param loginToken 登录token
* @return
*/
public static boolean validateLoginToken(String loginToken) {
if (StringUtils.isNoneBlank(loginToken)) {
// do something
return true;
}
return false;
}
}

登录配置类

To realize the core logic of single sign-on client,According to the request to judge any logintoken,Available from the request header orcookie、Link parameters to obtain,If not redirected to the certification service,Login authentication service implementation logic,The callback gateway interface,完成登录

package com.luck.config;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
@Configuration
public class LoginConfig {
/** 路由匹配器 */
public static PathMatcher pathMatcher = new AntPathMatcher();
/** 白名单 */
@Autowired
private IgnoreUrlsConfig ignoreUrlsConfig;
/** 拦截名单 */
@Autowired
private PageUrlsConfig pageUrlsConfig;
@FunctionalInterface
public interface LoginFunction {
public Mono<Void> run();
}
@Bean
public WebFilter getSsoLoginFilter() {
return new WebFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
try {
ServerHttpRequest request = exchange.getRequest();
String path = request.getPath().toString();
if (LoginTokenUtil.LOGIN_CALLBACK_PATH.equals(path)) {
String loginToken = request.getQueryParams().getFirst(LoginTokenUtil.LOGIN_CALLBACK_TOKEN);
String loginUrl = request.getQueryParams().getFirst(LoginTokenUtil.LOGIN_CALLBACK_URL);
if (StringUtils.isAnyBlank(loginToken, loginUrl)) {
throw new RuntimeException("参数错误!");
}
String url = URLDecoder.decode(loginUrl, "UTF-8");
boolean validateLoginToken = LoginTokenUtil.validateLoginToken(loginToken);
if (validateLoginToken) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.SEE_OTHER);// 校验成功,重定向
response.getHeaders().set(HttpHeaders.LOCATION, url);
return exchange.getResponse().setComplete();
}
// 校验失败,Throw exceptions or perform a login again
// redirectSSO(exchange, request, url);
throw new RuntimeException("token校验失败!");
}
String loginToken = LoginTokenUtil.getLoginToken(exchange);
Mono<Void> result = null;
if (StringUtils.isNoneBlank(loginToken)) {
boolean hasLogin = LoginTokenUtil.validateLoginToken(loginToken);
if (!hasLogin) {
result = redirectSSO(exchange, request, path);
} else {
return chain.filter(exchange);
}
} else {
result = redirectSSO(exchange, request, path);
}
if (null != result) {
return result;
}
throw new RuntimeException("token校验失败!");
} catch (Exception e) {
exchange.getResponse().getHeaders().set("Content-Type", "application/json; charset=utf-8");
return exchange.getResponse().writeWith(Mono.just(exchange.getResponse().bufferFactory().wrap(e.getMessage().getBytes())));
}
}
/**
* The single sign-on (sso)
* @param exchange 上下文
* @param request 请求
* @param path 请求地址
* @return
*/
private Mono<Void> redirectSSO(ServerWebExchange exchange, ServerHttpRequest request, String path) {
Mono<Void> result = match(pageUrlsConfig.getUrls(), ignoreUrlsConfig.getUrls(), path, () -> {
if (isPage(path)) {
URI uri = request.getURI();
String url = "/";
try {
url = LoginTokenUtil.LOGIN_PATH // The login service address
+ URLEncoder.encode(uri.getScheme() + "://" + uri.getAuthority() // gateway服务(http://gateway)
+ LoginTokenUtil.LOGIN_CALLBACK_PATH + "?" + LoginTokenUtil.LOGIN_CALLBACK_URL + "=" // gateway回调地址参数 http://gateway/sso/login?url=
+ URLEncoder.encode(uri.toString(), "UTF-8"), "UTF-8");// Login successful redirection address
} catch (UnsupportedEncodingException e1) {
e1.printStackTrace();
}
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.SEE_OTHER);
response.getHeaders().set(HttpHeaders.LOCATION, url);
exchange.getResponse().getHeaders().set("Content-Type", "text/plain; charset=utf-8");
return exchange.getResponse().setComplete();
}
return null;
});
return result;
}
};
}
/**
* Figure out whether the request page request
* @param path 请求路径
* @return
*/
private boolean isPage(String path) {
return true;
}
/**
* 路由匹配 (And specify the exclusion clause),If the match succeeds to perform authentication function
* @param patterns Routing clause set
* @param excludePatterns To exclude the routing of clause set
* @param path 请求链接
* @param function 要执行的方法
*/
public Mono<Void> match(List<String> patterns, List<String> excludePatterns, String path, LoginFunction function) {
if (isMatchCurrURI(patterns, path)) {
if (isMatchCurrURI(excludePatterns, path) == false) {
return function.run();
}
}
return null;
}
/**
* 路由匹配 (使用当前URI)
* @param patterns Routing clause set
* @param path By matching routing
* @return 是否匹配成功
*/
public boolean isMatchCurrURI(List<String> patterns, String path) {
return isMatch(patterns, path);
}
/**
* 路由匹配
* @param patterns Routing clause set
* @param path By matching routing
* @return 是否匹配成功
*/
public boolean isMatch(List<String> patterns, String path) {
for (String pattern : patterns) {
if (isMatch(pattern, path)) {
return true;
}
}
return false;
}
/**
* 路由匹配
* @param pattern Routing clause
* @param path By matching routing
* @return 是否匹配成功
*/
public boolean isMatch(String pattern, String path) {
return pathMatcher.match(pattern, path);
}
}

全局过滤器类

Mainly to add login gateway forwarding requeststoken

package com.luck.config;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* 全局过滤器,添加登录token请求头
*/
@Component
public class ForwardAuthFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange//
.getRequest()//
.mutate()//
.header(LoginTokenUtil.LOGIN_TOKEN_NAME, LoginTokenUtil.getLoginToken(exchange))//
.build();//
ServerWebExchange newExchange = exchange.mutate().request(request).build();
return chain.filter(newExchange);
}
}

项目启动类

package com.luck;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}

copyright:author[Yin Changqing],Please bring the original link to reprint, thank you. https://en.javamana.com/2022/218/202208061703170560.html