微服务多模块:Springboot+Security+Redis+Gateway+OpenFeign+Nacos+JWT (附源码)仅需一招,520彻底拿捏你

这篇具有很好参考价值的文章主要介绍了微服务多模块:Springboot+Security+Redis+Gateway+OpenFeign+Nacos+JWT (附源码)仅需一招,520彻底拿捏你。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

  可能有些人会觉得这篇似曾相识,没错,这篇是由原文章进行二次开发的。

前阵子有些事情,但最近看到评论区说原文章最后实现的是单模块的验证,由于过去太久也懒得验证,所以重新写了一个完整的可以跑得动的一个。

OK,回到正题,以下是真正对应的微服务多模块的一个方法,使用到的技术有:基于微服务的Springboot+Security+Redis+Gateway+OpenFeign+Nacos+JWT

对使用到的微服务技术进行在项目中的说明:

Security:负责登录验证(文章中没有实现授权,在过滤器中直接返回null,如果想实现授权,可以在返回null的地方添加授权信息类似ROLE_ADMIN,同时在Security的配置文件那里添加授权信息即可)。

Redis:负责缓存token跟用户数据。

Gateway:对前端提供的接口,由它多个模块进行接口调用。

OpenFeign:提供给security查询数据库中的用户信息。

Nacos:注册服务中心,注册服务的信息,使OpenFeign可以调用其他服务模块。

注意:虽然是原文章的二次编写,但是很多都不同,建议直接跟着这篇走。

目录

1.项目结构

2.Common模块

pom.xml

2.1 RedisConfig

2.2 RedisUtil

2.3 ResponseUtil

2.4  TokenUtil

         2.5  CorConfig

3.model模块

3.1 pom

3.2 User

         3.3 UserFeign

4.service模块

  4.1 目录结构​编辑

  4.2 service_user模块

    4.2.1 pom.xml

    4.2.2 application.yml

    4.2.3 Service_UserApp启动类

4.3 其他service模块

5.spring_security模块

5.1 pom

5.2 DiyUserDetails(UserDetails)

5.3 WebSecurityConfig(WebSecurityConfigurerAdapter)

5.4 TokenOncePerRequestFilter(OncePerRequestFilter)

5.5 LoginAuthenticationEntryPoint(AuthenticationEntryPoint)

5.6 LoginInFailHandler(AuthenticationFailureHandler)

5.7 LoginInSuccessHandler(AuthenticationSuccessHandler)

5.8 LogOutSuccessHandler(LogoutSuccessHandler)

5.9 NothingAccessDeniedHandler(AccessDeniedHandler)

         5.10 MyUserDetailService(UserDetailsService)

6.gateway模块

6.1 pom

6.2 application.yml

7.测试


1.项目结构

微服务多模块:Springboot+Security+Redis+Gateway+OpenFeign+Nacos+JWT (附源码)仅需一招,520彻底拿捏你

涉及的模块有

(1)common(Redis配置文件、Redis工具、Token工具、返回给前端信息的工具;即如下文件RedisConfig、RedisUtil、TokenUtil、ResponseUtil);

(2)gateway

(3)model(实体类,Feign的客户端);

(4)service(用户模块、课程模块);

(5)spring_security(security的过滤器跟配置文件)。

下面小编将全部一一介绍并且源码展示出来。

2.Common模块

pom.xml

        <!--springboot_redis缓存框架 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </dependency>
        <dependency>
            <groupId>com.goyes</groupId>
            <artifactId>model</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>compile</scope>
        </dependency>

2.1 RedisConfig

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
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.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import javax.annotation.Resource;
/*
 * Redis配置
 * 解决redis在业务逻辑处理层上不出错,缓存序列化问题
 * */
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
    @Resource
    RedisConnectionFactory redisConnectionFactory;
    @Bean
    public RedisTemplate<String,Object> redisTemplate(){
        RedisTemplate<String,Object> redisTemplate=new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
//Json序列化配置
        //1、String的序列化
        StringRedisSerializer stringRedisSerializer=new StringRedisSerializer();
        // key采用String的序列化方式
        redisTemplate.setKeySerializer(stringRedisSerializer);
        // hash的key也采用String的序列化方式
        redisTemplate.setHashKeySerializer(stringRedisSerializer);

        //2、json解析任意的对象(Object),变成json序列化
        Jackson2JsonRedisSerializer<Object> serializer=new Jackson2JsonRedisSerializer<Object>(Object.class);
        ObjectMapper mapper=new ObjectMapper(); //用ObjectMapper进行转义
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        //该方法是指定序列化输入的类型,就是将数据库里的数据按照一定类型存储到redis缓存中。
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(mapper);

        // value序列化方式采用jackson
        redisTemplate.setValueSerializer(serializer);
        // hash的value序列化方式采用jackson
        redisTemplate.setHashValueSerializer(serializer);

        return redisTemplate;
    }
}

2.2 RedisUtil

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.TimeUnit;

@Component
public class RedisUtil {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    public static StringRedisTemplate stringRedisTemplateStatic;
    @PostConstruct //在项目启动的时候执行该方法,也可以理解为在spring容器初始化的时候执行该方法。
    public void initStringRedisTemplate(){
        stringRedisTemplateStatic=this.stringRedisTemplate;
    }

    private static final DateTimeFormatter df=DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    /*
     * 保存token信息到redis,也可直接在创建token中使用该方法
     * */
    public static  void redis_SaveTokenInfo(String token,String username){
        //以username做key
        LocalDateTime localDateTime=LocalDateTime.now();
        stringRedisTemplateStatic.opsForHash().put(username,"token",token);
        stringRedisTemplateStatic.opsForHash().put(username,"refreshTime", //有效时间
                df.format(localDateTime.plus(7*24*60*60*1000, ChronoUnit.MILLIS)));
        stringRedisTemplateStatic.opsForHash().put(username,"expiration",  //过期时间 5分钟 300秒
                df.format(localDateTime.plus(300*1000, ChronoUnit.MILLIS)));
        stringRedisTemplateStatic.expire(username,7*24*60*60*1000, TimeUnit.SECONDS);
    }

    /*
     * 检查redis是否存在token
     * */
    public static boolean hasToken(String username){
        return stringRedisTemplateStatic.opsForHash().getOperations().hasKey(username);
    }
}

2.3 ResponseUtil

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import org.apache.ibatis.annotations.Result;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
@Data
public class ResponseUtil {
    public static int OK = 200;
    public static int ERROR = 404;
    public static String SUCCESS="操作成功!";
    public static String NO_SUCCESS="操作失败,请稍候重试。";
    //返回码(200)
    private int code;
    //返回消息
    private String message;

    @ApiModelProperty(value = "返回数据(单条或多条)")
    private Map<Object, Object> data = new HashMap<Object, Object>();


    public ResponseUtil(int code, String message) {
        this.code=code;
        this.message=message;
    }
    public ResponseUtil(int code, String message, Map<Object, Object> data) {
        this.code=code;
        this.message=message;
        this.data=data;
    }
    //对response写入Object数据
    public static void reponseOutDiy(HttpServletResponse response,int statusCode , Object result) {
        ObjectMapper mapper = new ObjectMapper();
        PrintWriter writer = null;
        response.setStatus(statusCode);
        response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
        try {
            writer = response.getWriter();
            mapper.writeValue(writer, result);
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (writer != null) {
                writer.flush();
                writer.close();
            }
        }
    }

}

2.4  TokenUtil

import com.Lino_white.model.User; //model模块的user
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

public class TokenUtil {
    public static final String APP_SECRET ="Lino_white"; //随便取,你的Token密钥
    public static final String TOKEN_HEAD="Authorization";

    public static final String TOKEN_PREFIX = "Bearer ";
    public static String createToken(User user){
        String token = Jwts.builder()
                .setId(String.valueOf(user.getId()))
                .setSubject(user.getUsername())
                .setIssuedAt(new Date()) //签发时间
                .setIssuer("Lino_white") //签发者
                .setExpiration(new Date(System.currentTimeMillis() + 300* 1000)) //过期时间 5分钟 自行设置
                .signWith(SignatureAlgorithm.HS256, APP_SECRET) //签名算法跟密钥
                .claim("identity", user.getIdentity()) //可添加额外的属性
                .compact();
        return token;
    }

//重新生成新的Token,异常时间由传入的参数决定
    public static String createToken(User user,Date expirationTime){
        SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        try {
            expirationTime= (Date) f.parse(f.format(expirationTime));
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
       
        String token = Jwts.builder()
                .setId(String.valueOf(user.getId()))
                .setSubject(user.getUsername())
                .setIssuedAt(new Date()) //签发时间
                .setIssuer("Lino_white") //签发者
                .setExpiration(expirationTime) //过期时间
                .signWith(SignatureAlgorithm.HS256, APP_SECRET) //签名算法跟密钥
                .claim("identity", user.getIdentity()) //可添加额外的属性
                .compact();
        return token;
    }

    //获得用户名
    public String getUsernameFromToken(String token){
        return Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(token).getBody().getSubject();
    }
    /**
     * 判断token是否存在与有效(1)
     */
    public boolean checkToken(String token){
        if (StringUtils.isEmpty(token)) return false;
        try {
            Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(token);
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
        return true;
    }
    /**
     * 判断token是否存在与有效(2)
     */
    public boolean checkToken(HttpServletRequest request){
        try {
            String token = request.getHeader("token");
            return checkToken(token);
        }catch (Exception e){
            e.printStackTrace();
            return false;
        }
    }
    //获得全部属性
    public Claims parseJwt(String token){
        Claims claims = Jwts.parser()
                .setSigningKey(APP_SECRET) // 设置标识名
                .parseClaimsJws(token)  //解析token
                .getBody();
        return claims;
    }
    //获得指定属性
    public String getTokenClaim(String token,String key){
        Claims body = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(token).getBody();
        return String.valueOf(body.get(key));
    }
}

2.5  CorConfig

package com.goyes.common.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * 解决跨域
 * @author: white
 */
@Configuration
public class CorConfig implements WebMvcConfigurer {


    @Override
    public void addCorsMappings(CorsRegistry registry) {
        System.out.println("开始解决跨域");
        registry.addMapping("/**")
                .allowedOrigins("*")
                .allowedMethods("*")
//                .allowCredentials(true)//是否有验证,有就打开
                .allowedHeaders("*")
                .maxAge(3600);
    }

}

3.model模块

3.1 pom

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
        </dependency>

3.2 User

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;
@AllArgsConstructor
@NoArgsConstructor
@Data
@ApiModel(value = "实体:用户")
@TableName("user")
public class User implements Serializable {
    @ApiModelProperty("用户id")
    @TableId(value = "id",type = IdType.AUTO)
    private int id;
    @ApiModelProperty("用户名")
    private String username;
    @ApiModelProperty("密码")
    private String password;
    @TableField("identity")
    @ApiModelProperty("身份")
    private String identity;

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", identity='" + identity + '\'' +
                '}';
    }
}

3.3 UserFeign

package com.goyes.model.client;

import com.goyes.model.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@FeignClient(name = "service-user")
public interface UserFeign {
    @GetMapping("/api/user/{username}")
    public User findUserByName(@PathVariable("username") String username);

}

4.service模块

  4.1 目录结构

  4.2 service_user模块

    4.2.1 pom.xml

注意:service_user接入了security模块。

        <!--openfeign 远程接口调用-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <!--nacos 注册中心-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

        <!--nacos 配置中心-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>

        <!--nacos 客户端-->
        <dependency>
            <groupId>com.alibaba.nacos</groupId>
            <artifactId>nacos-client</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <scope>compile</scope>
        </dependency>

        <dependency>
            <groupId>com.goyes</groupId>
            <artifactId>model</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>compile</scope>
        </dependency>

        <dependency>
            <groupId>com.goyes</groupId>
            <artifactId>common</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>compile</scope>
        </dependency>

        <!--加入各service模块,swagger文档实现接入-->
        <dependency>
            <groupId>com.goyes</groupId>
            <artifactId>service_other</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>com.goyes</groupId>
            <artifactId>service_course</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>com.goyes.service_comment</groupId>
            <artifactId>service_comment</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>compile</scope>
        </dependency>

        <!--接入security模块-->
        <dependency>
            <groupId>com.goyes</groupId>
            <artifactId>spring_security</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

    4.2.2 application.yml

server:
  port: 8001
spring:
  application:
    name: service-user
  main:
    allow-bean-definition-overriding: true
  profiles:
    active: dev
  cloud:
    nacos:
      config:
        server-addr: 127.0.0.1:8848
        group: dev
      discovery:
        cluster-name: WHITE
feign:
  client:
    config:
      default:
        connect-timeout: 10000
        read-timeout: 10000

    4.2.3 Service_UserApp启动类

@SpringBootApplication
@EnableSwagger2WebMvc
@EnableDiscoveryClient
@EnableFeignClients
@EnableCaching
public class Service_UserApp
{
    public static void main( String[] args )
    {
        SpringApplication.run(Service_UserApp.class, args);
    }
}

    4.2.4 ApiController控制器

在任意一个控制器中,添加如下代码,该接口将用于OpenFeign的远程接口调用,由security模块中的自定义类MyUserDetailService去进行调用。(MyUserDetailService的代码在介绍security模块中会出现)

    @GetMapping("/api/user/{username}")
    public User findUserByName(@PathVariable("username") String username){
        User userByName = userService.findUserByName(username);
        return userByName;
    }

4.3 其他service模块

对于其他模块,相对应跟service_user模块一样,进行如下操作即可:

(1)在pom.xml中引入security模块

        <!--接入security模块-->
        <dependency>
            <groupId>com.goyes</groupId>
            <artifactId>spring_security</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

(2)在application.xml中添加以下代码

spring:
  main:
    allow-bean-definition-overriding: true

        防止出现运行异常报错信息,对于同一个服务的FeignClient来说,配置该属性不会造成覆盖,详情可以查看该文章:Consider renaming one of the beans:

Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true

5.spring_security模块

5.1 pom

<dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--security安全框架-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!--springboot_redis缓存框架 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </dependency>
        <dependency>
            <groupId>com.goyes</groupId>
            <artifactId>model</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>com.goyes</groupId>
            <artifactId>common</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>compile</scope>
        </dependency>

5.2 DiyUserDetails(UserDetails)

import com.Lino_white.model.User;
import lombok.Data;
import lombok.EqualsAndHashCode;
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.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
@Data
@EqualsAndHashCode(callSuper = false)
public class DiyUserDetails extends User implements UserDetails, Serializable {

    //用户权限列表
    private Collection<String> authorities;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> authorities1 = new ArrayList<>();
        for(String permissionValue : authorities) {
            if(StringUtils.isEmpty(permissionValue)) continue;
            SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);
            authorities1.add(authority);
        }

        return authorities1;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

5.3 WebSecurityConfig(WebSecurityConfigurerAdapter)

注意:前面将对service_user的远程接口定义为/api/user/{username},所以在过滤方面要放行该路径,否则security无法调用数据库查询用户信息,导致程序报错。

对此,可以查看该文章feign.FeignException$Unauthorized

package com.goyes.spring_security.config;


import com.goyes.spring_security.filter.TokenAuthenticationFilter;
import com.goyes.spring_security.filter.TokenLoginFilter;
import com.goyes.spring_security.filter.TokenOncePerRequestFilter;
import com.goyes.spring_security.handler.*;
import com.goyes.spring_security.service.MyUserDetailService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsUtils;

@Configuration
@EnableWebSecurity //开启Security功能
@EnableGlobalMethodSecurity(prePostEnabled = true) //启动方法级别的权限认证
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyUserDetailService myUserDetailService;

    @Bean
    //配置密码加密器
    public PasswordEncoder passwordEncoder(){return new BCryptPasswordEncoder();}

    //配置哪些请求不拦截
    //TODO 将需要Feign的方法前缀都用上api,得到api/select/user/{user_id}这样的路径不受限制
    // 由于api路径是由服务模块自己去调用的,所以gateway不用做路径请求的处理
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/api/**","/doc.html#/**","/swagger-resources");
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(myUserDetailService).passwordEncoder(passwordEncoder());
    }

    //配置安全策略
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        System.out.println("读取配置*****************WHITE");
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                //该过滤器设置在用户名、密码、权限过滤器之前。这样每次访问接口都会经过此过滤器,我们可以获取请求路径,并判定当请求路径为/login时进入验证码验证流程。
                // 使用jwt的Authentication,来解析过来的请求是否有token
                .addFilterBefore(new TokenOncePerRequestFilter(), UsernamePasswordAuthenticationFilter.class)

                //登录后,访问没有权限处理类
                .exceptionHandling().accessDeniedHandler(new NothingAccessDeniedHandler())
                //匿名访问,没有权限的处理类
                .authenticationEntryPoint(new LoginAuthenticationEntryPoint())

                .and()
                .formLogin()
                .successHandler(new LoginInSuccessHandler())
                .failureHandler(new LoginInFailHandler())
                .and()
                .logout()
                .logoutSuccessHandler(new LogOutSuccessHandler())
                // 配置取消session管理
                .and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .csrf().disable();
    }
}

5.4 TokenOncePerRequestFilter(OncePerRequestFilter)

注意:在这里TokenUtil跟RedisUtil对于过期时间的定义不同。

token过期时间为3分钟,redis上存储的异常时间为5分钟,并且redis上存储的刷新时间为7天

在下面的配置文件中,仅仅对token进行分析而已,可以根据需要在这里做验证码校验。

token的过期时间在以下代码中是这样做的,当token过期时间3分钟到了,判断redis上存储的异常时间是否到了5分钟,没到5分钟就返回一个新的token给前端,前端拿到该token就可以继续访问;如果到了5分钟,则会停止访问并通知前端 “用户已经过期,请重新登录”。

小编有个想法(还没做):在这里可以重新定义过期时间,比如用户每次访问时都进行判断:当token的过期时间小于1分钟后就刷新redis的异常时间,这样可以使当token要过期时,就有新的token出现,但这样操作也存在缺点:就是要消耗内存资源,每次都得去读取token是否临近过期时间了。对于这块,可以针对自己的情况去做调整。

package com.goyes.spring_security.filter;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.goyes.common.utils.RedisUtil;
import com.goyes.common.utils.ResponseUtil;
import com.goyes.common.utils.TokenUtil;
import com.goyes.model.User;
import com.goyes.spring_security.model.DiyUserDetails;
import io.github.classgraph.json.JSONUtils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import jdk.nashorn.internal.parser.JSONParser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.json.JsonParser;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import springfox.documentation.spring.web.json.Json;
import sun.security.util.SecurityConstants;
//import sun.security.util.SecurityConstants;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.crypto.Data;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Date;

/**
 * 在用户名、密码、权限过滤器之前的过滤器
 * 在请求过来的时候,解析请求头中的token,再解析token得到用户信息,再存到SecurityContextHolder中
 *
 * TODO 下面过滤器仅做了针对token解析,包括token异常、过期、重新颁布等
 * @author white
 */
@Component
public class TokenOncePerRequestFilter extends OncePerRequestFilter {

    @Autowired
    StringRedisTemplate stringRedisTemplate = RedisUtil.stringRedisTemplateStatic;


    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {

        /*
        * TODO 可在这里判断请求过来的路径是否为login,方式为post,来在这里进行验证码有效验证
        *  验证成功则直接chain(request,response)继续走过滤
        * */
        String requestURI = request.getRequestURI();
        System.out.println("开始请求,请求路径:"+requestURI+"  请求方式:"+request.getMethod());
        User user = null;
        SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String authHeader = request.getHeader(TokenUtil.TOKEN_HEAD);

        //没有token不用理
        if (authHeader != null && authHeader.startsWith(TokenUtil.TOKEN_PREFIX)) {

            final String authToken = authHeader.replace(TokenUtil.TOKEN_PREFIX, "");
            //这里的authToken可能时间已过,需要重新创建一个token
            //先对比redis中的过期时间,redis的过期时间随着用户的操作而更新,token可能没有及时更新
            //判断是否一样,一样的话就是token失效了,跳转重新登录,
            // 不一样就是redis过期时间更新了,生成新的token返回给前端
            String username = null;
            Claims claims;
            try {
                claims = new TokenUtil().parseJwt(authToken);
                username = claims.getSubject();
            } catch (ExpiredJwtException e) {
                //token过期
                claims = e.getClaims();
                username = claims.getSubject();
                user = JSONObject.parseObject(String.valueOf(stringRedisTemplate.opsForHash().get(username, "user")), User.class);

                if (user == null) {
                    chain.doFilter(request, response);
                    return;
                } else {
                    if (RedisUtil.hasToken(username)) {
                        Object expiration = stringRedisTemplate.opsForHash().get(username, "expiration");
                        Object tokenExpirationTime = f.format(claims.getExpiration());
                        Date expirationDate_redisTime = null, expirationDate_tokenTime = null, nowTime;
                        try {
                            expirationDate_redisTime = (Date) f.parseObject(String.valueOf(expiration));
                            expirationDate_tokenTime = (Date) f.parseObject(String.valueOf(tokenExpirationTime));
                            nowTime = (Date) f.parseObject(f.format(new Date()));
                        } catch (ParseException ex) {
                            throw new RuntimeException(ex);
                        }

                        System.out.println("*********Token过期(Start)***********");
                        System.out.println("token浏览器过期时间:" + tokenExpirationTime);

                        System.out.println("redis过期时间:" + expiration);

                        //
                        /*
                         * redis<token || token=redis || redis <now  则token失效,跳转登录
                         * token<redis
                         * */
                        if (expirationDate_redisTime.getTime() < expirationDate_tokenTime.getTime() ||
                                expirationDate_tokenTime.getTime() == expirationDate_redisTime.getTime() ||
                                expirationDate_redisTime.getTime() < nowTime.getTime()) {

                            //时间相同,跳转登录
                            ResponseUtil.reponseOutDiy(response, 401, "用户已过期,请重新登录");
                            System.out.println("*********Token过期(End)失效***********");
                            return;
                        } else {
                            //时间不同,生成新token  需要用户id,身份,用户名
                            //response存入token   返回
                            Object expiration_redisTime = stringRedisTemplate.opsForHash().get(username, "expiration");

                            Date date;
                            try {
                                date = (Date) f.parseObject(String.valueOf(expiration_redisTime));
                            } catch (ParseException ex) {
                                throw new RuntimeException(ex);
                            }
                            //通过数据库查询数据,创建token
                            System.out.println("这里之前开始的时间:" + date);
                            String token = TokenUtil.createToken(user, date);
                            System.out.println("—————————————————start—————————————————————");
                            System.out.println("token:" + token);
                            RedisUtil.redis_SaveTokenInfo(user, token);
                            response.setHeader(TokenUtil.TOKEN_HEAD, TokenUtil.TOKEN_PREFIX + token);
                            request.setAttribute(TokenUtil.TOKEN_HEAD, TokenUtil.TOKEN_PREFIX + token);
                            Date expiration1 = new TokenUtil().parseJwt(token).getExpiration();
                            System.out.println("重新更新token后过期时间:" + expiration1);
                            System.out.println("—————————————————End—————————————————————");
                            ResponseUtil.reponseOutDiy(response, 200, token);
                            System.out.println("*********Token过期(End)新Token***********");
                            return;
                        }
                    } else {
                        //TODO 新增,如果redis没有username,说明未登录
                        throw new RuntimeException("未登录");
                    }
                }
            }
            //避免每次请求都请求数据库查询用户信息,从缓存中查询
            user = JSONObject.parseObject(String.valueOf(stringRedisTemplate.opsForHash().get(username, "user")), User.class);
            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                if (user != null) {
                    UsernamePasswordAuthenticationToken authentication =
                            // TODO 未修改  这里的权限先空着
                            new UsernamePasswordAuthenticationToken(user, user.getPassword(), null);
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            }
        }
        System.out.println("走过滤——————————————————————————");
        chain.doFilter(request, response);
    }
}

5.5 LoginAuthenticationEntryPoint(AuthenticationEntryPoint)

import com.Lino_white.common.ResponseUtil;
import com.Lino_white.common.TokenUtil;
import jdk.nashorn.internal.parser.Token;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
 * 匿名未登录的时候访问,需要登录的资源的调用类
 * @author Lino_white
 */
@Component
public class LoginAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        String token =httpServletRequest.getHeader(TokenUtil.TOKEN_HEAD);
        System.out.println("当前未登录,无法访问 ::"+token);
        if (token!=null && token.contains(TokenUtil.TOKEN_PREFIX)) {
            token=token.replace(TokenUtil.TOKEN_PREFIX,"");
            String usernameFromToken = new TokenUtil().getUsernameFromToken(token);
            System.out.println("用户名:"+usernameFromToken);
        }
        ResponseUtil.reponseOutDiy(httpServletResponse,401,"当前未登录,无法访问");
    }
}

5.6 LoginInFailHandler(AuthenticationFailureHandler)

import com.Lino_white.common.ResponseUtil;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
 * 登录账号密码错误等情况下,会调用的处理类
 * @author Lino_white
 */
@Component
public class LoginInFailHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        System.out.println("认证失败————————————");
        ResponseUtil.reponseOutDiy(httpServletResponse,401,"登录失败,请重试");
    }
}

5.7 LoginInSuccessHandler(AuthenticationSuccessHandler)

package com.goyes.spring_security.handler;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.goyes.common.utils.RedisUtil;
import com.goyes.common.utils.ResponseUtil;
import com.goyes.common.utils.TokenUtil;
import com.goyes.model.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
/**
 * @LoginInSuccessHandler.java的作用:
 * 登录成功处理类,登录成功后会调用里面的方法
 * @author: white文
 * @time: 2023/5/18 16:02
 */
@Slf4j
@Component
public class LoginInSuccessHandler implements AuthenticationSuccessHandler {


    /**
     * 用户通过TokenLoginFilter(UsernamePasswordAuthenticationFilter)后,
     * 验证成功到这里进行
     * 1.获取当前用户
     * 2.token创建
     * 3.并将其存入redis并返回
     */
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        log.info("登录成功,开始初始化token并缓存在redis");
                User user =(User) authentication.getPrincipal();
        String token = TokenUtil.createToken(user);
        //redis缓存token
        RedisUtil.redis_SaveTokenInfo(user,token);
        //写入response
        response.setHeader("token", TokenUtil.TOKEN_PREFIX+token);

        try {
            //登录成功,返回json格式进行提示
            response.setContentType("application/json;charset=utf-8");
            response.setStatus(HttpServletResponse.SC_OK);
            PrintWriter out=response.getWriter();
            Map<String,Object> map=new HashMap<String,Object>(4);
            map.put("code",HttpServletResponse.SC_OK);
            map.put("message","这里全部都是自定义的!登录成功");
            map.put("token",token);
            out.write(new ObjectMapper().writeValueAsString(map));
            out.flush();
            out.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

}

5.8 LogOutSuccessHandler(LogoutSuccessHandler)

import com.Lino_white.common.RedisUtil;
import com.Lino_white.common.ResponseUtil;
import com.Lino_white.common.TokenUtil;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class LogOutSuccessHandler implements LogoutSuccessHandler {
    private StringRedisTemplate stringRedisTemplate= RedisUtil.stringRedisTemplateStatic;
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        //用户退出登录
        System.out.println("LogoutSuccessHandler退出");
        String token=request.getHeader("token");
        if (token==null) token=request.getHeader(TokenUtil.TOKEN_HEAD);
        token=token.replace(TokenUtil.TOKEN_PREFIX,"");
        String username = new TokenUtil().getUsernameFromToken(token);
        Authentication au = SecurityContextHolder.getContext().getAuthentication();
        if (au!=null) new SecurityContextLogoutHandler().logout(request,response,au);
        Boolean delete = stringRedisTemplate.delete(username);
        if (delete) ResponseUtil.reponseOutDiy(response,200,"用户已成功退出");
    }
}

5.9 NothingAccessDeniedHandler(AccessDeniedHandler)

import com.Lino_white.common.ResponseUtil;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
 * 没有权限,被拒绝访问时的调用类
 * @author Lino_white
 */
@Component
public class NothingAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        System.out.println("没有权限");
        ResponseUtil.reponseOutDiy(httpServletResponse,403,"当前您没有该权限");
    }
}

5.10 MyUserDetailService(UserDetailsService)

注意:在这里调用了model模块中的UserFeign文件,实现读取service_user模块中的findUserByName方法。

package com.goyes.spring_security.service;

import com.goyes.model.User;
import com.goyes.model.client.UserFeign;
import com.goyes.spring_security.model.DiyUserDetails;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

/**
 * 从数据库读取用户信息(用户名,密码,身份)进行身份认证
 */
@Service
public class MyUserDetailService implements UserDetailsService{
    @Autowired
    private UserFeign userFeign;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        System.out.println("********开始loadUserByUsername********");
        User user = userFeign.findUserByName(username);
        System.out.println("浏览器的username:"+username);

        if (user==null) throw new UsernameNotFoundException(username);
        System.out.println("数据库的username:"+user.getUsername());
        //根据当前用户名查询用户权限
        List<String> authorities=new ArrayList<>();
        authorities.add("ROLE_"+user.getIdentity());
        DiyUserDetails details=new DiyUserDetails();
        BeanUtils.copyProperties(user,details);
        details.setAuthorities(authorities);
        //如果数据库密码无加密,用下列
        //details.setPassword(new BCryptPasswordEncoder().encode(user.getPassword()));
        System.out.println("********结束loadUserByUsername********");
        return details;
    }
}

6.gateway模块

6.1 pom

        <dependency>
           <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>

6.2 application.yml

server:
  port: 10000
spring:
  application:
    name: gateway

  cloud:
    gateway:
      routes:
        - id: user
          uri: http://localhost:8001
          predicates:
            - Path=/user/**,/admin/**,/api/user/**,/login,/logout
        - id: course
          uri: http://localhost:8002
          predicates:
            - Path=/course/**

7.测试

这里是使用postman工具进行测试的,在这里之前已经在数据库有用户名跟密码都为111的数据,并且密码已是加密形式。

1.首次访问/admin/findAll,gateway会调用到8001端口下的user模块,如下图

微服务多模块:Springboot+Security+Redis+Gateway+OpenFeign+Nacos+JWT (附源码)仅需一招,520彻底拿捏你

 首次访问/course/findAll,gateway会调用到8002端口下的course模块,如下图

微服务多模块:Springboot+Security+Redis+Gateway+OpenFeign+Nacos+JWT (附源码)仅需一招,520彻底拿捏你

2.POST访问/login,并且提供相关参数(数据库存在用户111和加密过的密码111),得到token,如下图:

微服务多模块:Springboot+Security+Redis+Gateway+OpenFeign+Nacos+JWT (附源码)仅需一招,520彻底拿捏你

这时,redis数据库就有了用户名为 user的数据 

微服务多模块:Springboot+Security+Redis+Gateway+OpenFeign+Nacos+JWT (附源码)仅需一招,520彻底拿捏你

3.复制刚才返回给前端的token,在Authorization的Type中,选择Bearer Token,粘贴上刚才的Token,点击Send发送

微服务多模块:Springboot+Security+Redis+Gateway+OpenFeign+Nacos+JWT (附源码)仅需一招,520彻底拿捏你

 5.再次请求8002端口下的source模块

微服务多模块:Springboot+Security+Redis+Gateway+OpenFeign+Nacos+JWT (附源码)仅需一招,520彻底拿捏你

 再次请求8001端口下的user模块

微服务多模块:Springboot+Security+Redis+Gateway+OpenFeign+Nacos+JWT (附源码)仅需一招,520彻底拿捏你

6.当token过期后,redis中的异常时间还没到,则会返回给前端一个新的token,拿着新token继续请求即可。

微服务多模块:Springboot+Security+Redis+Gateway+OpenFeign+Nacos+JWT (附源码)仅需一招,520彻底拿捏你

7.当token过期并且redis的异常时间也过了之后,用户就需要重新登录。

微服务多模块:Springboot+Security+Redis+Gateway+OpenFeign+Nacos+JWT (附源码)仅需一招,520彻底拿捏你

8.退出则为/logout 。

微服务多模块:Springboot+Security+Redis+Gateway+OpenFeign+Nacos+JWT (附源码)仅需一招,520彻底拿捏你

  同时,redis数据库中用户名为user的key值也被删除掉。

至此,结束!祝大家520快乐!!!文章来源地址https://www.toymoban.com/news/detail-451827.html

到了这里,关于微服务多模块:Springboot+Security+Redis+Gateway+OpenFeign+Nacos+JWT (附源码)仅需一招,520彻底拿捏你的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • SpringCloud微服务之间如何进行用户信息传递(涉及:Gateway、OpenFeign组件)

    在业务微服务中通过工具类获取当前用户信息 网关微服务(Gateway)往业务微服务传递用户信息 业务微服务之间通过OpenFeign传递用户信息 只要把上面两处打通,然后业务微服务在通过拦截器获取到用户信息,之后再将用户信息存在ThreadLocal中,这样我们就可以实现在业务微服

    2024年02月13日
    浏览(55)
  • 研发提速:nacos+openfeign环境下的本地链接服务

    项目研发过程中,经常会遇到与测试人员工作重叠的情况,十分影响效率。 做了一个修改,可以在本地环境启动项目后和测试环境交互,并且不影响测试环境,理论上也可以用于线上环境的异常的快速处理。 准备事项如下: 一:搭建本地的nacos服务。 二:导入测试环境相应

    2024年02月08日
    浏览(37)
  • springboot-gateway注册nacos失败,控制台没有报错

    最近springboot的gateway注册到nacos上,没有注册成功 我是在common里面引入了nacos的依赖,依赖如下: 然后代码yml里面添加如下:

    2023年04月09日
    浏览(51)
  • 微服务动态权限管理方案(Spring Cloud Gateway+Spring Cloud Security)

    微服务认证方案的大体方向是统一在网关层面认证鉴权,微服务只负责业务,和鉴权完全隔离 整体包含以下四个角色 客户端 :需要访问微服务资源 网关 :负责转发、认证、鉴权 OAuth2.0授权服务 :负责认证授权颁发令牌 微服务集合 :提供资源的一系列服务。 这里的客户端

    2024年02月12日
    浏览(53)
  • Gateway服务集成Nacos2021.0.4错误解决

    gateway服务集成nacos,启动后报错: Caused by: com.alibaba.nacos.shaded.io.grpc.netty.shaded.io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: no further information: ; 版本: jdk:1.8 spring-boot:2.7.11 spring-cloud:2021.0.6 spring-cloud-alibaba:2021.0.4.0 单配置文件:application.yaml中配置 多配置文件:

    2024年02月13日
    浏览(36)
  • 若依框架解读(微服务版)——2.模块间的调用逻辑(ruoyi-api模块)(OpenFeign)(@innerAuth)

    我们可以了解到一共有这么多服务,我们先启动这三个服务 其中rouyi–api模块是远程调用也就是提取出来的openfeign的接口 ruoyi–commom是通用工具模块 其他几个都是独立的服务 api模块当中有几个提取出来的OpenFeign的接口 分别为文件,日志,用户服务 我们以RemoteUserService接口为

    2023年04月09日
    浏览(39)
  • Spring Security Oauth2.1 最新版 1.1.0 整合 gateway 完成授权认证(拥抱 springboot 3.1)

    目录 背景 demo地址 版本 Spring Boot 3.1 Spring Authorization Server 1.1.0 基础 spring security OAuth2 模块构成 授权方式 认证方式 集成过程 官方demo 代码集成 依赖 授权服务AuthorizationServerConfig配置 重要组件 测试 查看授权服务配置 访问授权服务 授权 回调 获取 access_token 获取用户信息 个性

    2024年02月08日
    浏览(64)
  • Nacos配置管理、Fegin远程调用、Gateway服务网关

    Nacos除了可以做注册中心,同样可以做配置管理来使用。 当微服务部署的实例越来越多,达到数十、数百时,逐个修改微服务配置就会让人抓狂,而且很容易出错。我们需要一种统一配置管理方案,可以集中管理所有实例的配置。 Nacos一方面可以将配置集中管理,另一方可以

    2024年02月03日
    浏览(40)
  • Nacos配置管理、Feign远程调用、Gateway服务网关

    1.在Nacos中添加配置 Data Id = 服务名称-环境名称.yaml eg: userservice-dev.yaml 2.引入nacos-config依赖 在user-service服务中,引入nacos-config的客户端依赖 3.添加 bootstrap.yaml ,我们这里以显示一个日期时间为例 在user-service中添加一个bootstrap.yaml文件 4.编写controller 方式一 给 @Value 注入的变量

    2024年02月12日
    浏览(40)
  • springcloud gateway实时监听nacos微服务上下线

    Nacos : 1.3.1 SpringCloud : 2021.0.2 SpringCloud gateway : 3.1.2 微服务下线后,网关存在短时间内转发失效服务,导致前端访问异常 微服务上线后,网关没有及时刷新本地缓存的服务,导致前端可能找不到服务实例 nacos的主动推送实例变化比网关自己拉取要及时的多 此处配置注意点: 1、

    2024年02月08日
    浏览(58)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包