SpringBoot + SpringSecurity + redis 整合优化版(2)

版本:SprintBoost2.7.0、  SpringSecurity5.4.x以上、Redis

跟上一篇文章不一样,这次我们采取Redis来存储用户Token的方法来实现

老规矩,下面就让我们按流程来吧。

首先,还是一样,先让我们实现一个登陆的用户实体对象,这里添加@JsonIgnore注解是因为通过Redis将这个实体序列化 -> 反序列化,会因为没有属性下面几个方法会报错。具体注解还得根据Redis的序列化策略还添加,我这边Redis序列化策略用的是jackson2, 所以我就用jackson的注解。具体这个类内的用户属性,是采用继承还是直接创建个User属性,都可以按自己喜好实现,我这里选择了直接实现数据库的用户实体类。

package com.mrlv.rua.auth.entity;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.google.common.collect.Lists;
import com.mrlv.rua.admin.entity.SysPerm;
import com.mrlv.rua.admin.entity.SysRole;
import com.mrlv.rua.admin.entity.SysUser;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author lvshiyu
 * @description: 登陆用户信息体
 * @date 2022年07月06日 15:33
 */
public class LoginUser extends SysUser implements UserDetails {

    /**
     * 权限列表,为什么是字符串,是因为我这里只需要保存用户所拥有的权限唯一标识即可,可自行改动。
     */
    private List<String> permissionList;

    public List<String> getPermissionList() {
        return permissionList;
    }

    public void setPermissionList(List<String> permissionList) {
        this.permissionList = permissionList;
    }

    /**
     * 返回授予用户的权限。 不能返回null。
     * 返回:权限,按自然键排序(从不为空)
     * @return
     */
    @JsonIgnore
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        //如果用户的权限为null,则返回空数组
        if (permissionList == null){
            return new ArrayList<>(0);
        }
        List<SimpleGrantedAuthority> authorities = permissionList.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
        return authorities;
    }

    /**
     * 指示用户的帐户是否已过期。过期的帐户无法进行身份验证。
     * 返回:如果用户的帐户有效(即未过期),返回true,如果不再有效(即过期),返回false。
     * @return
     */
    @JsonIgnore
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    /**
     * 用户被锁定或解锁状态。被锁定的用户无法进行认证。
     * 如果用户没有被锁定,返回true,否则返回false
     * @return
     */
    @JsonIgnore
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    /**
     * 指示用户的凭据(密码)是否已过期。过期的凭据将阻止身份验证。
     * 返回:如果用户的凭证有效(即未过期),返回true,如果不再有效(即过期),返回false。
     * @return
     */
    @JsonIgnore
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    /**
     * 表示该用户是启用还是禁用。 被禁用的用户无法进行认证。
     * 返回:如果用户已启用,则为true,否则为false
     * @return
     */
    @JsonIgnore
    @Override
    public boolean isEnabled() {
        return true;
    }

    @Override
    public String toString() {
        return "LoginUser{" +
                "permissionList=" + permissionList +
                '}';
    }
}

接下来实现Securtiy内置的UserDetailsService接口,这个接口的作用就是后期配Security配置中将其配置进去,是Security内置的登陆校验器,但具体登陆账户信息获取这一块得我们来实现。我这里表关联已经把用户信息和权限查询了出来。

package com.mrlv.rua.auth.service.impl;

import com.mrlv.rua.auth.mapper.SysUserMapper;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * @author lvshiyu
 * @description: TODO
 * @date 2022年07月06日 16:41
 */
@Service("userDetailsService")
public class UserDetailsServiceImpl implements UserDetailsService {

    @Resource
    private SysUserMapper sysUserMapper;

    /**
     * 根据用户名查询用户信息
     * @param username
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //根据用户账户查询用户的信息进行校验
        UserDetails userDetails = sysUserMapper.getUserDetails(username);
        //完成校验,赋予授权
        return userDetails;
    }
}

然后是需要实现Security内置的接口FilterInvocationSecurityMetadataSource,这个接口的作用是用来获取全局权限元数据。我们这里采用静态的Map来存储。这里的Map,Key存的是权限的接口路径,value存储的是权限的唯一标识,后面我需要通过路径来获取该接口所需要的权限标识。

DynamicSecurityMetadataSource中,获取我们的每个请求,然后通过Object我们获取请求的URL,再根据请求接口的URL获取访问该接口所需要的权限标识,并返回标识集合

DynamicSecurityMetadataSource中还加了个clearDataSource方法,用来后期清除缓存用。

package com.mrlv.rua.auth.security;

import cn.hutool.core.util.URLUtil;
import com.google.common.collect.Lists;
import com.mrlv.rua.auth.service.IDynamicSecurityService;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

/**
 * @author lvshiyu
 * @description: 动态权限数据源,用于获取动态权限
 * @date 2022年07月05日 17:51
 */
@Component
public class DynamicSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {

    /**
     * 权限集合
     */
    private static Map<String, ConfigAttribute> configAttributeMap = null;

    /**
     * 动态权限服务
     */
    @Resource
    private IDynamicSecurityService dynamicSecurityService;

    /**
     * 初始化所有的对应权限集合
     */
    @PostConstruct
    public void loadDataSource() {
        //加载所有的URL和资源map
        configAttributeMap = dynamicSecurityService.loadDataSource();
    }

    /**
     * 获取访问路径所需要的权限
     * @param object
     * @return
     * @throws IllegalArgumentException
     */
    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        //如果内存中的缓存数据为空,则加载所有的URL和资源map
        if (configAttributeMap == null) {
            this.loadDataSource();
        }
        List<ConfigAttribute> configAttributes = new ArrayList<>();
        //获取当前访问的路径
        String url = ((FilterInvocation) object).getRequestUrl();
        String path = URLUtil.getPath(url);
        //获取访问该路径所需资源
        PathMatcher pathMatcher = new AntPathMatcher();
        configAttributeMap.forEach((key, value) -> {
            if (pathMatcher.match(key, path)){
                configAttributes.add(value);
            }
        });
        //未设置操作请求权限,返回空集合
        return configAttributes;
    }

    /**
     * 清除权限集合
     */
    public static void clearDataSource() {
        configAttributeMap = null;
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }

}

dynamicSecurityService就是个普通的service,对权限表进行了列表查询,区别是这里的返回做了下处理。

    public Map<String, ConfigAttribute> loadDataSource() {
        //查询所有的动态权限
        List<SysPerm> sysPerms = sysPermMapper.selectList(new QueryWrapper<>());
        Map<String, ConfigAttribute> collect = sysPerms.stream().collect(Collectors.toMap(
                SysPerm::getPath,
                e -> new org.springframework.security.access.SecurityConfig(e.getPermission())));
        return collect;
    }

接下来,我们还要实现Security内置的接口AcessDecisionManager。这个接口的作用是用来比较登陆用户的权限的,将上面那个接口获取到访问接口所需要的权限标识和登陆用户所拥有的权限进行比对。

package com.mrlv.rua.auth.security;

import cn.hutool.core.collection.CollUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Component;

import java.util.Collection;

/**
 * @author lvshiyu
 * @description: 自定义访问权限决策管理器,用于判断用户是否有访问权限
 * @date 2022年07月05日 17:39
 */
@Component
@Slf4j
public class DynamicAccessDecisionManager implements AccessDecisionManager {

    /**
     * 访问权限决策
     * @param authentication 用户拥有的权限
     * @param object
     * @param configAttributes 资源所需要的权限
     * @throws AccessDeniedException
     * @throws InsufficientAuthenticationException
     */
    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
        //当接口未被配置资源时直接放行
        if (CollUtil.isEmpty(configAttributes)) {
            //未配置资源访问限制
            log.info("未配置资源访问限制");
            return;
        }
        for (ConfigAttribute attribute : configAttributes) {
            SimpleGrantedAuthority needAuthority = new SimpleGrantedAuthority(attribute.getAttribute());
            //将访问所需资源或用户拥有资源进行比对
            if (authentication.getAuthorities().contains(needAuthority)) {
                return;
            }
        }
        throw new AccessDeniedException("抱歉,您没有访问权限");
    }

    @Override
    public boolean supports(ConfigAttribute attribute) {
        return true;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }
}

基本的实现都差不多后我们开始实现登陆接口,Controller我就不发出来了,直接上Service。

首先构建一个Security的登陆实体类,走内置的登陆认证。会根据我们UserDetailsServiceImpl所返回的查询结果进行认证,如果通过了,我们将用户信息写入Redis中,将key构建Token返回给前台;如果不通过则抛出对应的异常,我们这里对异常进行捕捉并返回想要返回的错误信息。当然,也可以再全局异常中进行捕捉。至于AuthenticationManager ,我们且看后面。

package com.mrlv.rua.auth.service.impl;

import com.mrlv.rua.auth.consts.RedisPreConst;
import com.mrlv.rua.auth.dto.LoginDTO;
import com.mrlv.rua.auth.entity.LoginUser;
import com.mrlv.rua.auth.mapper.SysUserMapper;
import com.mrlv.rua.auth.service.ILoginService;
import com.mrlv.rua.auth.utils.JwtUtil;
import com.mrlv.rua.common.exception.MasterException;
import com.mrlv.rua.common.redis.utils.RedisUtil;
import com.mrlv.rua.common.wrapper.Result;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * @author lvshiyu
 * @description: 用户登陆服务
 * @date 2022年07月06日 15:01
 */
@Service

public class LoginServiceImpl implements ILoginService {

    @Resource
    private AuthenticationManager authenticationManager;

    /**
     * 登陆
     * @param dto
     * @return
     */
    @Override
    public Result login(LoginDTO dto) {
        //进行用户认证  获取AuthenticationManager authenticate
        //构建认证对象
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(dto.getUsername(),
                dto.getPassword());
        //登陆认证...
        try {
            Authentication authenticate = authenticationManager.authenticate(authenticationToken);
            if (authenticate == null) {
                throw new MasterException("登陆失败");
            }
            LoginUser loginUser = (LoginUser)authenticate.getPrincipal();
            //写Redis
            boolean result = RedisUtil.hset(RedisPreConst.AUTH_ONLINE_USER + loginUser.getId(), "PC", loginUser);
            if (result) {
                //认证成功,生成token
                return Result.createBySuccess(JwtUtil.createToken(loginUser.getId() , "PC"));
            }
            return Result.createByErrorMessage("登陆失败");
        } catch (AccountExpiredException e) {
            //账号过期
            return Result.createByErrorMessage("账号过期");
        } catch (BadCredentialsException e) {
            //密码错误
            return Result.createByErrorMessage("密码错误");
        } catch (CredentialsExpiredException e) {
            //密码过期
            return Result.createByErrorMessage("密码过期");
        } catch (DisabledException e) {
            //账号不可用
            return Result.createByErrorMessage("账号不可用");
        } catch (LockedException e) {
            //账号锁定
            return Result.createByErrorMessage("账号锁定");
        } catch (InternalAuthenticationServiceException e) {
            //用户不存在
            return Result.createByErrorMessage("用户不存在");
        } catch (AuthenticationException e) {
            //其他错误
            return Result.createByErrorMessage("其他错误");
        } catch (Exception e) {
            throw new RuntimeException("登陆失败");
        }
    }
}

AuthenticationManager 需要注入依赖,这个依赖我们在配置类里面注册,在此之前,我们先创建一个过滤器。这里过滤器拦截请求,并验证其Token是否正常,然后从Redis中取出用户信息,并写入全局上下文。 如果没有Token或Token异常,则直接往下走,让Security自行处理(因为上下文取不到用户信息,所有会跳到未登录页面)。

package com.mrlv.rua.auth.security;

import cn.hutool.core.util.StrUtil;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.mrlv.rua.auth.consts.RedisPreConst;
import com.mrlv.rua.auth.entity.LoginUser;
import com.mrlv.rua.auth.utils.JwtUtil;
import com.mrlv.rua.common.redis.utils.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.SecurityMetadataSource;
import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
import org.springframework.security.access.intercept.InterceptorStatusToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.FilterInvocation;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.annotation.Resource;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author lvshiyu
 * @description: 动态权限过滤器
 * @date 2022年07月05日 17:32
 */
@Component
@Slf4j
public class DynamicSecurityFilter extends OncePerRequestFilter {

    /**
     * 过滤
     * @param request
     * @param response
     * @param filterChain
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //获取请求头,判断是否已经登陆
        String token = request.getHeader(JwtUtil.HEADER_STRING);
        if (StrUtil.isNotBlank(token)) {
            String userId = null;
            try {
                userId = JwtUtil.getUserId(token);
                String key = RedisPreConst.AUTH_ONLINE_USER + userId;
                if (RedisUtil.hHasKey(key, "PC")) {
                    LoginUser userDetails = (LoginUser)RedisUtil.hget(key, "PC");
                    UsernamePasswordAuthenticationToken user = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                    SecurityContextHolder.getContext().setAuthentication(user);
                }
            } catch (JWTVerificationException e) {
              log.info("token异常 error:{}", e.getMessage(), e);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        filterChain.doFilter(request, response);
    }
}

最后是通过配置类SpringSecurityConfig把所有的东西拼装起来。

首先把过滤器DynamicSecurityFilter、用户信息获取UserDetailsService、权限决策器DynamicAccessDecisionManager、权限数据源加载DynamicSecurityMetadataSource 依赖注入,

然后逐个配置上。

这里有两个异常处理:无权访问、无登录。分别返回对应的JSON。 同时放开登陆接口 /login。

最后把我们的过滤器DynamicSecurityFilter配置在内置过滤器FilterSecurityInterceptor之前,同时配置UserDetailsService

注入依赖  AuthenticationManager ,用于 loginService 注入调用登陆认证。

我这里的密码加密没有配置,如有需要可以自行配置。

package com.mrlv.rua.auth.security;

import cn.hutool.json.JSONUtil;
import com.mrlv.rua.common.wrapper.Result;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.*;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;

import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;

import javax.annotation.Resource;

/**
 * @author lvshiyu
 * @description: SpringSecurity 5.4.x以上新用法配置 为避免循环依赖,仅用于配置HttpSecurity
 * @date 2022年07月05日 15:43
 */
@Configuration
public class SpringSecurityConfig {

    @Resource
    private DynamicSecurityFilter dynamicSecurityFilter;

    @Resource
    private UserDetailsService userDetailsService;


    @Resource
    private DynamicAccessDecisionManager accessDecisionManager;

    @Resource
    private DynamicSecurityMetadataSource securityMetadataSource;

    /**
     * 配置过滤
     * @param security
     * @return
     * @throws Exception
     */
    @Bean
    SecurityFilterChain filterChain(HttpSecurity security) throws Exception {
        security.cors().and().csrf().disable()
                .authorizeRequests()
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O o) {
                        //决策管理器
                        o.setAccessDecisionManager(accessDecisionManager);
                        //安全元数据源
                        o.setSecurityMetadataSource(securityMetadataSource);
                        return o;
                    }
                }).and()
                //关闭session,不通过Session获取SecurityContext
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                //异常处理(权限拒绝、登录失效等)
                .and().exceptionHandling()
                    .authenticationEntryPoint((request, response, accessDeniedException) -> {
                        //处理匿名用户访问无权限资源时的异常(即未登录,或者登录状态过期失效)
                        response.setContentType("application/json;charset=utf-8");
                        response.getWriter().write(JSONUtil.toJsonStr(Result.createByErrorMessage("请登录")));
                    })
                    .accessDeniedHandler((request, response, accessDeniedException) -> {
                        //返回json形式的错误信息
                        response.setContentType("application/json;charset=utf-8");
                        response.getWriter().write(JSONUtil.toJsonStr(Result.createByErrorMessage("没有权限访问")));
                        response.getWriter().flush();
                    })
                //对于登录接口 允许匿名访问
                .and().authorizeRequests()
                .antMatchers("/login").anonymous()
                //特定化权限的写法
                //.antMatchers("/textCors").hasAuthority("system:ddd:aaa")
                //除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated();
        //在security原生过滤器之前添加过滤器
        return security.addFilterBefore(dynamicSecurityFilter, FilterSecurityInterceptor.class)
                .userDetailsService(userDetailsService)
                .build();
    }

    /**
     * 获取AuthenticationManager(认证管理器),登录时认证使用
     * @param authenticationConfiguration
     * @return
     * @throws Exception
     */
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }

    /**
     * 添加加密配置
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new PasswordEncoder() {
            @Override
            public String encode(CharSequence rawPassword) {
                return rawPassword.toString();
            }

            @Override
            public boolean matches(CharSequence rawPassword, String encodedPassword) {
                if (rawPassword == null) {
                    throw new IllegalArgumentException("rawPassword cannot be null");
                }
                if (encodedPassword == null || encodedPassword.length() == 0) {
                    return false;
                }
                if (!rawPassword.equals(encodedPassword)) {
                    return false;
                }
                return true;
            }
        };
    }
}

以上整合SpringBoot、SpringSecurity完成,实现了前后端分离,用户状态保存到Redis(支持分布式认证、系统重启也不会导致用户离线、可以手动让用户离线等优势)。

可能讲的不够细,原理什么的也没有细讲,这里只说实现功能,如果遇到什么问题可以留言。

### 回答1: springcloud是一个开源的微服务框架,它基于Spring Boot,并提供了一整套解决方案,用于构建分布式系统中的各个微服务。通过使用springcloud,我们可以轻松实现服务注册与发现、负载均衡、断路器、配置中心等功能,简化了微服务开发和管理的复杂度。 springboot是一个基于Spring的轻量级开发框架,它通过开箱即用的原则,提供了一种快速构建应用程序的方式。使用springboot,我们可以简化繁琐的配置,只需少量的代码即可实现一个功能完整的应用程序,并且可以方便地和其他Spring生态的框架进行集成。 OAuth2是一种授权协议,用于保护Web应用程序、移动应用程序和API的资源。通过OAuth2协议,用户可以授权第三方应用程序访问他们的资源,而无需提供他们的密码。它提供了一种安全且可扩展的机制来处理用户身份验证和授权,并且被广泛应用于各种应用程序中。 Spring Security是一个Java框架,用于提供身份验证和访问控制的功能。它可以轻松地集成到Spring应用程序中,提供了一套强大的API和安全策略,用于保护应用程序免受各种攻击,包括身份验证和授权、会话管理、密码加密等。 Redis是一种内存数据存储系统,它以键值对的形式存储数据,并支持多种数据结构,如字符串、列表、集合、有序集合等。Redis具有高速、持久化和可扩展性等特点,可用于缓存、消息队列、分布式锁等各种场景。在使用Spring框架开发时,我们可以使用Redis作为缓存层,提高应用程序的性能和响应速度。 综上所述,Spring Cloud提供了构建和管理微服务的解决方案,Spring Boot简化了应用程序的开发,OAuth2和Spring Security提供了安全和授权的功能,而Redis作为内存数据存储系统,为应用程序提供了可扩展的缓存和数据存储能力。这些技术和框架相互协作,可以帮助开发者更快速、更安全地构建分布式系统。 ### 回答2: Spring Cloud是一个用于构建分布式系统的开发工具包,它提供了多个子项目来解决分布式系统的常见问题,例如服务注册与发现、配置管理、断路器、负载均衡等。Spring Boot是用于简化Spring应用程序开发的工具,它提供了一种自动配置的方式来快速搭建和运行Spring应用。OAuth2是一个开放标准,用于授权访问特定资源,它允许用户使用某个网站的授权信息来访问其他网站上的受保护资源。Spring Security是一个全面的身份验证和授权框架,它提供了一套安全服务,用于保护Web应用程序中的资源。Redis是一个高性能的键值存储系统,它常被用作缓存、队列、消息中间件等。 结合以上几个技术,可以构建一个基于Spring Cloud的分布式系统,使用Spring Boot快速搭建各个服务,使用Spring Security进行身份验证和授权管理。而OAuth2可以用于保护系统中的资源,通过认证服务器进行用户认证和授权,使得只有授权的用户才能访问相应的资源。Spring Security与OAuth2可以集成使用,通过Spring Security提供的权限管理功能来管理不同角色对资源的访问权限。同时,将Redis作为缓存服务器,可用于提高系统的性能和响应速度。 总之,Spring Cloud、Spring Boot、OAuth2、Spring SecurityRedis等技术可以在构建分布式系统时发挥重要作用,帮助我们快速搭建实现各个功能模块,并提供高性能和安全性。 ### 回答3: Spring Cloud是一套基于Spring Boot的微服务框架,它提供了在分布式系统中构建和管理各种微服务的解决方案。它具有服务注册与发现、负载均衡、熔断、服务网关等功能,可以方便地实现微服务架构。 Spring Boot是一个用于快速开发基于Spring框架的应用程序的工具,它简化了Spring应用程序的配置和部署流程。它提供了自动化配置、内嵌服务器、开箱即用的特性,使得我们只需要关注业务逻辑的开发而不用过多关注框架的配置。 OAuth2是一种开放标准的授权协议,它使得用户可以通过授权的方式将与用户相关的信息共享给第三方应用程序。它使用令牌的方式进行授权,具有安全性高、可扩展性好的优点,常用于实现单点登录和授权管理。 Spring Security是一个用于在Java应用程序中提供身份验证和访问控制的框架。它可以与Spring BootSpring Cloud集成,提供了认证、授权、密码加密等功能,帮助我们更好地保护应用程序的安全。 Redis是一种高性能的键值存储系统,它支持多种数据结构,如字符串、列表、哈希表等。它具有高并发读写、持久化、分布式等特点,常用于缓存、消息队列、会话管理等场景。 综上所述,Spring Cloud提供了构建微服务的解决方案,Spring Boot简化了Spring应用程序的开发,OAuth2实现了授权管理,Spring Security提供了身份验证和访问控制,而Redis则可以用于缓存和数据存储。这些技术的结合可以帮助我们构建安全、高效的分布式系统。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值