springboot3微服务下结合springsecurity的认证授权实现

1. 简介

在微服务架构中,系统被拆分成许多小型、独立的服务,每个服务负责一个功能模块。这种架构风格带来了一系列的优势,如服务的独立性、弹性、可伸缩性等。然而,它也带来了一些挑战,特别是在安全性方面。这时候就体现出认证服务器的重要性,它可以在网关服务器的基础上做登录认证,权限认证等功能。本篇文章就以如下结构实现一个demo供大家参考选择,整体逻辑如下图所示

  1. 当客户端第一次发起资源请求(一般前端会处理好逻辑,比如vue中实现未登录的用户无法访问系统资源等)
  2. gateway拦截到请求并检查请求头中是否携带token,有则放行没有则无权限无法访问(返回401)
  3. 客户端接拦截到响应并解析出当前响应状态码是401,则会redirect到登录页面(未登录的用户请先登录)gateway拦截到请求后判断当前是登录url则放行,转发到认证服务器进行登录操作
  4. 根据email / username判断是否存在数据库,存在则取出数据对登录密码进行加密比对,比对通过则代表成功登录生成token,并且获取该用户所对应角色的权限信息,并将其存在redis中
  5. 用户端拦截到登录响应数据,从其中获取到token和一些用户信息保存到本地(session,localStorage等)
  6. 登录后的每次请求发送前都会在请求头中添加token信息(本次实现鉴权逻辑不写在认证服务器中,由每个资源服务器引入jar包依赖各自鉴权
  7. 通过@PreAuthorize注解进行判断当前用户是否有执行该方法相应的权限,如果有则顺利执行方法返回结果,否则无权限返回code401

在这里插入图片描述

2. 认证服务器实现

由于本次实现中,认证服务器负责的功能就是登录(查询用户信息,登出) 、 查找权限、对token的签发、刷新管理,所以该服务器就不考虑集成springsecurity,只需引入mybatis相关依赖和nacos服务注册发现的

2.1 微服务配置

2.1.1 改pom

<dependencies><!-- nacos --><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency><!-- 支持负载均衡 --><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-loadbalancer</artifactId></dependency><!--mybatis和springboot整合--><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId></dependency><!--Mysql数据库驱动8 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><!--persistence--><dependency><groupId>javax.persistence</groupId><artifactId>persistence-api</artifactId></dependency><!--通用Mapper4--><dependency><groupId>tk.mybatis</groupId><artifactId>mapper</artifactId></dependency><!--SpringBoot集成druid连接池--><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId></dependency><!--lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.28</version><scope>provided</scope></dependency><!--cloud_commons_utils--><dependency><groupId>com.simple.cloud</groupId><artifactId>simpleCloud_api_commons</artifactId><version>1.0-SNAPSHOT</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><scope>provided </scope></dependency><!-- redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>
</dependencies>

2.1.2 application.yml 配置

server:port: 10001spring:application:name: auth-servercloud:nacos:discovery:server-addr: localhost:8848data:redis:host: localhostport: 6379database: 0timeout: 1800000password:jedis:pool:max-active: 20 #最大连接数max-wait: -1    #最大阻塞等待时间(负数表示没限制)max-idle: 5    #最大空闲min-idle: 0     #最小空闲datasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driver # 用户登录表所在的数据库url: jdbc:mysql://localhost:3306/seata_system?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=trueusername: rootpassword: abc123# ========================mybatis===================
mybatis:mapper-locations: classpath:mapper/*.xmltype-aliases-package: com.simple.cloud.entitiesconfiguration:map-underscore-to-camel-case: true

2.1.3 主启动

@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("com.simple.cloud.mapper")
public class AuthMain10001 {public static void main(String[] args) {SpringApplication.run(AuthMain10001.class , args);}
}

2.2 需求功能实现

跟springsecurity的逻辑一样,我们需要提供一个加密器和一个UserDetailService并定义findByUsername方法,话不多说下面就跟我一起一一实现吧

2.2.1 utils工具类

2.2.1.1 SHA-256 加密器

在选择加密或哈希算法时,更推荐使用SHA-256或SHA-3这两个方法都属于SHA(安全散列算法)系列,它们提供了比MD5更强的安全性。SHA-256生成的是256位的哈希值,而SHA-3是最新的成员,提供了与SHA-2类似的安全性,但采用了不同的算法设计。这些算法在生成数字签名和验证数据完整性方面被广泛使用。本篇教程基于SHA-256实现密码加密

当然如果作者想基于对称加密是西安,AES(高级加密标准)是目前推荐的算法。感兴趣的读者可以去了解了解😁

public class SHA_256Helper {public static String encrypt(String password) {try {// 获取SHA-256 MessageDigest实例MessageDigest digest = MessageDigest.getInstance("SHA-256");// 将输入字符串转换为字节数组byte[] hash = digest.digest(password.getBytes(StandardCharsets.UTF_8));// 将字节数组转换为十六进制字符串StringBuilder hexString = new StringBuilder();for (byte b : hash) {String hex = Integer.toHexString(0xff & b);if (hex.length() == 1) {hexString.append('0');}hexString.append(hex);}return hexString.toString();} catch (NoSuchAlgorithmException e) {throw new RuntimeException("SHA-256 encoded fail!!+" + e);}}
}
2.2.1.2 JWTUtiles

JWT(JSON Web Token)是一种开放标准,它允许在两方之间安全地传输信息。由于JWT是经过数字签名的,因此它的内容不仅可以被校验,而且可以被信任。这使得JWT成为在用户登录场景中存储用户登录状态、过期时间等信息的理想选择。

将权限信息存储在JWT中的做法通常包括以下步骤:

  • 编码权限信息:在生成JWT时,可以将用户的权限信息作为有效载荷的一部分进行编码。这些信息可以是角色、权限级别或其他与用户相关的访问控制数据。
  • 传输token:当用户登录成功并获得了JWT后,前端会在后续的请求中携带这个JWT。这样,后端就可以通过解析JWT来验证用户的权限信息。
  • 解析和验证:后端接收到含有JWT的请求时,会首先对JWT进行解码和验证。验证成功后,就可以从JWT的有效载荷中读取出用户的权限信息,并根据这些信息来判断用户是否有权访问请求的资源或执行操作。

需要注意的是,尽管可以将这些信息放入JWT,但也要考虑安全性问题。例如,不应将敏感信息放入JWT的有效载荷中,因为有效载荷是可以被解码的。此外,应该设置合理的过期时间,并在必要时提供刷新机制,以便在不重新进行完整身份验证的情况下更新令牌。

public class JWTHelper {private static long tokenExpiration = 20 * 60 * 1000; // 20min过期private static long tokenRefreshExpiration = 12 * 60 * 60 * 1000; // 12小时过期private static String tokenSignKey = "31c78b41f"; //密钥private static String buildToken(Long userId, String email, List<String> permission , long timeToLive){return Jwts.builder().setSubject("AUTH-USER").setExpiration(new Date(System.currentTimeMillis() + timeToLive)).claim("userId", userId).claim("email", email).claim("permission", permission).signWith(SignatureAlgorithm.HS512, tokenSignKey).compressWith(CompressionCodecs.GZIP).compact();}public static String[] createToken(Long userId, String email, List<String> permission) {String token = buildToken(userId,email,permission,tokenExpiration);//token过期时可以刷新长期tokenString refreshToken = buildToken(userId,email,permission,tokenRefreshExpiration);return new String[]{token , refreshToken};}// 原始token过期时刷新token 而refreshToken保持不变(如果refresh都过期则需重新登录)public static String refresh(String refreshToken){return buildToken(SecurityAccessConstant.TOKEN_TYPE, getUserId(refreshToken) ,getEmail(refreshToken), getPermission(refreshToken) , tokenExpiration);}// 去掉前缀public static String getToken(String token){if(token == null)return null;if(token.startsWith(SecurityAccessConstant.TOKEN_PREFIX))return token.replace(SecurityAccessConstant.TOKEN_PREFIX,"");//没带前缀的认为是无效tokenreturn null;}// 获取当前token过期时间public static Date getExpirationDate(String token) {if(StringUtil.isBlank(token))return null;Claims claims = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token).getBody();return claims.getExpiration();}//判断当前token是否过期public static boolean isOutDate(String token){try {Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);Date expirationDate = claimsJws.getBody().getExpiration();return expirationDate.before(new Date());} catch (JwtException e) {// JWT token无效或已损坏return true;}}public static Long getUserId(String token) {try {if (token == null || token == "") return null;Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);Claims claims = claimsJws.getBody();Integer userId = (Integer) claims.get("userId");return userId.longValue();} catch (Exception e) {e.printStackTrace();return null;}}public static String getEmail(String token) {try {if (token == null || token == "") return "";Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);Claims claims = claimsJws.getBody();return (String) claims.get("email");} catch (Exception e) {e.printStackTrace();return null;}}public static List<String> getPermission(String token) {try {if (token == null || token == "") return null;Jws<Claims> claimsJws = Jwts.parser().setSigningKey(tokenSignKey).parseClaimsJws(token);Claims claims = claimsJws.getBody();return (List<String>) claims.get("permission");} catch (Exception e) {e.printStackTrace();return null;}}
}

对于对token无感刷新感兴趣的读者可以查阅 对token无感刷新的理解,里面提到了具体的实现逻辑和编码过程中的一些思考😁希望能对你有所帮助

2.2.1.3 SecurityAccessConstant 定义一些全局常量

往往是一些字符串类型的关键字,在这里统一定义外部就可以直接调用,方便微服务之间的管理

public class SecurityAccessConstant {public static String TOKEN_PREFIX = "Bearer ";public static String HEADER_NAME_TOKEN = "Authorization";public static String TOKEN_TYPE = "Short-lived";public static String REFRESH_TOKEN_TYPE = "refresh";public static String WEB_REQUEST_TO_AUTH_URL = "http://127.0.0.1:10001";public static String REQUEST_LOGGING_URI = "/simple/cloud/access/login";public static String REQUEST_REFRESH = "/simple/cloud/access/refresh";public static String USERINFO_REDIS_STORAGE_KEY = "_INFO_dbh9";public static String REFRESH_TOKEN_REDIS_STORAGE_KEY = "_REFRESH_s9k1";
}
2.2.1.4 ResponseUtil

在后面就可以看到,springboot3响应式编程里的filter使用的是ServerWebExchange,所以这里就会对该ServerWebExchange实例修改其响应返回而不继续执行后面的逻辑 其中响应修改的内容有响应状态码StatusCode和可能携带的响应信息RespondBody 具体封装响应体的写法可以看return处(使用writeWith封装)

public class ResponseUtils {public static Mono<Void> out(ServerWebExchange exchange, ResultData r){// 将ResultData对象转换为JSON字符串,并设置为响应体ObjectMapper objectMapper = new ObjectMapper();byte[] responseBody = new byte[0];try {responseBody = objectMapper.writeValueAsBytes(r);} catch (JsonProcessingException e) {e.printStackTrace();}exchange.getResponse().setStatusCode(HttpStatus.NETWORK_AUTHENTICATION_REQUIRED);exchange.getResponse().getHeaders().add("Content-Type", "application/json");return exchange.getResponse().writeWith(Mono.just(new DefaultDataBufferFactory().wrap(responseBody)));}/*** 使用WebClient异步访问 localhost:10001/auth/login 为例子* @param url http://localhost:10001 前缀* @param uri /auth/login 后边的路径名称* @param key,value 请求头中的键值对* @return*/public static Mono<ResultData> webClientRequest(String url , String uri , String key , String value){WebClient webClient = WebClient.create(url);Mono<ResultData> response = webClient.get().uri(uri).header(key , value).retrieve().bodyToMono(ResultData.class);return response;}
}

2.3 主体功能实现

该controller即是本次认证服务器实现的所以方法:

  • login: 登录方法,接收一个LoginVo 类型的登录数据(其中包含了email和password),首先根据邮箱去数据库找是否有该用户,如果有则继续对密码加密然后比对,当比对成功时则会查询该用户所有的权限信息本次实现的权限是通过与meau表集成,即根据type判断是权限还是菜单如下图(这部分根据自己的需求来自定义,拆开也可以)在这里插入图片描述之后将所有需要返回的数据放入map中统一返回就好了,这里包括了tokentokenExpire(便于前端判断token是否过期动态刷新)和refreshToken(用于短token刷新的凭证)
  • refresh : 即是上面使用refreshToken 来刷新token的实现方法,注意这里返回的结果为新下发的token和其过期时间
  • info : 该方法用户获取用户信息(这里就偷懒了从redis取出直接返回,想做更细化功能的读者可以在此基础上扩展)
  • logout : 退出登录接口,用于提醒服务器删掉保存的一些信息(比如redis或者消息队列中的)防止信息泄露
@RestController
@RequestMapping("/simple/cloud/access")
public class AuthController {@Resourceprivate SysUserService sysUserService;@Resourceprivate SysMenuService sysMenuService;@Resourceprivate RedisTemplate redisTemplate;/*** 登录* @return*/@PostMapping("/login")public ResultData login(@RequestBody LoginVo loginVo) throws Exception{// 先根据email找指定用户SysUser sysUser = sysUserService.findUserByEmail(loginVo.getEmail());if(sysUser == null)throw new Exception("找不到该用户");//加密密码来比较String encryptValue = SHA_256Helper.encrypt(loginVo.getPassword());if(!StringUtils.pathEquals(encryptValue,sysUser.getPassword()))throw new Exception("密码错误");System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"+encryptValue);//获取用户的角色List<SysRole> sysRoles = sysUserService.selectAllByUserId(sysUser.getId());sysUser.setRoleList(sysRoles);//根据id获取所有菜单列表List<RouterVo> routerList = sysMenuService.getAllRouterListByUserId(sysUser.getId());//根据id获取所有按钮列表List<String> permsList = sysMenuService.getAllMenuListByUserId(sysUser.getId());//map中插入相应的值Map<String, Object> map = new HashMap<>();map.put("routers",routerList);map.put("buttons",permsList);map.put("roles",sysUser.getRoleList());map.put("name",sysUser.getName());//存放token到请求头中String[] tokenArray = JWTHelper.createToken(sysUser.getId(), sysUser.getEmail(), permsList);map.put("token",tokenArray[0]);map.put("tokenExpire",JWTHelper.getExpirationDate(tokenArray[0]).getTime());map.put("refreshToken",tokenArray[1]);// 存放用户信息权限数据redisTemplate.opsForValue().set(sysUser.getId() + SecurityAccessConstant.USERINFO_REDIS_STORAGE_KEY, new ObjectMapper().writeValueAsString(map), 30*60, TimeUnit.SECONDS);// 存放refreshTokenredisTemplate.opsForValue().set(tokenArray[0], tokenArray[1], JWTHelper.getExpirationDate(tokenArray[1]).getTime() , TimeUnit.MILLISECONDS);return ResultData.success(map);}@GetMapping("/refresh")public ResultData refresh(HttpServletRequest request){String refreshToken = JWTHelper.getToken(request.getHeader(SecurityAccessConstant.HEADER_NAME_TOKEN));//刷新tokenString refresh = JWTHelper.refresh(refreshToken);Map<String, Object> map = new HashMap<>();map.put("token",refresh);map.put("expire",JWTHelper.getExpirationDate(refresh).getTime());return ResultData.success(map);}/*** 获取用户信息* @return*/@PostMapping("/info")public ResultData info(HttpServletRequest request) throws JsonProcessingException {String token = JWTHelper.getToken(request.getHeader(SecurityAccessConstant.HEADER_NAME_TOKEN));Long userId = JWTHelper.getUserId(token);if(userId == null)return ResultData.fail(ResultCodeEnum.RC401.getCode(), "token失效请重新登录");// 存放权限信息到redis中 , springsecurity通过 userId 做为key获取权限列表String storageJSON = (String) redisTemplate.opsForValue().get(userId + SecurityAccessConstant.USERINFO_REDIS_STORAGE_KEY);if(null == storageJSON)return ResultData.fail(ResultCodeEnum.RC401.getCode(), "登录失败请重新登录");return ResultData.success(new ObjectMapper().readValue(storageJSON , Map.class));}/*** 退出* @return*/@PostMapping("/logout")public ResultData logout(HttpServletRequest request){String token = JWTHelper.getToken(request.getHeader(SecurityAccessConstant.HEADER_NAME_TOKEN));if(token == null)return ResultData.success("token失效 以退出登录");Long userId = JWTHelper.getUserId(token);if(userId == null)return ResultData.success("token失效 以退出登录");redisTemplate.delete(userId + SecurityAccessConstant.USERINFO_REDIS_STORAGE_KEY);redisTemplate.delete(userId + SecurityAccessConstant.REFRESH_TOKEN_REDIS_STORAGE_KEY);return ResultData.success("退出成功");}
}

对于其中service、mapper方法这里就不在细讲,因为不同需求实现的逻辑都不相同,所以提供一个controller给各位读者参考(其实根据方法名可以直到该方法做什么的👍)


到此认证服务器的基本功能就实现完了,总结一下主要做的就是登录认证,token的下发、刷新和维护(对于这块感兴趣的读者可以去查阅另一篇文章 对token无感刷新的理解)

3. 鉴权功能依赖集成

在本次的实战中,我使用在每个微服务引入自定义的鉴权jar包的方法实现鉴权(该鉴权功能通过springsecurity实现)具体架构如下,只需要在需要鉴权的微服务pom.xml中引入该adapter即可
在这里插入图片描述

3.1 pom依赖引入

主要就是springsecurity的依赖,另外common是实现的一些工具类jar包,就如2.2.1 所讲述的那些

<dependencies><!--  Spring Security依赖  --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><!--cloud_commons_utils--><dependency><groupId>com.simple.cloud</groupId><artifactId>simpleCloud_api_commons</artifactId><version>1.0-SNAPSHOT</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
</dependencies>

3.2 过滤器实现

核心就是获取到封装权限信息的UsernamePasswordAuthenticationToken ,方法定义如下图所示,其中第二个credentials放的是密码等,但是为了防止泄露在登录成功后会将其设置为null
在这里插入图片描述

@Order(1)
public class TokenAuthenticationFilter extends OncePerRequestFilter {public TokenAuthenticationFilter() {}@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)throws IOException, ServletException {logger.info("uri:"+request.getRequestURI());//获取包含权限的authentication UsernamePasswordAuthenticationToken authentication = getAuthentication(request);if(null != authentication) {SecurityContextHolder.getContext().setAuthentication(authentication);chain.doFilter(request, response);} else {ResponseUtil.out(response, ResultData.fail(ResultCodeEnum.RC401.getCode(), ResultCodeEnum.RC401.getMessage()));}}private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {//请求头是否有tokenString token = JWTHelper.getToken(request.getHeader(SecurityAccessConstant.HEADER_NAME_TOKEN));if(null != token) {String email = JWTHelper.getEmail(token);Long userId = JWTHelper.getUserId(token);List<String> permission = JWTHelper.getPermission(token);if(null != permission) {//当前用户信息放到ThreadLocal里面LoginUserInfoHelper.setUserId(userId);LoginUserInfoHelper.setEmail(email);//把权限数据转换要求集合类型 List<SimpleGrantedAuthority>List<SimpleGrantedAuthority> collect = permission.stream().map(val -> new SimpleGrantedAuthority(val)).collect(Collectors.toList());return new UsernamePasswordAuthenticationToken(email, null, collect);}}return null;}
}

3.3 springsecurity 配置类

注意在springboot3中集成的springsecurity已经淘汰掉继承 WebSecurityConfigurerAdapter 的方法,鼓励开发者自己写配置类将bean注入容器中

由于不需要做登录认证,只需要做权限校验,所以 不需要引入登录相关的filter(userdetailService等那些都不用引入)需要引入的只有前面定义的TokenAuthenticationFilter 和 PasswordEncoder (其实这个也不需要因为并没有在逻辑中用到,但是加上也不妨碍)然后就是根据自己的业务需求配置SecurityFilterChain 就好啦

@Configuration
@EnableWebSecurity //@EnableWebSecurity是开启SpringSecurity的默认行为
@EnableMethodSecurity //启用方法级别鉴权
public class WebSecurityConfig{@Beanpublic PasswordEncoder passwordEncoder() {return new CustomSHA_256PasswordEncoder();}@Beanpublic TokenAuthenticationFilter authenticationJwtTokenFilter() {return new TokenAuthenticationFilter();}@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http// 禁用basic明文验证.httpBasic().disable()// 前后端分离架构不需要csrf保护.csrf().disable()// 禁用默认登录页.formLogin().disable()// 禁用默认登出页.logout().disable()// 前后端分离是无状态的,不需要session了,直接禁用。.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)).authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests// 允许所有OPTIONS请求.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()// 允许 SpringMVC 的默认错误地址匿名访问.requestMatchers("/error").permitAll()// 允许任意请求被已登录用户访问,不检查Authority.anyRequest().authenticated())// 加我们自定义的过滤器,替代UsernamePasswordAuthenticationFilter.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);return http.build();}/*** 需要调用AuthenticationManager.authenticate执行一次校验** @param config* @return* @throws Exception*/@Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {return config.getAuthenticationManager();}
}

以上就是全部编码,在完成后可以打成jar包供其他微服务引入依赖

3.4 使用鉴权

在需要鉴权的微服务中引入依赖

<!-- security -->
<dependency><groupId>com.simple.cloud</groupId><artifactId>simpleCloud_security_adapter</artifactId><version>1.0-SNAPSHOT</version>
</dependency>

然后在指定的方法上用springsecurity提供的方法进行权限控制就好啦,如下例子

注意注解的使用,在内是 hasAuthority(权限名/类别) ,为了该注解能在方法级别起效必需在springsecurity的配置类上标注注解@EnableMethodSecurity 启用方法级别鉴权

/*** 获取所有用户列表* */
@GetMapping("/listAll")
@PreAuthorize("hasAuthority('bnt.sysUser.list')")
public ResultData getAllUser(){List<SysUser> sysUsers = sysUserService.findAllUsers();if(sysUsers != null)return ResultData.success(sysUsers);return ResultData.fail(ResultCodeEnum.RC996.getCode(), "查询失败,请联系管理员");
}

4. 总结

基于这种登录认证和权限认证分离的方式设计有好有坏,对于好处而言:

  • 集中式认证管理:通过统一的认证服务器进行登录认证和token的签发刷新,可以简化认证流程,提高安全性和效率。

  • 灵活性和可扩展性:各个微服务自行处理权限认证,可以根据各自的业务需求灵活设计权限控制逻辑,便于扩展和维护。

  • 适应多种鉴权场景:这种方式可以适应外部应用接入、用户-服务鉴权、服务-服务鉴权等多种鉴权场景。

同时也会带来一些坏处:

  • 潜在的安全风险:如果各个微服务的权限认证实现不一致或存在缺陷,可能会引入安全风险。
  • 性能考虑:每个请求都可能需要经过权限认证,如果没有合理的优化,可能会对系统性能产生影响。

现在基于OAuth2 的权限认证模式也是一种普遍的实现方案,对于上面自研认证服务毕竟还是没有实现高并发场景下的功能,所以有感兴趣的读者可以往这方面继续专研⛽

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/332181.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

来自Java的“菱形继承“,你听说过吗?

一、菱形继承的概念 菱形继承又叫做钻石继承&#xff0c;指的是不同的类同时继承自相同的父类&#xff0c;存在一个子类同时继承这些不同的类&#xff0c;即我们常说的“多继承”问题。 例如&#xff1a;B类和C类分别继承A类&#xff0c;而D类同时继承B类和C类。 如此图所示 二…

专业渗透测试 Phpsploit-Framework(PSF)框架软件小白入门教程(十三)

本系列课程&#xff0c;将重点讲解Phpsploit-Framework框架软件的基础使用&#xff01; 本文章仅提供学习&#xff0c;切勿将其用于不法手段&#xff01; 接上一篇文章内容&#xff0c;讲述如何进行Phpsploit-Framework软件的基础使用和二次开发。 我们&#xff0c;继续讲一…

Unity | 框架MVC

目录 一、MVC介绍 二、搭建UI界面 三、代码实现 1.Model层 2.View层 3.Controller层 四、MVC框架测试 五、知识补充 一、MVC介绍 model&#xff1a;数据层。界面展示的数据&#xff08;需要进行初始化、更新、保存、事件通知等操作&#xff09;&#xff0c;单例模式&am…

React中显示数据

SX 会让你把标签放到 JavaScript 中。而大括号会让你 “回到” JavaScript 中&#xff0c;这样你就可以从你的代码中嵌入一些变量并展示给用户。例如&#xff0c;这将显示 user.name&#xff1a; return (<h1>{user.name}</h1> ); 你还可以将 JSX 属性 “转义到 …

宁夏银川、山东济南、中国最厉害的改名大师的老师颜廷利教授的前沿思想观点

在当代社会&#xff0c;一个响亮的声音穿越了传统的迷雾&#xff0c;它来自东方哲学的殿堂&#xff0c;由一位现代学者颜廷利教授所发出。他的话语&#xff0c;如同一股清泉&#xff0c;在混沌的世界里激荡着思考的波澜&#xff1a;"有‘智’不在年高&#xff0c;无‘智’…

嵌入式之音频基础知识

声音特性 1、响度&#xff1a;人主观上感觉声音的大小&#xff08;俗称音量&#xff09;&#xff0c;由“振幅”和人离声源的距离决定&#xff0c;振幅越大响度越大&#xff0c;人和声源的距离越小&#xff0c;响度越大&#xff1b; 2、音调&#xff1a;声音的高低&#xff0…

无人机反制:光电干扰一体设备技术详解

一、光电干扰技术原理 光电干扰技术是一种利用光学和电子技术手段对无人机实施干扰和控制的先进技术。该技术通过向无人机发射特定频率和强度的光信号或电磁信号&#xff0c;干扰无人机的视觉系统、控制系统或通信链路&#xff0c;进而达到反制无人机的目的。光电干扰技术具有…

world machine学习笔记(4)

选择设备&#xff1a; select acpect&#xff1a; heading&#xff1a;太阳的方向 elevation&#xff1a;太阳的高度 select colour&#xff1a;选择颜色 select convexity&#xff1a;选择突起&#xff08;曲率&#xff09; select height&#xff1a;选择高度 falloff&a…

neo4j开放远程连接

注&#xff1a;本博客所用neo4j版本为社区5.12版 第一步&#xff1a;修改neo4j配置文件 首先找到neo4j的安装位置&#xff0c;点击进入conf文件夹&#xff0c;随后点击neo4j.conf文件&#xff0c;在“Network connector configuration”下面的单元中找到server.default_liste…

7款好用到离谱的神级App推荐!

AI视频生成&#xff1a;小说文案智能分镜智能识别角色和场景批量Ai绘图自动配音添加音乐一键合成视频https://aitools.jurilu.com/ 转眼间&#xff0c;2024年已经是下个月。最近有很多小伙伴的咨询&#xff0c;我也赶紧整理了7款好用的软件&#xff0c;希望对大家有所帮助。 …

Elasticsearch 分析器(内置分析器,自定义分析器,IK分析器)

Elasticsearch 分析器&#xff08;内置分析器&#xff0c;自定义分析器&#xff0c;IK分析器&#xff09; 内置分析器使用分析器自定义分析器中文分析器&#xff08;IK分析器&#xff09;安装使用添加词典 内置分析器 官网&#xff1a;https://www.elastic.co/guide/en/elasti…

03_前端三大件CSS

文章目录 CSS用于页面元素美化1.CSS引入1.1style方式1.2写入head中&#xff0c;通过写style然后进行标签选择器加载样式1.3外部样式表 2.CSS样式选择器2.1 元素选择器2.2 id选择器2.3 class选择器 3.CSS布局相关3.1 CSS浮动背景&#xff1a;先设计一些盒子因此&#xff0c;引出…

【qt】QTreeWidget 树形组件

QTreeWidget 树形组件 一.什么是树形组件二.界面设计树形组件三.代码实现1.清空2.设置列数3.设置头标签4.添加根目录①QTreeWidgetitem②设置文本③设置图标④添加为顶层目录 5.添加子目录①初始化为父目录②子目录添加到父目录③获取到子目录 四.插入目录1.获取当前选中目录项…

python数据类型之元组、集合和字典

目录 0.三者主要作用 1.元组 元组特点 创建元组 元组解包 可变和不可变元素元组 2.集合 集合特点 创建集合 集合元素要求 集合方法 访问与修改 子集和超集 相等性判断 集合运算 不可变集合 3.字典 字典特点 字典创建和常见操作 字典内置方法 pprin模块 0.…

Vxe UI 表单设计器、零代码平台

vxe-pc-ui Vxe UI 表单设计器、零代码表单设计器 安装 Vxe UI PC端组件库 官方文档 查看 github、gitee // ...import VxeUI from vxe-pc-uiimport vxe-pc-ui/lib/style.css// ...// ...createApp(App).use(VxeUI).mount(#app)// ...使用 vxe-form-design 设计器组件 vxe-fo…

分享活动规划

前两天去参加菁英学院的一些辅导&#xff0c;是关于苏州久富农业机械的发展&#xff0c;看了他们企业的故事&#xff0c;我觉得我们农机很有前景和发展空间&#xff0c;我希望重新经过一次分享活动来分享我的感触&#xff0c;希望能够再次把我学到的内容传输到其他班的同学们 请…

word 全文中 英文字体 和 样式的字体 莫名奇妙地 被改成 “等线”

word全文中英文字体和样式的字体莫名奇妙地被改成“等线” sm word又抽风了&#xff0c;改完论文保存后打开突然发现全文字体都不对劲&#xff0c;吓得冷汗直冒&#xff1a;虽然我用git管理了论文版本&#xff0c;但是只有比较大的修改我才上传了&#xff0c;刚刚修了几个小时…

Redis篇 redis基本命令和定时器原理

基本命令和定时器原理 一. exists命令二. del命令三. Expire命令四. ttl命令五. redis的过期策略六. 定时器的两种设计方式七. type命令 一. exists命令 用来判断key的值是否存在 返回值是key的个数 这样写的话&#xff0c;有没有什么区别呢&#xff1f; 效率变低&#xff0c;消…

猫抓(cat-catch)插件的常规用法

目录 1.1、前言1.2、抓取图片资源1.3、抓取音频资源1.4、抓取视频资源 1.1、前言 本文将介绍利用猫抓&#xff08;cat-catch&#xff09;插件如下抓取网页上的图片、音频、视频等资源&#xff0c;猫抓&#xff08;cat-catch&#xff09;插件的安装及设置请参考推荐一款媒体影音…