Browse Source

权限支持基础调整

rengb 3 years ago
parent
commit
211f9640eb
30 changed files with 1181 additions and 679 deletions
  1. 12 0
      common/pom.xml
  2. 157 0
      common/src/main/java/com/lantone/common/api/CommonResult.java
  3. 14 0
      common/src/main/java/com/lantone/common/api/IErrorCode.java
  4. 32 0
      common/src/main/java/com/lantone/common/api/ResultCode.java
  5. 68 0
      common/src/main/java/com/lantone/common/config/BaseRedisConfig.java
  6. 55 0
      common/src/main/java/com/lantone/common/constant/AuthConstant.java
  7. 26 0
      common/src/main/java/com/lantone/common/domain/UserDto.java
  8. 184 0
      common/src/main/java/com/lantone/common/service/RedisService.java
  9. 200 0
      common/src/main/java/com/lantone/common/service/impl/RedisServiceImpl.java
  10. 15 13
      gateway-service/pom.xml
  11. 106 0
      gateway-service/src/main/java/com/lantone/authorization/AuthorizationManager.java
  12. 38 0
      gateway-service/src/main/java/com/lantone/component/RestAuthenticationEntryPoint.java
  13. 38 0
      gateway-service/src/main/java/com/lantone/component/RestfulAccessDeniedHandler.java
  14. 0 28
      gateway-service/src/main/java/com/lantone/config/ExclusionUrl.java
  15. 31 0
      gateway-service/src/main/java/com/lantone/config/GlobalCorsConfig.java
  16. 20 0
      gateway-service/src/main/java/com/lantone/config/IgnoreUrlsConfig.java
  17. 14 0
      gateway-service/src/main/java/com/lantone/config/RedisConfig.java
  18. 0 202
      gateway-service/src/main/java/com/lantone/config/RedisConfigurer.java
  19. 49 8
      gateway-service/src/main/java/com/lantone/config/ResourceServerConfig.java
  20. 0 13
      gateway-service/src/main/java/com/lantone/facade/TokenFacade.java
  21. 26 124
      gateway-service/src/main/java/com/lantone/filter/AuthGlobalFilter.java
  22. 46 0
      gateway-service/src/main/java/com/lantone/filter/IgnoreUrlsRemoveJwtFilter.java
  23. 1 1
      gateway-service/src/main/java/com/lantone/config/SwaggerHandler.java
  24. 0 16
      gateway-service/src/main/java/com/lantone/security/dto/JwtStore.java
  25. 0 84
      gateway-service/src/main/java/com/lantone/security/dto/Result.java
  26. 0 40
      gateway-service/src/main/java/com/lantone/service/SysTokenService.java
  27. 0 134
      gateway-service/src/main/java/com/lantone/service/impl/SysTokenServiceImpl.java
  28. 0 12
      gateway-service/src/main/resources/application.yml
  29. 16 2
      gateway-service/src/main/resources/bootstrap.yml
  30. 33 2
      pom.xml

+ 12 - 0
common/pom.xml

@@ -18,6 +18,10 @@
             <groupId>com.google.guava</groupId>
             <artifactId>guava</artifactId>
         </dependency>
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+        </dependency>
         <dependency>
             <groupId>com.github.xiaoymin</groupId>
             <artifactId>knife4j-micro-spring-boot-starter</artifactId>
@@ -54,6 +58,14 @@
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
     </dependencies>
 
 </project>

+ 157 - 0
common/src/main/java/com/lantone/common/api/CommonResult.java

@@ -0,0 +1,157 @@
+package com.lantone.common.api;
+
+/**
+ * @Description: 通用返回对象
+ * @author: rengb
+ * @time: 2021/1/5 18:27
+ */
+public class CommonResult<T> {
+
+    private long code;
+    private String message;
+    private T data;
+
+    protected CommonResult() {
+    }
+
+    protected CommonResult(long code, String message, T data) {
+        this.code = code;
+        this.message = message;
+        this.data = data;
+    }
+
+    /**
+     * 成功返回结果
+     *
+     * @param data 获取的数据
+     * @param <T>
+     * @return
+     */
+    public static <T> CommonResult<T> success(T data) {
+        return new CommonResult<T>(ResultCode.SUCCESS.getCode(), ResultCode.SUCCESS.getMessage(), data);
+    }
+
+    /**
+     * 成功返回结果
+     *
+     * @param data    获取的数据
+     * @param message 提示信息
+     * @param <T>
+     * @return
+     */
+    public static <T> CommonResult<T> success(T data, String message) {
+        return new CommonResult<T>(ResultCode.SUCCESS.getCode(), message, data);
+    }
+
+    /**
+     * 失败返回结果
+     *
+     * @param errorCode 错误码
+     * @param <T>
+     * @return
+     */
+    public static <T> CommonResult<T> failed(IErrorCode errorCode) {
+        return new CommonResult<T>(errorCode.getCode(), errorCode.getMessage(), null);
+    }
+
+    /**
+     * 失败返回结果
+     *
+     * @param errorCode 错误码
+     * @param message   错误信息
+     * @param <T>
+     * @return
+     */
+    public static <T> CommonResult<T> failed(IErrorCode errorCode, String message) {
+        return new CommonResult<T>(errorCode.getCode(), message, null);
+    }
+
+    /**
+     * 失败返回结果
+     *
+     * @param message 提示信息
+     * @param <T>
+     * @return
+     */
+    public static <T> CommonResult<T> failed(String message) {
+        return new CommonResult<T>(ResultCode.FAILED.getCode(), message, null);
+    }
+
+    /**
+     * 失败返回结果
+     *
+     * @param <T>
+     * @return
+     */
+    public static <T> CommonResult<T> failed() {
+        return failed(ResultCode.FAILED);
+    }
+
+    /**
+     * 参数验证失败返回结果
+     *
+     * @param <T>
+     * @return
+     */
+    public static <T> CommonResult<T> validateFailed() {
+        return failed(ResultCode.VALIDATE_FAILED);
+    }
+
+    /**
+     * 参数验证失败返回结果
+     *
+     * @param message 提示信息
+     * @param <T>
+     * @return
+     */
+    public static <T> CommonResult<T> validateFailed(String message) {
+        return new CommonResult<T>(ResultCode.VALIDATE_FAILED.getCode(), message, null);
+    }
+
+    /**
+     * 未登录返回结果
+     *
+     * @param data
+     * @param <T>
+     * @return
+     */
+    public static <T> CommonResult<T> unauthorized(T data) {
+        return new CommonResult<T>(ResultCode.UNAUTHORIZED.getCode(), ResultCode.UNAUTHORIZED.getMessage(), data);
+    }
+
+    /**
+     * 未授权返回结果
+     *
+     * @param data
+     * @param <T>
+     * @return
+     */
+    public static <T> CommonResult<T> forbidden(T data) {
+        return new CommonResult<T>(ResultCode.FORBIDDEN.getCode(), ResultCode.FORBIDDEN.getMessage(), data);
+    }
+
+    public long getCode() {
+        return code;
+    }
+
+    public void setCode(long code) {
+        this.code = code;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    public void setMessage(String message) {
+        this.message = message;
+    }
+
+    public T getData() {
+        return data;
+    }
+
+    public void setData(T data) {
+        this.data = data;
+    }
+
+}

+ 14 - 0
common/src/main/java/com/lantone/common/api/IErrorCode.java

@@ -0,0 +1,14 @@
+package com.lantone.common.api;
+
+/**
+ * @Description: 封装API的错误码
+ * @author: rengb
+ * @time: 2021/1/5 18:27
+ */
+public interface IErrorCode {
+
+    long getCode();
+
+    String getMessage();
+
+}

+ 32 - 0
common/src/main/java/com/lantone/common/api/ResultCode.java

@@ -0,0 +1,32 @@
+package com.lantone.common.api;
+
+/**
+ * @Description: 枚举了一些常用API操作码
+ * @author: rengb
+ * @time: 2021/1/5 18:27
+ */
+public enum ResultCode implements IErrorCode {
+
+    SUCCESS(200, "操作成功"),
+    FAILED(500, "操作失败"),
+    VALIDATE_FAILED(404, "参数检验失败"),
+    UNAUTHORIZED(401, "暂未登录或token已经过期"),
+    FORBIDDEN(403, "没有相关权限");
+
+    private long code;
+    private String message;
+
+    private ResultCode(long code, String message) {
+        this.code = code;
+        this.message = message;
+    }
+
+    public long getCode() {
+        return code;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+}

+ 68 - 0
common/src/main/java/com/lantone/common/config/BaseRedisConfig.java

@@ -0,0 +1,68 @@
+package com.lantone.common.config;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.PropertyAccessor;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
+import com.lantone.common.service.RedisService;
+import com.lantone.common.service.impl.RedisServiceImpl;
+import org.springframework.context.annotation.Bean;
+import org.springframework.data.redis.cache.RedisCacheConfiguration;
+import org.springframework.data.redis.cache.RedisCacheManager;
+import org.springframework.data.redis.cache.RedisCacheWriter;
+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;
+
+/**
+ * @Description: Redis基础配置
+ * @author: rengb
+ * @time: 2021/1/5 18:27
+ */
+public class BaseRedisConfig {
+
+    @Bean
+    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
+        RedisSerializer<Object> serializer = redisSerializer();
+        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
+        redisTemplate.setConnectionFactory(redisConnectionFactory);
+        redisTemplate.setKeySerializer(new StringRedisSerializer());
+        redisTemplate.setValueSerializer(serializer);
+        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
+        redisTemplate.setHashValueSerializer(serializer);
+        redisTemplate.afterPropertiesSet();
+        return redisTemplate;
+    }
+
+    @Bean
+    public RedisSerializer<Object> redisSerializer() {
+        //创建JSON序列化器
+        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
+        ObjectMapper objectMapper = new ObjectMapper();
+        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
+        //必须设置,否则无法将JSON转化为对象,会转化成Map类型
+        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
+        serializer.setObjectMapper(objectMapper);
+        return serializer;
+    }
+
+    @Bean
+    public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
+        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
+        //设置Redis缓存有效期为1天
+        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
+                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer())).entryTtl(Duration.ofDays(1));
+        return new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);
+    }
+
+    @Bean
+    public RedisService redisService() {
+        return new RedisServiceImpl();
+    }
+
+}

+ 55 - 0
common/src/main/java/com/lantone/common/constant/AuthConstant.java

@@ -0,0 +1,55 @@
+package com.lantone.common.constant;
+
+/**
+ * @Description: 权限相关常量定义
+ * @author: rengb
+ * @time: 2021/1/5 18:27
+ */
+public interface AuthConstant {
+
+    /**
+     * JWT存储权限前缀
+     */
+    String AUTHORITY_PREFIX = "ROLE_";
+
+    /**
+     * JWT存储权限属性
+     */
+    String AUTHORITY_CLAIM_NAME = "authorities";
+
+    /**
+     * 后台管理client_id
+     */
+    String ADMIN_CLIENT_ID = "admin-app";
+
+    /**
+     * 前台商城client_id
+     */
+    String PORTAL_CLIENT_ID = "portal-app";
+
+    /**
+     * 后台管理接口路径匹配
+     */
+    String ADMIN_URL_PATTERN = "/mall-admin/**";
+
+    /**
+     * Redis缓存权限规则key
+     */
+    String RESOURCE_ROLES_MAP_KEY = "auth:resourceRolesMap";
+
+    /**
+     * 认证信息Http请求头
+     */
+    String JWT_TOKEN_HEADER = "Authorization";
+
+    /**
+     * JWT令牌前缀
+     */
+    String JWT_TOKEN_PREFIX = "Bearer ";
+
+    /**
+     * 用户信息Http请求头
+     */
+    String USER_TOKEN_HEADER = "user";
+
+}

+ 26 - 0
common/src/main/java/com/lantone/common/domain/UserDto.java

@@ -0,0 +1,26 @@
+package com.lantone.common.domain;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+/**
+ * @Description: 登录用户信息
+ * @author: rengb
+ * @time: 2021/1/5 18:27
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@NoArgsConstructor
+public class UserDto {
+
+    private Long id;
+    private String username;
+    private String password;
+    private Integer status;
+    private String clientId;
+    private List<String> roles;
+
+}

+ 184 - 0
common/src/main/java/com/lantone/common/service/RedisService.java

@@ -0,0 +1,184 @@
+package com.lantone.common.service;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @Description: redis操作Service
+ * @author: rengb
+ * @time: 2021/1/5 18:27
+ */
+public interface RedisService {
+
+    /**
+     * 保存属性
+     */
+    void set(String key, Object value, long time);
+
+    /**
+     * 保存属性
+     */
+    void set(String key, Object value);
+
+    /**
+     * 获取属性
+     */
+    Object get(String key);
+
+    /**
+     * 删除属性
+     */
+    Boolean del(String key);
+
+    /**
+     * 批量删除属性
+     */
+    Long del(List<String> keys);
+
+    /**
+     * 设置过期时间
+     */
+    Boolean expire(String key, long time);
+
+    /**
+     * 获取过期时间
+     */
+    Long getExpire(String key);
+
+    /**
+     * 判断是否有该属性
+     */
+    Boolean hasKey(String key);
+
+    /**
+     * 按delta递增
+     */
+    Long incr(String key, long delta);
+
+    /**
+     * 按delta递减
+     */
+    Long decr(String key, long delta);
+
+    /**
+     * 获取Hash结构中的属性
+     */
+    Object hGet(String key, String hashKey);
+
+    /**
+     * 向Hash结构中放入一个属性
+     */
+    Boolean hSet(String key, String hashKey, Object value, long time);
+
+    /**
+     * 向Hash结构中放入一个属性
+     */
+    void hSet(String key, String hashKey, Object value);
+
+    /**
+     * 直接获取整个Hash结构
+     */
+    Map<Object, Object> hGetAll(String key);
+
+    /**
+     * 直接设置整个Hash结构
+     */
+    Boolean hSetAll(String key, Map<String, Object> map, long time);
+
+    /**
+     * 直接设置整个Hash结构
+     */
+    void hSetAll(String key, Map<String, ?> map);
+
+    /**
+     * 删除Hash结构中的属性
+     */
+    void hDel(String key, Object... hashKey);
+
+    /**
+     * 判断Hash结构中是否有该属性
+     */
+    Boolean hHasKey(String key, String hashKey);
+
+    /**
+     * Hash结构中属性递增
+     */
+    Long hIncr(String key, String hashKey, Long delta);
+
+    /**
+     * Hash结构中属性递减
+     */
+    Long hDecr(String key, String hashKey, Long delta);
+
+    /**
+     * 获取Set结构
+     */
+    Set<Object> sMembers(String key);
+
+    /**
+     * 向Set结构中添加属性
+     */
+    Long sAdd(String key, Object... values);
+
+    /**
+     * 向Set结构中添加属性
+     */
+    Long sAdd(String key, long time, Object... values);
+
+    /**
+     * 是否为Set中的属性
+     */
+    Boolean sIsMember(String key, Object value);
+
+    /**
+     * 获取Set结构的长度
+     */
+    Long sSize(String key);
+
+    /**
+     * 删除Set结构中的属性
+     */
+    Long sRemove(String key, Object... values);
+
+    /**
+     * 获取List结构中的属性
+     */
+    List<Object> lRange(String key, long start, long end);
+
+    /**
+     * 获取List结构的长度
+     */
+    Long lSize(String key);
+
+    /**
+     * 根据索引获取List中的属性
+     */
+    Object lIndex(String key, long index);
+
+    /**
+     * 向List结构中添加属性
+     */
+    Long lPush(String key, Object value);
+
+    /**
+     * 向List结构中添加属性
+     */
+    Long lPush(String key, Object value, long time);
+
+    /**
+     * 向List结构中批量添加属性
+     */
+    Long lPushAll(String key, Object... values);
+
+    /**
+     * 向List结构中批量添加属性
+     */
+    Long lPushAll(String key, Long time, Object... values);
+
+    /**
+     * 从List结构中移除属性
+     */
+    Long lRemove(String key, long count, Object value);
+
+}

+ 200 - 0
common/src/main/java/com/lantone/common/service/impl/RedisServiceImpl.java

@@ -0,0 +1,200 @@
+package com.lantone.common.service.impl;
+
+import com.lantone.common.service.RedisService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @Description: redis操作实现类
+ * @author: rengb
+ * @time: 2021/1/5 18:27
+ */
+public class RedisServiceImpl implements RedisService {
+
+    @Autowired
+    private RedisTemplate<String, Object> redisTemplate;
+
+    @Override
+    public void set(String key, Object value, long time) {
+        redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
+    }
+
+    @Override
+    public void set(String key, Object value) {
+        redisTemplate.opsForValue().set(key, value);
+    }
+
+    @Override
+    public Object get(String key) {
+        return redisTemplate.opsForValue().get(key);
+    }
+
+    @Override
+    public Boolean del(String key) {
+        return redisTemplate.delete(key);
+    }
+
+    @Override
+    public Long del(List<String> keys) {
+        return redisTemplate.delete(keys);
+    }
+
+    @Override
+    public Boolean expire(String key, long time) {
+        return redisTemplate.expire(key, time, TimeUnit.SECONDS);
+    }
+
+    @Override
+    public Long getExpire(String key) {
+        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
+    }
+
+    @Override
+    public Boolean hasKey(String key) {
+        return redisTemplate.hasKey(key);
+    }
+
+    @Override
+    public Long incr(String key, long delta) {
+        return redisTemplate.opsForValue().increment(key, delta);
+    }
+
+    @Override
+    public Long decr(String key, long delta) {
+        return redisTemplate.opsForValue().increment(key, -delta);
+    }
+
+    @Override
+    public Object hGet(String key, String hashKey) {
+        return redisTemplate.opsForHash().get(key, hashKey);
+    }
+
+    @Override
+    public Boolean hSet(String key, String hashKey, Object value, long time) {
+        redisTemplate.opsForHash().put(key, hashKey, value);
+        return expire(key, time);
+    }
+
+    @Override
+    public void hSet(String key, String hashKey, Object value) {
+        redisTemplate.opsForHash().put(key, hashKey, value);
+    }
+
+    @Override
+    public Map<Object, Object> hGetAll(String key) {
+        return redisTemplate.opsForHash().entries(key);
+    }
+
+    @Override
+    public Boolean hSetAll(String key, Map<String, Object> map, long time) {
+        redisTemplate.opsForHash().putAll(key, map);
+        return expire(key, time);
+    }
+
+    @Override
+    public void hSetAll(String key, Map<String, ?> map) {
+        redisTemplate.opsForHash().putAll(key, map);
+    }
+
+    @Override
+    public void hDel(String key, Object... hashKey) {
+        redisTemplate.opsForHash().delete(key, hashKey);
+    }
+
+    @Override
+    public Boolean hHasKey(String key, String hashKey) {
+        return redisTemplate.opsForHash().hasKey(key, hashKey);
+    }
+
+    @Override
+    public Long hIncr(String key, String hashKey, Long delta) {
+        return redisTemplate.opsForHash().increment(key, hashKey, delta);
+    }
+
+    @Override
+    public Long hDecr(String key, String hashKey, Long delta) {
+        return redisTemplate.opsForHash().increment(key, hashKey, -delta);
+    }
+
+    @Override
+    public Set<Object> sMembers(String key) {
+        return redisTemplate.opsForSet().members(key);
+    }
+
+    @Override
+    public Long sAdd(String key, Object... values) {
+        return redisTemplate.opsForSet().add(key, values);
+    }
+
+    @Override
+    public Long sAdd(String key, long time, Object... values) {
+        Long count = redisTemplate.opsForSet().add(key, values);
+        expire(key, time);
+        return count;
+    }
+
+    @Override
+    public Boolean sIsMember(String key, Object value) {
+        return redisTemplate.opsForSet().isMember(key, value);
+    }
+
+    @Override
+    public Long sSize(String key) {
+        return redisTemplate.opsForSet().size(key);
+    }
+
+    @Override
+    public Long sRemove(String key, Object... values) {
+        return redisTemplate.opsForSet().remove(key, values);
+    }
+
+    @Override
+    public List<Object> lRange(String key, long start, long end) {
+        return redisTemplate.opsForList().range(key, start, end);
+    }
+
+    @Override
+    public Long lSize(String key) {
+        return redisTemplate.opsForList().size(key);
+    }
+
+    @Override
+    public Object lIndex(String key, long index) {
+        return redisTemplate.opsForList().index(key, index);
+    }
+
+    @Override
+    public Long lPush(String key, Object value) {
+        return redisTemplate.opsForList().rightPush(key, value);
+    }
+
+    @Override
+    public Long lPush(String key, Object value, long time) {
+        Long index = redisTemplate.opsForList().rightPush(key, value);
+        expire(key, time);
+        return index;
+    }
+
+    @Override
+    public Long lPushAll(String key, Object... values) {
+        return redisTemplate.opsForList().rightPushAll(key, values);
+    }
+
+    @Override
+    public Long lPushAll(String key, Long time, Object... values) {
+        Long count = redisTemplate.opsForList().rightPushAll(key, values);
+        expire(key, time);
+        return count;
+    }
+
+    @Override
+    public Long lRemove(String key, long count, Object value) {
+        return redisTemplate.opsForList().remove(key, count, value);
+    }
+
+}

+ 15 - 13
gateway-service/pom.xml

@@ -15,9 +15,8 @@
 
     <dependencies>
         <dependency>
-            <groupId>com.diagbot</groupId>
+            <groupId>com.lantone</groupId>
             <artifactId>common</artifactId>
-            <version>0.0.1-SNAPSHOT</version>
         </dependency>
         <dependency>
             <groupId>org.springframework.cloud</groupId>
@@ -35,22 +34,25 @@
             <groupId>org.projectlombok</groupId>
             <artifactId>lombok</artifactId>
         </dependency>
-        <!--security-->
         <dependency>
-            <groupId>org.springframework.cloud</groupId>
-            <artifactId>spring-cloud-starter-oauth2</artifactId>
-            <version>2.2.0.RELEASE</version>
+            <groupId>org.springframework.security</groupId>
+            <artifactId>spring-security-config</artifactId>
         </dependency>
-
-        <!--redis设置-->
         <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-data-redis</artifactId>
+            <groupId>org.springframework.security</groupId>
+            <artifactId>spring-security-oauth2-resource-server</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.security</groupId>
+            <artifactId>spring-security-oauth2-client</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.security</groupId>
+            <artifactId>spring-security-oauth2-jose</artifactId>
         </dependency>
-
         <dependency>
-            <groupId>org.apache.commons</groupId>
-            <artifactId>commons-pool2</artifactId>
+            <groupId>com.nimbusds</groupId>
+            <artifactId>nimbus-jose-jwt</artifactId>
         </dependency>
     </dependencies>
 

+ 106 - 0
gateway-service/src/main/java/com/lantone/authorization/AuthorizationManager.java

@@ -0,0 +1,106 @@
+package com.lantone.authorization;
+
+import cn.hutool.core.convert.Convert;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONUtil;
+import com.lantone.common.constant.AuthConstant;
+import com.lantone.common.domain.UserDto;
+import com.lantone.config.IgnoreUrlsConfig;
+import com.nimbusds.jose.JWSObject;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.security.authorization.AuthorizationDecision;
+import org.springframework.security.authorization.ReactiveAuthorizationManager;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.web.server.authorization.AuthorizationContext;
+import org.springframework.stereotype.Component;
+import org.springframework.util.AntPathMatcher;
+import org.springframework.util.PathMatcher;
+import reactor.core.publisher.Mono;
+
+import java.net.URI;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * @Description: 鉴权管理器,用于判断是否有资源的访问权限
+ * @author: rengb
+ * @time: 2021/1/5 18:27
+ */
+@Component
+public class AuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {
+
+    @Autowired
+    private RedisTemplate<String, Object> redisTemplate;
+    @Autowired
+    private IgnoreUrlsConfig ignoreUrlsConfig;
+
+    @Override
+    public Mono<AuthorizationDecision> check(Mono<Authentication> mono, AuthorizationContext authorizationContext) {
+        ServerHttpRequest request = authorizationContext.getExchange().getRequest();
+        URI uri = request.getURI();
+        PathMatcher pathMatcher = new AntPathMatcher();
+        //白名单路径直接放行
+        List<String> ignoreUrls = ignoreUrlsConfig.getUrls();
+        for (String ignoreUrl : ignoreUrls) {
+            if (pathMatcher.match(ignoreUrl, uri.getPath())) {
+                return Mono.just(new AuthorizationDecision(true));
+            }
+        }
+        //对应跨域的预检请求直接放行
+        if (request.getMethod() == HttpMethod.OPTIONS) {
+            return Mono.just(new AuthorizationDecision(true));
+        }
+        //不同用户体系登录不允许互相访问
+        try {
+            String token = request.getHeaders().getFirst(AuthConstant.JWT_TOKEN_HEADER);
+            if (StrUtil.isEmpty(token)) {
+                return Mono.just(new AuthorizationDecision(false));
+            }
+            String realToken = token.replace(AuthConstant.JWT_TOKEN_PREFIX, "");
+            JWSObject jwsObject = JWSObject.parse(realToken);
+            String userStr = jwsObject.getPayload().toString();
+            UserDto userDto = JSONUtil.toBean(userStr, UserDto.class);
+            if (AuthConstant.ADMIN_CLIENT_ID.equals(userDto.getClientId()) && !pathMatcher.match(AuthConstant.ADMIN_URL_PATTERN, uri.getPath())) {
+                return Mono.just(new AuthorizationDecision(false));
+            }
+            if (AuthConstant.PORTAL_CLIENT_ID.equals(userDto.getClientId()) && pathMatcher.match(AuthConstant.ADMIN_URL_PATTERN, uri.getPath())) {
+                return Mono.just(new AuthorizationDecision(false));
+            }
+        } catch (ParseException e) {
+            e.printStackTrace();
+            return Mono.just(new AuthorizationDecision(false));
+        }
+        //非管理端路径直接放行
+        if (!pathMatcher.match(AuthConstant.ADMIN_URL_PATTERN, uri.getPath())) {
+            return Mono.just(new AuthorizationDecision(true));
+        }
+        //管理端路径需校验权限
+        Map<Object, Object> resourceRolesMap = redisTemplate.opsForHash().entries(AuthConstant.RESOURCE_ROLES_MAP_KEY);
+        Iterator<Object> iterator = resourceRolesMap.keySet().iterator();
+        List<String> authorities = new ArrayList<>();
+        while (iterator.hasNext()) {
+            String pattern = (String) iterator.next();
+            if (pathMatcher.match(pattern, uri.getPath())) {
+                authorities.addAll(Convert.toList(String.class, resourceRolesMap.get(pattern)));
+            }
+        }
+        authorities = authorities.stream().map(i -> i = AuthConstant.AUTHORITY_PREFIX + i).collect(Collectors.toList());
+        //认证通过且角色匹配的用户可访问当前路径
+        return mono
+                .filter(Authentication::isAuthenticated)
+                .flatMapIterable(Authentication::getAuthorities)
+                .map(GrantedAuthority::getAuthority)
+                .any(authorities::contains)
+                .map(AuthorizationDecision::new)
+                .defaultIfEmpty(new AuthorizationDecision(false));
+    }
+
+}

+ 38 - 0
gateway-service/src/main/java/com/lantone/component/RestAuthenticationEntryPoint.java

@@ -0,0 +1,38 @@
+package com.lantone.component;
+
+import cn.hutool.json.JSONUtil;
+import com.lantone.common.api.CommonResult;
+import org.springframework.core.io.buffer.DataBuffer;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
+import org.springframework.stereotype.Component;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+import java.nio.charset.Charset;
+
+/**
+ * @Description: 自定义返回结果:没有登录或token过期时
+ * @author: rengb
+ * @time: 2021/1/5 18:27
+ */
+@Component
+public class RestAuthenticationEntryPoint implements ServerAuthenticationEntryPoint {
+
+    @Override
+    public Mono<Void> commence(ServerWebExchange exchange, AuthenticationException e) {
+        ServerHttpResponse response = exchange.getResponse();
+        response.setStatusCode(HttpStatus.OK);
+        response.getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
+        response.getHeaders().set("Access-Control-Allow-Origin", "*");
+        response.getHeaders().set("Cache-Control", "no-cache");
+        String body = JSONUtil.toJsonStr(CommonResult.unauthorized(e.getMessage()));
+        DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(Charset.forName("UTF-8")));
+        return response.writeWith(Mono.just(buffer));
+    }
+    
+}

+ 38 - 0
gateway-service/src/main/java/com/lantone/component/RestfulAccessDeniedHandler.java

@@ -0,0 +1,38 @@
+package com.lantone.component;
+
+import cn.hutool.json.JSONUtil;
+import com.lantone.common.api.CommonResult;
+import org.springframework.core.io.buffer.DataBuffer;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler;
+import org.springframework.stereotype.Component;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+import java.nio.charset.Charset;
+
+/**
+ * @Description: 自定义返回结果:没有权限访问时
+ * @author: rengb
+ * @time: 2021/1/5 18:27
+ */
+@Component
+public class RestfulAccessDeniedHandler implements ServerAccessDeniedHandler {
+
+    @Override
+    public Mono<Void> handle(ServerWebExchange exchange, AccessDeniedException denied) {
+        ServerHttpResponse response = exchange.getResponse();
+        response.setStatusCode(HttpStatus.OK);
+        response.getHeaders().set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
+        response.getHeaders().set("Access-Control-Allow-Origin", "*");
+        response.getHeaders().set("Cache-Control", "no-cache");
+        String body = JSONUtil.toJsonStr(CommonResult.forbidden(denied.getMessage()));
+        DataBuffer buffer = response.bufferFactory().wrap(body.getBytes(Charset.forName("UTF-8")));
+        return response.writeWith(Mono.just(buffer));
+    }
+
+}

+ 0 - 28
gateway-service/src/main/java/com/lantone/config/ExclusionUrl.java

@@ -1,28 +0,0 @@
-package com.lantone.config;
-
-import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.stereotype.Component;
-
-import java.util.List;
-
-/**
- * @ClassName: ExclusionUrl
- * @Description:
- * @Author songxl
- * @Date 2021/5/29
- * @Version 1.0
- */
-@Component
-@ConfigurationProperties(prefix = "exclusion")
-public class ExclusionUrl {
-
-    private List<String> url;
-
-    public List<String> getUrl() {
-        return url;
-    }
-
-    public void setUrl(List<String> url) {
-        this.url = url;
-    }
-}

+ 31 - 0
gateway-service/src/main/java/com/lantone/config/GlobalCorsConfig.java

@@ -0,0 +1,31 @@
+package com.lantone.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;
+
+/**
+ * @Description: 全局跨域配置   注意:前端从网关进行调用时需要配置
+ * @author: rengb
+ * @time: 2021/1/5 18:27
+ */
+@Configuration
+public class GlobalCorsConfig {
+
+    @Bean
+    public CorsWebFilter corsFilter() {
+        CorsConfiguration config = new CorsConfiguration();
+        config.addAllowedMethod("*");
+        config.addAllowedOrigin("*");
+        config.addAllowedHeader("*");
+        config.setAllowCredentials(true);
+        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
+        source.registerCorsConfiguration("/**", config);
+
+        return new CorsWebFilter(source);
+    }
+
+}

+ 20 - 0
gateway-service/src/main/java/com/lantone/config/IgnoreUrlsConfig.java

@@ -0,0 +1,20 @@
+package com.lantone.config;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+/**
+ * 网关白名单配置
+ * Created by macro on 2020/6/17.
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Component
+@ConfigurationProperties(prefix="secure.ignore")
+public class IgnoreUrlsConfig {
+    private List<String> urls;
+}

+ 14 - 0
gateway-service/src/main/java/com/lantone/config/RedisConfig.java

@@ -0,0 +1,14 @@
+package com.lantone.config;
+
+import com.lantone.common.config.BaseRedisConfig;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @Description: Redis相关配置
+ * @author: rengb
+ * @time: 2021/1/5 18:27
+ */
+@Configuration
+public class RedisConfig extends BaseRedisConfig {
+
+}

+ 0 - 202
gateway-service/src/main/java/com/lantone/config/RedisConfigurer.java

@@ -1,202 +0,0 @@
-package com.lantone.config;
-
-import com.fasterxml.jackson.annotation.JsonAutoDetect;
-import com.fasterxml.jackson.annotation.PropertyAccessor;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.cache.CacheManager;
-import org.springframework.cache.annotation.CachingConfigurerSupport;
-import org.springframework.cache.annotation.EnableCaching;
-import org.springframework.cache.interceptor.KeyGenerator;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Primary;
-import org.springframework.data.redis.cache.RedisCacheConfiguration;
-import org.springframework.data.redis.cache.RedisCacheManager;
-import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
-import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
-import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
-import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
-import org.springframework.data.redis.core.RedisTemplate;
-import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
-import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
-import org.springframework.data.redis.serializer.RedisSerializationContext;
-import org.springframework.data.redis.serializer.StringRedisSerializer;
-
-import java.time.Duration;
-
-@Configuration
-@EnableCaching
-@Slf4j
-public class RedisConfigurer extends CachingConfigurerSupport {
-
-    @Value("${spring.redis.database.cache}")
-    private String databaseCache;
-    @Value("${spring.redis.database.token}")
-    private String databaseMr;
-    @Value("${spring.redis.host}")
-    private String host;
-    @Value("${spring.redis.password}")
-    private String password;
-    @Value("${spring.redis.port}")
-    private int port;
-    @Value("${spring.redis.timeout}")
-    private int timeout;
-    @Value("${spring.redis.lettuce.pool.max-active}")
-    private int maxActive;
-    @Value("${spring.redis.lettuce.pool.max-idle}")
-    private int maxIdle;
-    @Value("${spring.redis.lettuce.pool.max-wait}")
-    private long maxWaitMillis;
-    @Value("${spring.redis.lettuce.pool.min-idle}")
-    private int minIdle;
-
-    @Autowired
-    @Qualifier("factoryForCache")
-    private LettuceConnectionFactory lettuceConnectionFactory;
-
-    @Bean
-    public GenericObjectPoolConfig getRedisConfig() {
-        GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
-        poolConfig.setMaxTotal(maxActive);
-        poolConfig.setMaxIdle(maxIdle);
-        poolConfig.setMaxWaitMillis(maxWaitMillis);
-        poolConfig.setMinIdle(minIdle);
-        return poolConfig;
-    }
-
-    @Bean
-    @Override
-    public CacheManager cacheManager() {
-        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
-                // 设置 key为string序列化
-                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
-                // 设置value为json序列化
-                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(getSerializer()))
-                // 不缓存空值
-                .disableCachingNullValues();
-        RedisCacheManager cacheManager = RedisCacheManager.builder(lettuceConnectionFactory)
-                .cacheDefaults(redisCacheConfiguration)
-                .transactionAware()
-                .build();
-        cacheManager.afterPropertiesSet();
-        log.info("RedisCacheManager config success");
-        return cacheManager;
-    }
-
-    @Bean(name = "springSessionDefaultRedisSerializer")
-    public GenericJackson2JsonRedisSerializer getGenericJackson2JsonRedisSerializer() {
-        return new GenericJackson2JsonRedisSerializer();
-    }
-
-    /**
-     * 缓存使用的redis
-     *
-     * @return
-     */
-    @Bean("factoryForCache")
-    @Primary
-    public LettuceConnectionFactory redisConnectionFactory() {
-        return getRedisConnectionFactory(Integer.valueOf(databaseCache));
-    }
-
-    @Bean
-    public RedisTemplate<String, Object> redisTemplate() {
-        return getRedisTemplate(lettuceConnectionFactory);
-    }
-
-    private Jackson2JsonRedisSerializer getSerializer() {
-        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);
-        return jackson2JsonRedisSerializer;
-    }
-
-    @Bean
-    @Override
-    public KeyGenerator keyGenerator() {
-        //  设置自动key的生成规则,配置spring boot的注解,进行方法级别的缓存
-        // 使用:进行分割,可以很多显示出层级关系
-        // 这里其实就是new了一个KeyGenerator对象
-        return (target, method, params) -> {
-            StringBuilder sb = new StringBuilder();
-            sb.append(target.getClass().getName());
-            sb.append(":");
-            sb.append(method.getName());
-            for (Object obj : params) {
-                sb.append(":" + String.valueOf(obj));
-            }
-            String rsToUse = String.valueOf(sb);
-            log.info("自动生成Redis Key -> [{}]", rsToUse);
-            return rsToUse;
-        };
-    }
-
-    /**
-     * Token使用的redis
-     *
-     * @return
-     */
-    @Bean("factoryForToken")
-    public LettuceConnectionFactory redisConnectionFactoryForToken() {
-        return getRedisConnectionFactory(Integer.valueOf(databaseMr));
-    }
-
-    @Bean(name = "redisTemplateForToken")
-    public RedisTemplate<String, Object> redisTemplateForToken(@Qualifier("factoryForToken") LettuceConnectionFactory factory) {
-        return getRedisTemplate(factory);
-    }
-
-    /**
-     * 表字段和注释使用的redis
-     *
-     * @return
-     */
-    @Bean("factoryForTable")
-    public LettuceConnectionFactory redisConnectionFactoryForTable() {
-        return getRedisConnectionFactory(Integer.valueOf(databaseMr));
-    }
-
-    @Bean(name = "redisTemplateForTable")
-    public RedisTemplate<String, Object> redisTemplateForTable(@Qualifier("factoryForTable") LettuceConnectionFactory factory) {
-        return getRedisTemplate(factory);
-    }
-
-
-    private LettuceConnectionFactory getRedisConnectionFactory(Integer database) {
-        RedisStandaloneConfiguration connection = new RedisStandaloneConfiguration();
-        connection.setHostName(host);
-        connection.setPort(port);
-        connection.setPassword(password);
-        connection.setDatabase(database);
-        GenericObjectPoolConfig poolConfig = getRedisConfig();
-        LettuceClientConfiguration builder = LettucePoolingClientConfiguration.builder()
-                .commandTimeout(Duration.ofMillis(timeout))
-                .poolConfig(poolConfig)
-                .shutdownTimeout(Duration.ZERO)
-                .build();
-        LettuceConnectionFactory factory = new LettuceConnectionFactory(connection, builder);
-        return factory;
-    }
-
-    private RedisTemplate<String, Object> getRedisTemplate(LettuceConnectionFactory factory) {
-        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
-        redisTemplate.setConnectionFactory(factory);
-
-        // value值的序列化
-        redisTemplate.setValueSerializer(getSerializer());
-        redisTemplate.setHashValueSerializer(getSerializer());
-        // key的序列化采用StringRedisSerializer
-        redisTemplate.setKeySerializer(new StringRedisSerializer());
-        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
-        redisTemplate.afterPropertiesSet();
-        return redisTemplate;
-    }
-}
- 

+ 49 - 8
gateway-service/src/main/java/com/lantone/config/ResourceServerConfig.java

@@ -1,27 +1,68 @@
 package com.lantone.config;
 
+import cn.hutool.core.util.ArrayUtil;
+import com.lantone.authorization.AuthorizationManager;
+import com.lantone.common.constant.AuthConstant;
+import com.lantone.component.RestAuthenticationEntryPoint;
+import com.lantone.component.RestfulAccessDeniedHandler;
+import com.lantone.filter.IgnoreUrlsRemoveJwtFilter;
 import lombok.AllArgsConstructor;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
 import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
+import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
 import org.springframework.security.config.web.server.ServerHttpSecurity;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
+import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
+import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter;
 import org.springframework.security.web.server.SecurityWebFilterChain;
+import reactor.core.publisher.Mono;
 
 /**
- * @ClassName: ResourceServerConfig
- * @Description:资源服务器配置
- * @Author songxl
- * @Date 2021/5/29
- * @Version 1.0
+ * @Description: 资源服务器配置
+ * @author: rengb
+ * @time: 2021/1/5 18:27
  */
 @AllArgsConstructor
 @Configuration
 @EnableWebFluxSecurity
 public class ResourceServerConfig {
 
+    private final AuthorizationManager authorizationManager;
+    private final IgnoreUrlsConfig ignoreUrlsConfig;
+    private final RestfulAccessDeniedHandler restfulAccessDeniedHandler;
+    private final RestAuthenticationEntryPoint restAuthenticationEntryPoint;
+    private final IgnoreUrlsRemoveJwtFilter ignoreUrlsRemoveJwtFilter;
+
     @Bean
-    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
-        http.csrf().disable();
+    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
+        http.oauth2ResourceServer().jwt()
+                .jwtAuthenticationConverter(jwtAuthenticationConverter());
+        //自定义处理JWT请求头过期或签名错误的结果
+        http.oauth2ResourceServer().authenticationEntryPoint(restAuthenticationEntryPoint);
+        //对白名单路径,直接移除JWT请求头
+        http.addFilterBefore(ignoreUrlsRemoveJwtFilter, SecurityWebFiltersOrder.AUTHENTICATION);
+        http.authorizeExchange()
+                .pathMatchers(ArrayUtil.toArray(ignoreUrlsConfig.getUrls(), String.class)).permitAll()//白名单配置
+                .anyExchange().access(authorizationManager)//鉴权管理器配置
+                .and().exceptionHandling()
+                .accessDeniedHandler(restfulAccessDeniedHandler)//处理未授权
+                .authenticationEntryPoint(restAuthenticationEntryPoint)//处理未认证
+                .and().csrf().disable();
         return http.build();
     }
-}
+
+    @Bean
+    public Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtAuthenticationConverter() {
+        JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
+        jwtGrantedAuthoritiesConverter.setAuthorityPrefix(AuthConstant.AUTHORITY_PREFIX);
+        jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName(AuthConstant.AUTHORITY_CLAIM_NAME);
+        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
+        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);
+        return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter);
+    }
+
+}

+ 0 - 13
gateway-service/src/main/java/com/lantone/facade/TokenFacade.java

@@ -1,13 +0,0 @@
-package com.lantone.facade;
-
-import com.lantone.service.impl.SysTokenServiceImpl;
-import org.springframework.stereotype.Component;
-
-/**
- * @Description: token实现
- * @author: gaodm
- * @time: 2018/10/29 14:24
- */
-@Component
-public class TokenFacade extends SysTokenServiceImpl {
-}

+ 26 - 124
gateway-service/src/main/java/com/lantone/filter/AuthGlobalFilter.java

@@ -1,151 +1,53 @@
 package com.lantone.filter;
 
-import com.alibaba.fastjson.JSON;
-import com.auth0.jwt.interfaces.Claim;
-import com.auth0.jwt.interfaces.DecodedJWT;
-import com.diagbot.util.JwtUtil;
-import com.lantone.config.ExclusionUrl;
-import com.lantone.facade.TokenFacade;
-import com.lantone.security.dto.Result;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
+import cn.hutool.core.util.StrUtil;
+import com.lantone.common.constant.AuthConstant;
+import com.nimbusds.jose.JWSObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.cloud.gateway.filter.GatewayFilterChain;
 import org.springframework.cloud.gateway.filter.GlobalFilter;
 import org.springframework.core.Ordered;
-import org.springframework.core.io.buffer.DataBuffer;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.MediaType;
 import org.springframework.http.server.reactive.ServerHttpRequest;
-import org.springframework.http.server.reactive.ServerHttpResponse;
 import org.springframework.stereotype.Component;
-import org.springframework.util.StringUtils;
 import org.springframework.web.server.ServerWebExchange;
 import reactor.core.publisher.Mono;
 
-import java.util.List;
-import java.util.Map;
+import java.text.ParseException;
 
 /**
- * @ClassName: AuthGlobalFilter
  * @Description: 将登录用户的JWT转化成用户信息的全局过滤器
- * @Author songxl
- * @Date 2021/6/29
- * @Version 1.0
+ * @author: rengb
+ * @time: 2021/1/5 18:27
  */
 @Component
-@Slf4j
 public class AuthGlobalFilter implements GlobalFilter, Ordered {
-    private static final String URI = "/v2/api-docs";
-    @Autowired
-    private ExclusionUrl exclusionUrl;
-    @Autowired
-    TokenFacade tokenFacade;
-    /**
-     * @Author songxl
-     * @Description 全局过滤器过滤方法
-     * @Date  2021/6/29
-     * @Param [exchange, chain]
-     * @Return reactor.core.publisher.Mono<java.lang.Void>
-     * @MethodName filter
-     */
+
+    private static Logger LOGGER = LoggerFactory.getLogger(AuthGlobalFilter.class);
+
     @Override
     public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
-
-        ServerHttpRequest request = exchange.getRequest();
-        ServerHttpResponse response = exchange.getResponse();
-        //1.swagger请求不要拦截
-        String path = request.getURI().getPath();
-        log.info("request path:{}", path);
-        if (StringUtils.endsWithIgnoreCase(path, URI)) {
-            return chain.filter(exchange);
-        }
-        //2、白名单无需权限判断
-        if (isExclusionUrl(path)) {
+        String token = exchange.getRequest().getHeaders().getFirst(AuthConstant.JWT_TOKEN_HEADER);
+        if (StrUtil.isEmpty(token)) {
             return chain.filter(exchange);
         }
-        String headerToken = request.getHeaders().getFirst("Authorization");
-        //3、只要带上了token, 就需要判断Token是否有效
-        if (!StringUtils.isEmpty(headerToken)) {
-            headerToken = headerToken.replaceFirst("Bearer ", "");
-            if (!tokenFacade.verifyToken(headerToken, 1)) {
-                return getVoidMono(response, 401, "token无效");
-            }
-            //4、判断请求的URL是否有权限
-            boolean permission = hasPermission(headerToken, path);
-            if (!permission) {
-                return getVoidMono(response, 403, "无访问权限");
-            }
-            return chain.filter(exchange);
+        try {
+            //从token中解析用户信息并设置到Header中去
+            String realToken = token.replace(AuthConstant.JWT_TOKEN_PREFIX, "");
+            JWSObject jwsObject = JWSObject.parse(realToken);
+            String userStr = jwsObject.getPayload().toString();
+            LOGGER.info("AuthGlobalFilter.filter() user:{}", userStr);
+            ServerHttpRequest request = exchange.getRequest().mutate().header(AuthConstant.USER_TOKEN_HEADER, userStr).build();
+            exchange = exchange.mutate().request(request).build();
+        } catch (ParseException e) {
+            e.printStackTrace();
         }
-        return getVoidMono(response, 401, "请求token为空");
+        return chain.filter(exchange);
     }
 
     @Override
     public int getOrder() {
         return 0;
     }
-    /**
-     * @Author songxl
-     * @Description 白名单请求判断
-     * @Date  2021/7/1
-     * @Param [path]
-     * @Return boolean
-     * @MethodName isExclusionUrl
-     */
-    private boolean isExclusionUrl(String path) {
-        List<String> exclusions = exclusionUrl.getUrl();
-        if (exclusions.size() == 0) {
-            return false;
-        }
-        for (String action:exclusions){
-            if (StringUtils.endsWithIgnoreCase(path, action)) {
-                return true;
-            }
-        }
-        return false;
-    }
-    /**
-     * @Author songxl
-     * @Description 从token获取权限信息,判断token是否有该请求权限
-     * @Date  2021/7/1
-     * @Param [headerToken, path]
-     * @Return boolean
-     * @MethodName hasPermission
-     */
-    private boolean hasPermission(String headerToken, String path) {
-        String url, method;
-        try {
-            DecodedJWT jwt = JwtUtil.decodedJWT(headerToken);
-            Map<String, Claim> claims = jwt.getClaims();
-            Claim claim = (Claim) claims.get("authorities");
-            String[] permissinos = claim.asArray(String.class);
-            for (Object permission : permissinos) {
-                String[] authority = (permission + "").split(";");
-                url = authority[0];
-                method = authority[1];
-                if (StringUtils.endsWithIgnoreCase(path, url)) {
-                    return true;
-                }
-            }
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-        return false;
-    }
-    /**
-     * @Author songxl
-     * @Description 请求返回拼装方法
-     * @Date  2021/7/1
-     * @Param [response, i, msg]
-     * @Return reactor.core.publisher.Mono<java.lang.Void>
-     * @MethodName getVoidMono
-     */
-    private Mono<Void> getVoidMono(ServerHttpResponse response, int i, String msg) {
-        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
-        response.setStatusCode(HttpStatus.OK);
-        Result failed = Result.failure(i, msg);
-        byte[] bits = JSON.toJSONString(failed).getBytes();
-        DataBuffer buffer = response.bufferFactory().wrap(bits);
-        return response.writeWith(Mono.just(buffer));
-    }
-}
+
+}

+ 46 - 0
gateway-service/src/main/java/com/lantone/filter/IgnoreUrlsRemoveJwtFilter.java

@@ -0,0 +1,46 @@
+package com.lantone.filter;
+
+import com.lantone.common.constant.AuthConstant;
+import com.lantone.config.IgnoreUrlsConfig;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.stereotype.Component;
+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;
+
+import java.net.URI;
+import java.util.List;
+
+/**
+ * @Description: 白名单路径访问时需要移除JWT请求头
+ * @author: rengb
+ * @time: 2021/1/5 18:27
+ */
+@Component
+public class IgnoreUrlsRemoveJwtFilter implements WebFilter {
+
+    @Autowired
+    private IgnoreUrlsConfig ignoreUrlsConfig;
+
+    @Override
+    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
+        ServerHttpRequest request = exchange.getRequest();
+        URI uri = request.getURI();
+        PathMatcher pathMatcher = new AntPathMatcher();
+        //白名单路径移除JWT请求头
+        List<String> ignoreUrls = ignoreUrlsConfig.getUrls();
+        for (String ignoreUrl : ignoreUrls) {
+            if (pathMatcher.match(ignoreUrl, uri.getPath())) {
+                request = exchange.getRequest().mutate().header(AuthConstant.JWT_TOKEN_HEADER, "").build();
+                exchange = exchange.mutate().request(request).build();
+                return chain.filter(exchange);
+            }
+        }
+        return chain.filter(exchange);
+    }
+
+}

+ 1 - 1
gateway-service/src/main/java/com/lantone/config/SwaggerHandler.java

@@ -1,4 +1,4 @@
-package com.lantone.config;
+package com.lantone.handler;
 
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.http.HttpStatus;

+ 0 - 16
gateway-service/src/main/java/com/lantone/security/dto/JwtStore.java

@@ -1,16 +0,0 @@
-package com.lantone.security.dto;
-
-import lombok.Getter;
-import lombok.Setter;
-
-/**
- * @Description:
- * @author: gaodm
- * @time: 2018/10/29 14:38
- */
-@Getter
-@Setter
-public class JwtStore {
-    private String accessToken;
-    private String refreshToken;
-}

+ 0 - 84
gateway-service/src/main/java/com/lantone/security/dto/Result.java

@@ -1,84 +0,0 @@
-package com.lantone.security.dto;
-
-import java.util.Map;
-
-/**
- * @ClassName: Result
- * @Description:
- * @Author songxl
- * @Date 2021/6/30
- * @Version 1.0
- */
-public class Result<T> {
-
-
-    private Integer code=200;
-    private String msg="操作成功";
-    private String description;
-    private T data;
-
-
-    public Integer getCode() {
-        return code;
-    }
-
-    public void setCode(Integer code) {
-        this.code = code;
-    }
-
-    public String getMsg() {
-        return msg;
-    }
-
-    public void setMsg(String msg) {
-        this.msg = msg;
-    }
-
-    public T getData() {
-        return data;
-    }
-
-    public Result setData(T data) {
-        this.data = data;
-        return this;
-    }
-
-    public String getDescription() {
-        return description;
-    }
-
-    public void setDescription(String description) {
-        this.description = description;
-    }
-
-
-
-    public Result() {
-    }
-
-    public static Result failure(int code, String msg) {
-        Result result = new Result();
-        result.setCode(code);
-        result.setMsg(msg);
-        return result;
-    }
-
-    public static Result ok(String msg) {
-        Result result = new Result();
-        result.setMsg(msg);
-        return result;
-    }
-
-    public static Result ok(Map<String, Object> map) {
-        Result result = new Result();
-        result.setData(map);
-        return result;
-    }
-
-    public static Result ok() {
-        return new Result();
-    }
-
-
-}
-

+ 0 - 40
gateway-service/src/main/java/com/lantone/service/SysTokenService.java

@@ -1,40 +0,0 @@
-package com.lantone.service;
-
-
-
-import java.util.List;
-
-/**
- * @Description: Token验证类
- * @author: gaodm
- * @time: 2018/10/29 13:35
- */
-public interface SysTokenService {
-
-
-    /**
-     * 验证token是否有效
-     *
-     * @param token 待验证的token
-     * @param type  1:accessToken,2:refreshToken
-     * @return
-     */
-    Boolean verifyToken(String token, Integer type);
-
-    /**
-     * 删除用户token
-     *
-     * @param userId 用户ID
-     * @return 删除是否成功
-     */
-    Boolean deleteToken(String userId);
-
-    /**
-     * 批量删除用户token
-     *
-     * @param userIds 用户列表
-     * @return 删除是否成功
-     */
-    Boolean deleteBatchToken(List<Long> userIds);
-
-}

+ 0 - 134
gateway-service/src/main/java/com/lantone/service/impl/SysTokenServiceImpl.java

@@ -1,134 +0,0 @@
-package com.lantone.service.impl;
-import com.diagbot.util.JwtUtil;
-import com.lantone.security.dto.JwtStore;
-import com.lantone.service.SysTokenService;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
-import org.springframework.dao.DataAccessException;
-import org.springframework.data.redis.connection.RedisConnection;
-import org.springframework.data.redis.core.RedisCallback;
-import org.springframework.data.redis.core.RedisTemplate;
-import org.springframework.stereotype.Service;
-
-import java.util.List;
-
-/**
- * @Description: Token验证类 实现
- * @author: gaodm
- * @time: 2018/10/29 13:34
- */
-@Slf4j
-@Service
-public class SysTokenServiceImpl implements SysTokenService {
-
-    @Autowired
-    @Qualifier("redisTemplateForToken")
-    RedisTemplate redisForToken;
-
-    private byte[] serializeKey(Object o) {
-        return redisForToken.getKeySerializer().serialize(o);
-    }
-
-    private byte[] serializeValue(Object o) {
-        return redisForToken.getValueSerializer().serialize(o);
-    }
-
-    private Object deserializeValue(byte[] b) {
-        return redisForToken.getValueSerializer().deserialize(b);
-    }
-
-    private byte[] getUserTokenKey(String userId) {
-        String userTokensFormat = "user_tokens_%s";
-        return serializeKey(String.format(userTokensFormat, userId));
-    }
-
-
-    /**
-     * 验证token是否有效
-     *
-     * @param token 待验证的token
-     * @param type  1:accessToken,2:refreshToken
-     * @return
-     */
-    @Override
-    public Boolean verifyToken(String token, Integer type) {
-        Boolean res = false;
-        if (null == token) {
-            return false;
-        }
-        String userId = JwtUtil.getUserId(token);
-        //从redis中取出
-        final byte[] redis_key = getUserTokenKey(userId);
-        JwtStore tokenStore = (JwtStore) redisForToken.execute(new RedisCallback<JwtStore>() {
-            @Override
-            public JwtStore doInRedis(RedisConnection connection) throws DataAccessException {
-                byte[] bytes = connection.get(redis_key);
-                if (bytes == null) {
-                    return null;
-                }
-                return (JwtStore) deserializeValue(bytes);
-            }
-        });
-
-        if (null != tokenStore) {
-            if (type == 1) {
-                if (null != tokenStore.getAccessToken() && tokenStore.getAccessToken().equals(token)) {
-                    res = true;
-                }
-            }
-
-            if (type == 2) {
-                if (null != tokenStore.getRefreshToken() && tokenStore.getRefreshToken().equals(token)) {
-                    res = true;
-                }
-            }
-        }
-
-        return res;
-    }
-
-    /**
-     * 删除用户token
-     *
-     * @param userId 用户ID
-     * @return 删除是否成功
-     */
-    @Override
-    public Boolean deleteToken(String userId) {
-        final byte[] redis_key = getUserTokenKey(userId);
-        Long l = (Long) redisForToken.execute(new RedisCallback<Long>() {
-            @Override
-            public Long doInRedis(RedisConnection connection) throws DataAccessException {
-                return connection.del(redis_key);
-            }
-        });
-        return l > 0;
-    }
-
-    /**
-     * 批量删除用户token
-     *
-     * @param userIds 用户列表
-     * @return 删除是否成功
-     */
-    @Override
-    public Boolean deleteBatchToken(List<Long> userIds) {
-        Long l = (Long) redisForToken.execute(new RedisCallback<Long>() {
-            @Override
-            public Long doInRedis(RedisConnection connection) throws DataAccessException {
-                connection.openPipeline();
-                Long cnt = 0L;
-                for (Long userId : userIds) {
-                    byte[] redis_key = getUserTokenKey(userId.toString());
-                    connection.del(redis_key);
-                    cnt++;
-                }
-                connection.closePipeline();
-                return cnt;
-            }
-        });
-        return l > 0;
-    }
-
-}

+ 0 - 12
gateway-service/src/main/resources/application.yml

@@ -1,12 +0,0 @@
-#白名单
-exclusion:
-  url:
-    - /sys/user/getJwt
-    - /sys/user/getJwtNoPass
-    - /sys/user/refreshJwt
-    - /sys/user/checkToken
-    - /sys/dictionaryInfo/getDictionary
-    - /oauth/token
-    - /oauth/check_token
-    - /cache/clear
-

+ 16 - 2
gateway-service/src/main/resources/bootstrap.yml

@@ -13,7 +13,7 @@ spring:
       discovery:
         locator:
           enabled: true
-#          lower-case-service-id: true
+      #          lower-case-service-id: true
       routes:
         - id: structure-center
           uri: lb://structure-center
@@ -59,4 +59,18 @@ management:
         include: '*'
   endpoint:
     health:
-      show-details: always
+      show-details: always
+
+secure:
+  ignore:
+    urls: #配置白名单路径
+      - "/doc.html"
+      - "/swagger-resources/**"
+      - "/swagger/**"
+      - "/**/v2/api-docs"
+      - "/**/*.js"
+      - "/**/*.css"
+      - "/**/*.png"
+      - "/**/*.ico"
+      - "/webjars/springfox-swagger-ui/**"
+      - "/actuator/**"

+ 33 - 2
pom.xml

@@ -6,7 +6,7 @@
     <parent>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-parent</artifactId>
-        <version>2.1.8.RELEASE</version>
+        <version>2.3.0.RELEASE</version>
         <relativePath/> <!-- lookup parent from repository -->
     </parent>
 
@@ -42,6 +42,7 @@
         <beetl.version>3.0.13.RELEASE</beetl.version>
         <lombok.version>1.18.8</lombok.version>
         <guava.version>28.1-jre</guava.version>
+        <hutool-all.version>5.4.0</hutool-all.version>
         <knife4j-micro-spring-boot-starter.version>2.0.4</knife4j-micro-spring-boot-starter.version>
         <commons-lang3.version>3.8.1</commons-lang3.version>
         <fastjson.version>1.2.62</fastjson.version>
@@ -49,6 +50,9 @@
         <commons-codec.version>1.11</commons-codec.version>
         <poi.version>4.0.1</poi.version>
         <commons-beanutils.version>1.9.3</commons-beanutils.version>
+        <nimbus-jose-jwt.version>8.16</nimbus-jose-jwt.version>
+        <spring-data-commons.version>2.3.0.RELEASE</spring-data-commons.version>
+        <jackson-databind.version>2.12.3</jackson-databind.version>
         <common.version>0.0.1-SNAPSHOT</common.version>
         <dblayer-mbg.version>0.0.1-SNAPSHOT</dblayer-mbg.version>
         <txlcn-tc.version>5.0.2.RELEASE</txlcn-tc.version>
@@ -154,13 +158,20 @@
                 <version>${txlcn-txmsg-netty.version}</version>
             </dependency>
 
-            <!-- 工具类jar -->
+            <!--guava Java工具包-->
             <dependency>
                 <groupId>com.google.guava</groupId>
                 <artifactId>guava</artifactId>
                 <version>${guava.version}</version>
             </dependency>
 
+            <!--Hutool Java工具包-->
+            <dependency>
+                <groupId>cn.hutool</groupId>
+                <artifactId>hutool-all</artifactId>
+                <version>${hutool-all.version}</version>
+            </dependency>
+
             <!-- Knife4j API文档生产工具 -->
             <dependency>
                 <groupId>com.github.xiaoymin</groupId>
@@ -213,6 +224,26 @@
                 <artifactId>commons-beanutils</artifactId>
                 <version>${commons-beanutils.version}</version>
             </dependency>
+
+            <!--JWT(Json Web Token)登录支持-->
+            <dependency>
+                <groupId>com.nimbusds</groupId>
+                <artifactId>nimbus-jose-jwt</artifactId>
+                <version>${nimbus-jose-jwt.version}</version>
+            </dependency>
+
+            <dependency>
+                <groupId>org.springframework.data</groupId>
+                <artifactId>spring-data-commons</artifactId>
+                <version>${spring-data-commons.version}</version>
+            </dependency>
+
+            <!-- JSON序列化/反序列化工具 -->
+            <dependency>
+                <groupId>com.fasterxml.jackson.core</groupId>
+                <artifactId>jackson-databind</artifactId>
+                <version>${jackson-databind.version}</version>
+            </dependency>
         </dependencies>
     </dependencyManagement>