SpringSecurity 微服务权限方案
一、 什么是微服务
1.1 微服务由来
微服务最早由 Martin Fowler 与 James Lewis 于 2014 年共同提出,微服务架构风格是一种使用一套小服务来开发单个应用的方式途径,每个服务运行在自己的进程中,并使用轻量级机制通信,通常是 HTTP API,这些服务基于业务能力构建,并能够通过自动化部署机制来独立部署,这些服务使用不同的编程语言实现,以及不同数据存储技术,并保持最低限度的集中式管理。
1.2 微服务优势
(1)微服务每个模块就相当于一个单独的项目,代码量明显减少,遇到问题也相对来说比较好解决。
(2)微服务每个模块都可以使用不同的存储方式(比如有的用 redis,有的用 mysql等),数据库也是单个模块对应自己的数据库。
(3)微服务每个模块都可以使用不同的开发技术,开发模式更灵活。
1.3 微服务本质
(1)微服务,关键其实不仅仅是微服务本身,而是系统要提供一套基础的架构,这种架构使得微服务可以独立的部署、运行、升级,不仅如此,这个系统架构还让微服务与微服务之间在结构上“松耦合”,而在功能上则表现为一个统一的整体。这种所谓的“统一的整体”表现出来的是统一风格的界面,统一的权限管理,统一的安全策略,统一的上线过程,统一的日志和审计方法,统一的调度方式,统一的访问入口等等。
(2)微服务的目的是有效的拆分应用,实现敏捷开发和部署。
二、 微服务认证与授权实现思路
2.1 认证授权过程分析
(1)如果是基于 Session,那么 Spring-security 会对 cookie 里的 sessionid 进行解析,找到服务器存储的 session 信息,然后判断当前用户是否符合请求的要求。
(2)如果是 token,则是解析出 token,然后将当前请求加入到 Spring-security 管理的权限信息中去
如果系统的模块众多,每个模块都需要进行授权与认证,所以我们选择基于 token 的形式进行授权与认证,用户根据用户名密码认证成功,然后获取当前用户角色的一系列权限值,并以用户名为 key,权限列表为 value 的形式存入 redis 缓存中,根据用户名相关信息生成 token 返回,浏览器将 token 记录到 cookie 中,每次调用 api 接口都默认将 token 携带到 header 请求头中,Spring-security 解析 header 头获取 token 信息,解析 token 获取当前用户名,根据用户名就可以从 redis 中获取权限列表,这样 Spring-security 就能够判断当前请求是否有权限访问
2.2 建表
三、需要什么技术
3.1 Maven
- 创建父工程:管理项目依赖版本
- 创建子模块:使用具体依赖
3.2 Spring boot
- 本质上就是Spring
3.3 MyBatis—plus
- 操作数据库框架
3.4 Spring Cloud(主要用到)
- (1) Gateway网关: 对外统一对外接口
- (2) 注册中心 Nacos
3.5 其他技术
- Redis Jwt 生成token字符串
- Swagger (接口测试)
3.6 前端技术
- vue
三、项目搭建
3.1 创建父工程 springSecurity_By_SpringCloud :管理依赖版本
3.2 在父工程创建子模块
-(1)common
- service——base :工具类
- spring——security:权限配置
-(2)infrastructure
- api——gateway:网关
-(3)service
- service——acl:权限管理微服务模块
3.3 父工程pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<modules>
<module>common</module>
<module>infrastructure</module>
<module>service</module>
</modules>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.test</groupId>
<artifactId>SpringSecurity_By_SpringCloud</artifactId>
<packaging>pom</packaging><!--父工程-->
<version>0.0.1-SNAPSHOT</version>
<name>SpringSecurity_By_SpringCloud</name>
<description>SpringSecurity_By_SpringCloud</description>
<!--同一版本定义-->
<properties>
<java.version>1.8</java.version>
<cloud-alibaba.version>0.2.2.RELEASE</cloud-alibaba.version>
<mybatis-plus.version>3.5.2</mybatis-plus.version>
<velocity.version>2.0</velocity.version>
<gson.version>2.8.2</gson.version>
<swagger.version>2.9.2</swagger.version>
<jwt.version>0.9.1</jwt.version>
<fastjson.version>1.2.62</fastjson.version>
<json.version>20170516</json.version>
<lombok.version>1.18.22</lombok.version>
</properties>
<dependencyManagement>
<dependencies>
<!--Spring Cloud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR9</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--mybatis-plus 持久层-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- velocity 模板引擎, Mybatis Plus 代码生成器需要 -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>${velocity.version}</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>${gson.version}</version>
</dependency>
<!--swagger-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${swagger.version}</version>
</dependency>
<!--swagger ui-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>${swagger.version}</version>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jwt.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>${json.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3.3.1 在common模块 pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>SpringSecurity_By_SpringCloud</artifactId>
<groupId>com.test</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>common</artifactId>
<packaging>pom</packaging>
<modules>
<module>service_base</module>
<module>spring_security</module>
</modules>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>provided </scope>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<scope>provided </scope>
</dependency>
<!--lombok用来简化实体类:需要安装lombok插件-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<!--swagger-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<scope>provided </scope>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<scope>provided </scope>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- spring2.X集成redis所需common-pool2-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.6.0</version>
</dependency>
</dependencies>
</project>
3.3.1.1 在common模块中 service-base 的 pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>common</artifactId>
<groupId>com.test</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>service_base</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
</project>
3.3.1.2 在common模块中 spring——security 的 pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>common</artifactId>
<groupId>com.test</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>spring_security</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.test</groupId>
<artifactId>service_base</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- Spring Security依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
</dependencies>
</project>
3.3.2 infrastructure模块 pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>SpringSecurity_By_SpringCloud</artifactId>
<groupId>com.test</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>infrastructure</artifactId>
<packaging>pom</packaging>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<modules>
<module>api_gateway</module>
</modules>
</project>
3.3.2.1 在infrastructure模块中 api——gateway 的 pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>infrastructure</artifactId>
<groupId>com.test</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>api_gateway</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.test</groupId>
<artifactId>service_base</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--gson-->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<!--服务调用-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
</project>
3.3.3 service模块 pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>SpringSecurity_By_SpringCloud</artifactId>
<groupId>com.test</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>service</artifactId>
<packaging>pom</packaging>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<modules>
<module>service_acl</module>
</modules>
<dependencies>
<dependency>
<groupId>com.test</groupId>
<artifactId>service_base</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!--服务注册-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--服务调用-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!--swagger-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- velocity 模板引擎, Mybatis Plus 代码生成器需要 -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
</dependency>
<!--lombok用来简化实体类:需要安装lombok插件-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--gson-->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
</project>
3.3.3.1 在service模块中 service_acl pom文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>service</artifactId>
<groupId>com.test</groupId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>service_acl</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.test</groupId>
<artifactId>spring_security</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
</dependencies>
</project>
3.4 启动 redis 和 nacos
3.5 开发
3.5.1 编写工具类,比如MD5加密等等
MD5 字符串加密
MD5.java
package com.test.utils.utils;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public final class MD5 {
public static String encrypt(String strSrc) {
try {
char hexChars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
'9', 'a', 'b', 'c', 'd', 'e', 'f' };
byte[] bytes = strSrc.getBytes();
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(bytes);
bytes = md.digest();
int j = bytes.length;
char[] chars = new char[j * 2];
int k = 0;
for (int i = 0; i < bytes.length; i++) {
byte b = bytes[i];
chars[k++] = hexChars[b >>> 4 & 0xf];
chars[k++] = hexChars[b & 0xf];
}
return new String(chars);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
throw new RuntimeException("MD5加密出错!!+" + e);
}
}
public static void main(String[] args) {
System.out.println(MD5.encrypt("111111"));
}
}
R 定义所有controller 返回统一结果
R.java
package com.test.utils.utils;
import lombok.Data;
import java.util.HashMap;
import java.util.Map;
//统一返回结果的类
@Data
public class R {
private Boolean success;
private Integer code;
private String message;
private Map<String, Object> data = new HashMap<String, Object>();
//把构造方法私有
private R() {}
//成功静态方法
public static R ok() {
R r = new R();
r.setSuccess(true);
r.setCode(20000);
r.setMessage("成功");
return r;
}
//失败静态方法
public static R error() {
R r = new R();
r.setSuccess(false);
r.setCode(20001);
r.setMessage("失败");
return r;
}
public R success(Boolean success){
this.setSuccess(success);
return this;
}
public R message(String message){
this.setMessage(message);
return this;
}
public R code(Integer code){
this.setCode(code);
return this;
}
public R data(String key, Object value){
this.data.put(key, value);
return this;
}
public R data(Map<String, Object> map){
this.setData(map);
return this;
}
}
Redis RsponseUtil 数据返回
ResponseUtil.java
package com.test.utils.utils;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class ResponseUtil {
public static void out(HttpServletResponse response, R r) {
ObjectMapper mapper = new ObjectMapper();
response.setStatus(HttpStatus.OK.value());
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
try {
mapper.writeValue(response.getWriter(), r);
} catch (IOException e) {
e.printStackTrace();
}
}
}
exceptionHandler 异常处理类
package com.test.utils.exceptionhandler;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor //生成有参数构造方法
@NoArgsConstructor //生成无参数构造
public class GuliException extends RuntimeException {
private Integer code;//状态码
private String msg;//异常信息
}
package com.test.utils.exceptionhandler;
import com.test.utils.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
//指定出现什么异常执行这个方法
@ExceptionHandler(Exception.class)
@ResponseBody //为了返回数据
public R error(Exception e) {
e.printStackTrace();
return R.error().message("执行了全局异常处理..");
}
//特定异常
@ExceptionHandler(ArithmeticException.class)
@ResponseBody //为了返回数据
public R error(ArithmeticException e) {
e.printStackTrace();
return R.error().message("执行了ArithmeticException异常处理..");
}
//自定义异常
@ExceptionHandler(GuliException.class)
@ResponseBody //为了返回数据
public R error(GuliException e) {
log.error(e.getMessage());
e.printStackTrace();
return R.error().code(e.getCode()).message(e.getMsg());
}
}
Redis配置
package com.test.utils;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
@EnableCaching //开启缓存
@Configuration //配置类
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
template.setConnectionFactory(factory);
//key序列化方式
template.setKeySerializer(redisSerializer);
//value序列化
template.setValueSerializer(jackson2JsonRedisSerializer);
//value hashmap序列化
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 配置序列化(解决乱码的问题),过期时间600秒
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(600))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
return cacheManager;
}
}
swagger 接口调试
package com.test.utils;
import com.google.common.base.Predicates;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration//配置类
@EnableSwagger2 //swagger注解
public class SwaggerConfig {
@Bean
public Docket webApiConfig(){
return new Docket(DocumentationType.SWAGGER_2)
.groupName("webApi")
.apiInfo(webApiInfo())
.select()
//.paths(Predicates.not(PathSelectors.regex("/admin/.*")))
.paths(Predicates.not(PathSelectors.regex("/error.*")))
.build();
}
private ApiInfo webApiInfo(){
return new ApiInfoBuilder()
.title("网站-课程中心API文档")
.description("本文档描述了课程中心微服务接口定义")
.version("1.0")
.contact(new Contact("java", "http://www.baidu.com", "1123@qq.com"))
.build();
}
}
mybatisplus操作
package com.test.utils.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
//属性名称,不是字段名称
this.setFieldValByName("gmtCreate", new Date(), metaObject);
this.setFieldValByName("gmtModified", new Date(), metaObject);
}
@Override
public void updateFill(MetaObject metaObject) {
this.setFieldValByName("gmtModified", new Date(), metaObject);
}
}
3.5.2 SpringSecurity 相关配置
PasswordEncoder.java 密码加密类
package com.test.security.security;
import com.test.utils.utils.MD5;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
// 密码处理
@Component
public class DefaultPasswordEncoder implements PasswordEncoder {
public DefaultPasswordEncoder() {
this(-1);
}
public DefaultPasswordEncoder(int strength) {
}
//进行MD5加密
@Override
public String encode(CharSequence charSequence) {
return MD5.encrypt(charSequence.toString());
}
//进行密码比对
@Override
public boolean matches(CharSequence charSequence, String encodedPassword) {
return encodedPassword.equals(MD5.encrypt(charSequence.toString()));
}
}
token 操作工具类
package com.test.security.security;
import io.jsonwebtoken.CompressionCodecs;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;
import java.util.Date;
//token 操作工具类
@Component
public class TokenManager {
//token有效时长
private long tokenEcpiration = 24*60*60*1000;
//编码秘钥
private String tokenSignKey = "123456";
//1 使用jwt根据用户名生成token
public String createToken(String username) {
String token = Jwts.builder().setSubject(username)
.setExpiration(new Date(System.currentTimeMillis()+tokenEcpiration))//过期时间
.signWith(SignatureAlgorithm.HS512, tokenSignKey).compressWith(CompressionCodecs.GZIP).compact();
return token;
}
//2 根据token字符串得到用户信息
public String getUserInfoFromToken(String token) {
String userinfo = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody().getSubject();
return userinfo;
}
//3 删除token
public void removeToken(String token) {
//jwt token 无需删除,客户端扔掉即可。前端处理即可!!!
}
}
3.5.2.1 jwt
a、访问令牌的类型
b、JWT 的组成
典型的,一个 JWT 看起来如下图:
该对象为一个很长的字符串,字符之间通过"."分隔符分为三个子串。每一个子串表示了一个功能块,总共有以下三个部分:JWT 头、有效载荷和签名
c、JWT 头
JWT 头部分是一个描述 JWT 元数据的 JSON 对象,通常如下所示。
{
"alg": "HS256",
"typ": "JWT"
}
在上面的代码中,alg 属性表示签名使用的算法,默认为 HMAC SHA256(写为 HS256);typ 属性表示令牌的类型,JWT 令牌统一写为 JWT。最后,使用 Base64 URL 算法将上述JSON 对象转换为字符串保存。
d、有效载荷
有效载荷部分,是 JWT 的主体内容部分,也是一个 JSON 对象,包含需要传递的数据。 JWT指定七个默认字段供选择。
- iss:发行人
- exp:到期时间
- sub:主题
- aud:用户
- nbf:在此之前不可用
- iat:发布时间
- jti:JWT ID 用于标识该 JWT
除以上默认字段外,我们还可以自定义私有字段,如下例:
{
"sub": "1234567890",
"name": "Helen",
"admin": true
}
请注意:默认情况下 JWT 是未加密的,任何人都可以解读其内容,因此不要构建隐私信息字段,存放保密信息,以防止信息泄露。
JSON 对象也使用 Base64 URL 算法转换为字符串保存。
e、签名哈希
签名哈希部分是对上面两部分数据签名,通过指定的算法生成哈希,以确保数据不会被篡改。
首先,需要指定一个密码(secret)。该密码仅仅为保存在服务器中,并且不能向用户公开。然后,使用标头中指定的签名算法(默认情况下为 HMAC SHA256)根据以下公式生成签名。HMACSHA256(base64UrlEncode(header) + “.” + base64UrlEncode(claims), secret)在计算出签名哈希后,JWT 头,有效载荷和签名哈希的三个部分组合成一个字符串,每个部分用"."分隔,就构成整个 JWT 对象。
f、Base64URL 算法
如前所述,JWT 头和有效载荷序列化的算法都用到了 Base64URL。该算法和常见 Base64 算法类似,稍有差别。作为令牌的 JWT 可以放在 URL 中(例如 api.example/?token=xxx)。 Base64 中用的三个字符是"+“,”/“和”=“,由于在 URL 中有特殊含义,因此 Base64URL 中对他们做了替换:”=“去掉,”+“用”-“替换,”/“用”_"替换,这就是 Base64URL 算法。
3.5.2.3 具体实现类
3.5.2.4 实现权限管理功能代码
3.5.2.4.1 退出处理器
TokenLogoutHandler.java
package com.test.security.security;
import com.test.utils.utils.R;
import com.test.utils.utils.ResponseUtil;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
//退出处理器
public class TokenLogoutHandler implements LogoutHandler {
private TokenManager tokenManager;//token工具类
private RedisTemplate redisTemplate;//redis模板
//有参构造方法
public TokenLogoutHandler(TokenManager tokenManager,RedisTemplate redisTemplate) {
this.tokenManager = tokenManager;
this.redisTemplate = redisTemplate;
}
@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
//1 从header里面获取token
//2 token不为空,移除token,从redis删除token
String token = request.getHeader("token");
if(token != null) {
//移除
tokenManager.removeToken(token);
//从token获取用户名
String username = tokenManager.getUserInfoFromToken(token);
redisTemplate.delete(username);//username是key,password是值
}
ResponseUtil.out(response, R.ok());
}
}
3.5.2.5 未授权统一处理类:UnauthEntryPoint
package com.test.security.security;
import com.test.utils.utils.R;
import com.test.utils.utils.ResponseUtil;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
// 未授权统一处理类
public class UnauthEntryPoint implements AuthenticationEntryPoint {
//未授权时执行方法
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
ResponseUtil.out(httpServletResponse, R.error());
}
}
3.5.2.4 编写自定义认证和授权的处理器
User.java
package com.test.security.entity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
@Data
@ApiModel(description = "用户实体类")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "微信openid")
private String username;
@ApiModelProperty(value = "密码")
private String password;
@ApiModelProperty(value = "昵称")
private String nickName;
@ApiModelProperty(value = "用户头像")
private String salt;
@ApiModelProperty(value = "用户签名")
private String token;
}
UserDetial.java的
package com.test.security.entity;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@Data
public class SecurityUser implements UserDetails {
//当前登录用户
private transient User currentUserInfo;
//当前权限
private List<String> permissionValueList;
public SecurityUser() {
}
public SecurityUser(User user) {
if (user != null) {
this.currentUserInfo = user;
}
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new ArrayList<>();
for(String permissionValue : permissionValueList) {
if(StringUtils.isEmpty(permissionValue)) continue;
SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);
authorities.add(authority);
}
return authorities;
}
@Override
public String getPassword() {
return currentUserInfo.getPassword();
}
@Override
public String getUsername() {
return currentUserInfo.getUsername();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
权限认证:TokenLoginFilter
package com.test.security.filter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.test.security.entity.SecurityUser;
import com.test.security.entity.User;
import com.test.security.security.TokenManager;
import com.test.utils.utils.R;
import com.test.utils.utils.ResponseUtil;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
//认证
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {
//token操作
private TokenManager tokenManager;
//redis 操作
private RedisTemplate redisTemplate;
private AuthenticationManager authenticationManager;
public TokenLoginFilter(AuthenticationManager authenticationManager, TokenManager tokenManager, RedisTemplate redisTemplate) {
this.authenticationManager = authenticationManager;
this.tokenManager = tokenManager;
this.redisTemplate = redisTemplate;
this.setPostOnly(false);
//设置登录路径,提交方式
this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/acl/login","POST"));
}
//1 获取表单提交用户名和密码
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
//获取表单提交数据
try {
User user = new ObjectMapper().readValue(request.getInputStream(), User.class);
return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(),user.getPassword(),
new ArrayList<>()));
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException();
}
}
//2 认证成功调用的方法
@Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, FilterChain chain, Authentication authResult)
throws IOException, ServletException {
//认证成功,得到认证成功之后用户信息
SecurityUser user = (SecurityUser)authResult.getPrincipal();
//根据用户名生成token
String token = tokenManager.createToken(user.getCurrentUserInfo().getUsername());
//把用户名称和用户权限列表放到redis
redisTemplate.opsForValue().set(user.getCurrentUserInfo().getUsername(),user.getPermissionValueList());
//返回token
ResponseUtil.out(response, R.ok().data("token",token));
}
//3 认证失败调用的方法
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed)
throws IOException, ServletException {
ResponseUtil.out(response, R.error());
}
}
权限授权:继承最基本的过滤器BasicAuthenticationFilter
package com.test.security.filter;
import com.test.security.security.TokenManager;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
//授权
public class TokenAuthFilter extends BasicAuthenticationFilter {
private TokenManager tokenManager;
private RedisTemplate redisTemplate;
public TokenAuthFilter(AuthenticationManager authenticationManager,TokenManager tokenManager,RedisTemplate redisTemplate) {
super(authenticationManager);
this.tokenManager = tokenManager;
this.redisTemplate = redisTemplate;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
//获取当前认证成功用户权限信息
UsernamePasswordAuthenticationToken authRequest = getAuthentication(request);
//判断如果有权限信息,放到权限上下文中
if(authRequest != null) {
SecurityContextHolder.getContext().setAuthentication(authRequest);
}
//放行操作
chain.doFilter(request,response);
}
//核心代码(重要!!!)
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
//从header获取token
String token = request.getHeader("token");
if(token != null) {
//从token获取用户名
String username = tokenManager.getUserInfoFromToken(token);
//从redis获取对应权限列表
List<String> permissionValueList = (List<String>)redisTemplate.opsForValue().get(username);
Collection<GrantedAuthority> authority = new ArrayList<>();
for(String permissionValue : permissionValueList) {
SimpleGrantedAuthority auth = new SimpleGrantedAuthority(permissionValue);
authority.add(auth);
}
return new UsernamePasswordAuthenticationToken(username,token,authority);
}
return null;
}
}
核心配置类:
TokenWebSecurityConfig
package com.test.security.config;
import com.test.security.filter.TokenAuthFilter;
import com.test.security.filter.TokenLoginFilter;
import com.test.security.security.DefaultPasswordEncoder;
import com.test.security.security.TokenLogoutHandler;
import com.test.security.security.TokenManager;
import com.test.security.security.UnauthEntryPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
//核心配置类(Configuration)
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter {
private TokenManager tokenManager;//工具类
private RedisTemplate redisTemplate;//redis模板
private DefaultPasswordEncoder defaultPasswordEncoder;
private UserDetailsService userDetailsService;
//有参构造
@Autowired
public TokenWebSecurityConfig(UserDetailsService userDetailsService, DefaultPasswordEncoder defaultPasswordEncoder,
TokenManager tokenManager, RedisTemplate redisTemplate) {
this.userDetailsService = userDetailsService;
this.defaultPasswordEncoder = defaultPasswordEncoder;
this.tokenManager = tokenManager;
this.redisTemplate = redisTemplate;
}
/**
* 配置设置
* @param http
* @throws Exception
*/
//设置退出的地址和token,redis操作地址
@Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling()
.authenticationEntryPoint(new UnauthEntryPoint())//没有权限访问时调用
.and().csrf().disable()
.authorizeRequests()
.anyRequest().authenticated()
.and().logout().logoutUrl("/admin/acl/index/logout")//退出路径
.addLogoutHandler(new TokenLogoutHandler(tokenManager,redisTemplate)).and()
.addFilter(new TokenLoginFilter(authenticationManager(), tokenManager, redisTemplate))//认证过滤器
.addFilter(new TokenAuthFilter(authenticationManager(), tokenManager, redisTemplate)).httpBasic();//授权设置
}
//调用userDetailsService和密码处理
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(defaultPasswordEncoder);
}
//不进行认证的路径,可以直接访问
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/api/**");
}
}
UserDetailService.java
import org.springframework.stereotype.Service;
import java.util.List;
//名字和TokenWebSecurityConfig中的名字相同
@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.test.security.entity.User curUser = new com.test.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;
}
}
package com.test.aclservice.service;
import com.test.aclservice.entity.User;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 用户表 服务类
* </p>
*
* @author testjava
* @since 2023-02-24
*/
public interface UserService extends IService<User> {
// 从数据库中取出用户信息
User selectByUsername(String username);
}
package com.test.aclservice.service;
import com.alibaba.fastjson.JSONObject;
import com.test.aclservice.entity.Permission;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
/**
* <p>
* 权限 服务类
* </p>
*
* @author testjava
* @since 2023-02-24
*/
public interface PermissionService extends IService<Permission> {
//获取全部菜单
List<Permission> queryAllMenu();
//根据角色获取菜单
List<Permission> selectAllMenu(String roleId);
//给角色分配权限
void saveRolePermissionRealtionShip(String roleId, String[] permissionId);
//递归删除菜单
void removeChildById(String id);
//根据用户id获取用户菜单
List<String> selectPermissionValueByUserId(String id);
List<JSONObject> selectPermissionByUserId(String id);
//获取全部菜单
List<Permission> queryAllMenuGuli();
//递归删除菜单
void removeChildByIdGuli(String id);
//给角色分配权限
void saveRolePermissionRealtionShipGuli(String roleId, String[] permissionId);
}
package com.test.aclservice.service.impl;
import com.test.aclservice.entity.User;
import com.test.aclservice.service.PermissionService;
import com.test.aclservice.service.UserService;
import com.test.security.entity.SecurityUser;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.List;
//名字和TokenWebSecurityConfig中的名字相同
@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.test.security.entity.User curUser = new com.test.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;
}
}
<select id="selectPermissionValueByUserId" resultType="String">
select
p.permission_value
from acl_user_role ur
inner join acl_role_permission rp on rp.role_id = ur.role_id
inner join acl_permission p on p.id = rp.permission_id
where ur.user_id = #{userId}
and p.type = 2
and ur.is_deleted = 0
and rp.is_deleted = 0
and p.is_deleted = 0
</select>
3.5.4 配置文件
#服务端口
server.port=8009
# 服务名
spring.application.name=service-acl
# mysql数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/dblearning?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT
spring.datasource.username=root
spring.datasource.password=root
# redis连接
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.database= 0
spring.redis.timeout=1800000
spring.redis.lettuce.pool.max-active=20
spring.redis.lettuce.pool.max-wait=-1
#最大阻塞等待时间(负数表示没限制)
spring.redis.lettuce.pool.max-idle=5
spring.redis.lettuce.pool.min-idle=0
#最小空闲
#配置mapper xml文件的路径
mybatis-plus.mapper-locations=classpath:mybatis/*.xml
# nacos服务地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
# 返回json全局时间格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8
# mybatis日志
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
3.5.4 配置Gateway网关
暴露统一端口
package com.test.gateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
}
package com.test.gateway.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.util.pattern.PathPatternParser;
@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);
}
}
infrastucture 配置文件
# 服务端口
server.port=8222
# 服务名
spring.application.name=service-gateway
# nacos服务地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
# 使用服务发现路由
spring.cloud.gateway.discovery.locator.enabled=true
# 配置路由规则
spring.cloud.gateway.routes[0].id=service-acl
# 设置路由uri lb://注册服务名称 lb load-balance
spring.cloud.gateway.routes[0].uri=lb://service-acl
# 具体路径规则
spring.cloud.gateway.routes[0].predicates= Path=/*/acl/**