微服务Redis-Session共享登录状态

news/2024/7/4 7:37:51 标签: 微服务, 架构, 云原生

一、背景

        随着项目越来越大,需要将多个服务拆分成微服务,使代码看起来不要过于臃肿,庞大。微服务之间通常采取feign交互,为了保证不同微服务之间增加授权校验,需要增加Spring Security登录验证,为了多个服务之间session可以共享,可以通过数据库实现session共享,也可以采用redis-session实现共享。

        本文采取Spring security做登录校验,用redis做session共享。实现单服务登录可靠性,微服务之间调用的可靠性与通用性

二、代码

本文项目采取 主服务一服务、子服务二 来举例

1、服务依赖文件

主服务依赖

    implementation group: 'org.springframework.boot', name: 'spring-boot-starter-data-redis', version: '2.5.4'
    implementation group: 'org.springframework.session', name: 'spring-session-data-redis', version: '2.4.1'
    implementation(group: 'io.github.openfeign', name: 'feign-httpclient')
    implementation 'org.springframework.boot:spring-boot-starter-security'

子服务依赖

    implementation group: 'org.springframework.boot', name: 'spring-boot-starter-data-redis', version: '2.5.4'
    implementation group: 'org.springframework.session', name: 'spring-session-data-redis', version: '2.4.1'
    implementation 'org.springframework.boot:spring-boot-starter-security'

2、服务配置文件

主服务配置文件

#redis连接

spring.redis.host=1.2.3.4

#Redis服务器连接端口

spring.redis.port=6379

#Redis服务器连接密码

spring.redis.password=password

#连接池最大连接数(使用负值表示没有限制)

spring.redis.pool.max-active=8

#连接池最大阻塞等待时间(使用负值表示没有限制)

spring.redis.pool.max-wait=-1

#连接池中的最大空闲连接

spring.redis.pool.max-idle=8

#连接池中的最小空闲连接

spring.redis.pool.min-idle=0

#连接超时时间(毫秒)

spring.redis.timeout=30000

#数据库

spring.redis.database=0

#redis-session配置

spring.session.store-type=redis

#部分post请求过长会报错,需加配置

server.tomcat.max-http-header-size=65536

子服务配置文件

#单独登录秘钥

micService.username=service

micService.password=aaaaaaaaaaaaa

#登录白名单

micService.ipList=1.2.3.4,1.2.3.5,127.0.0.1,0:0:0:0:0:0:0:1

spring.redis.host=1.2.3.4

#Redis服务器连接端口

spring.redis.port=6379

#Redis服务器连接密码

spring.redis.password=password

#连接池最大连接数(使用负值表示没有限制)

spring.redis.pool.max-active=8

#连接池最大阻塞等待时间(使用负值表示没有限制)

spring.redis.pool.max-wait=-1

#连接池中的最大空闲连接

spring.redis.pool.max-idle=8

#连接池中的最小空闲连接

spring.redis.pool.min-idle=0

#连接超时时间(毫秒)

spring.redis.timeout=30000

#数据库

spring.redis.database=0

#最大请求头限制

server.maxPostSize=-1

server.maxHttpHeaderSize=102400

#redis session缓存

spring.session.store-type=redis

server.servlet.session.timeout=30m

3、登录校验文件

主服务SecurityConfig.java

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    //注入密码加密的类

    @Bean
    public AuthenticationProvider authenticationProvider() {
        AuthenticationProvider authenticationProvider = new EncoderProvider();
        return authenticationProvider;
    }

    @Autowired
    public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
        auth.authenticationProvider(authenticationProvider());
    }


    public static final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                .successHandler((request,response,authentication) -> {
                    HttpSession session = request.getSession();
                    session.setAttribute("TaobaoUser",authentication.getPrincipal());
                    ObjectMapper mapper = new ObjectMapper();
                    response.setContentType("application/json;charset=utf-8");
                    PrintWriter out = response.getWriter();
                    TaobaoUser user = (CurrentUser)session.getAttribute("TaobaoUser");
                    out.write(mapper.writeValueAsString(user));
                    out.flush();
                    out.close();
                })
                .failureHandler((request,response,authentication) -> {
                    ObjectMapper mapper = new ObjectMapper();
                    response.setContentType("application/json;charset=utf-8");
                    PrintWriter out = response.getWriter();
                    out.write(mapper.writeValueAsString(new ExceptionMessage("400",authentication.getMessage())));
                    out.flush();
                    out.close();
                })
                .loginPage("/Login.html")
                .loginProcessingUrl("/login")
                .and()
                .authorizeRequests()
                .antMatchers("/api/common/invalidUrl","/**/*.css", "/**/*.js", "/**/*.gif ", "/**/*.png ", "/**/*.jpg", "/webjars/**", "**/favicon.ico", "/guestAccess", "/Login.html",
                        "/v2/api-docs","/configuration/security","/configuration/ui","/api/common/CheckLatestVersionInfo").permitAll()
                .anyRequest()
                //任何请求
                .authenticated()
                .and()
                .sessionManagement()
                .maximumSessions(-1)
                .sessionRegistry(sessionRegistry());
        ;
        http.csrf().disable();
    }
    @Autowired
    private FindByIndexNameSessionRepository sessionRepository;

    @Bean
    public SpringSessionBackedSessionRegistry sessionRegistry(){
        return new SpringSessionBackedSessionRegistry(sessionRepository);
    }

}

EncoderProvider.java

@Service
public class EncoderProvider implements AuthenticationProvider {
    public static final Logger logger = LoggerFactory.getLogger(EncoderProvider.class);

    /**
     * 自定义验证方式
     */
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        try {
            //支持用户名和员工号登录 可能为用户名或员工号
            String username = authentication.getName();
            String credential = (String) authentication.getCredentials();
            //加密过程在这里体现
            TaobaoUser user= userService.getUserData(username);
            //校验,用户名是否存在
            if(user==null){
                throw new DisabledException("用户名或密码错误");
            }
            //校验登录状态
            checkPassword()
            Collection<GrantedAuthority> authorities = new ArrayList<>();
            return new UsernamePasswordAuthenticationToken(userCurrent, credential, authorities);
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new DisabledException("登录发生错误 : " + ex.getMessage());
        }
    }


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

子服务SecurityConfig.java

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    //注入密码加密的类

    @Bean
    public AuthenticationProvider authenticationProvider() {
        AuthenticationProvider authenticationProvider = new EncoderProvider();
        return authenticationProvider;
    }

    @Autowired
    public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(authenticationProvider());
    }


    public static final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        logger.info("用户登录日志test1 username:"+http.toString());
                http.formLogin()
                    .loginProcessingUrl("/login")
                    .successHandler((request,response,authentication) -> {
                    HttpSession session = request.getSession();
                    session.setAttribute("TaobaoUser",authentication.getPrincipal());
                    ObjectMapper mapper = new ObjectMapper();
                    response.setContentType("application/json;charset=utf-8");
                    PrintWriter out = response.getWriter();
                    TaobaoUser user = (TaobaoUser )session.getAttribute("TaobaoUser");
                    out.write(mapper.writeValueAsString(user));
                    out.flush();
                    out.close();
                })
                .failureHandler((request,response,authentication) -> {
                    ObjectMapper mapper = new ObjectMapper();
                    response.setContentType("application/json;charset=utf-8");
                    PrintWriter out = response.getWriter();
                    out.write(mapper.writeValueAsString(new ExceptionMessage("400",authentication.getMessage())));
                    out.flush();
                    out.close();
                })
                .loginPage("/Login.html")
                .and()
                .authorizeRequests()
                .antMatchers("/**/*.css", "/**/*.js", "/**/*.gif ", "/**/*.png ",
                        "/**/*.jpg", "/webjars/**", "**/favicon.ico", "/Login.html",
                        "/v2/api-docs","/configuration/security","/configuration/ui").permitAll()
                .anyRequest().authenticated()
                .and()
                .sessionManagement()
                .maximumSessions(-1)
                .sessionRegistry(sessionRegistry());
        http.csrf().disable();
    }
    @Autowired
    private FindByIndexNameSessionRepository sessionRepository;

    @Bean
    public SpringSessionBackedSessionRegistry sessionRegistry(){
        return new SpringSessionBackedSessionRegistry(sessionRepository);
    }

}

EncoderProvider.java

@Service
public class EncoderProvider implements AuthenticationProvider {
    public static final Logger logger = LoggerFactory.getLogger(EncoderProvider.class);

    @Value("${service.username}")
    private String  userName1;
    @Value("${service.ipList}")
    private String  ipList;

    /**
     * 自定义验证方式
     */
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        try {
            //支持用户名和员工号登录 可能为用户名或员工号
            String username = authentication.getName();
            String credential = (String)authentication.getCredentials();
            TaobaoUser user=new TaobaoUser();
            if(username.equals(userName1)){
                  List<String> ips = Arrays.asList(ipList.split(","));
                  WebAuthenticationDetails details = (WebAuthenticationDetails) authentication.getDetails();
                  String remoteIp = details.getRemoteAddress();
                  logger.info("ip为{}-通过用户{}调用接口",remoteIp,username);
                  if(!ips.contains(remoteIp)){
                      throw new DisabledException("无权登陆!");
                  }
            }else{
                throw new DisabledException("账户不存在!");
            }
            user.setA("A");
            Collection<GrantedAuthority> authorities = new ArrayList<>();
            return new UsernamePasswordAuthenticationToken(currentUser, credential, authorities);
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new DisabledException("登录发生错误 : " + ex.getMessage());
        }
    }

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

}

4、主服务feign配置

FeignManage.java

#url = "${file.client.url}",
@FeignClient(name="file-service",
            fallback = FeignFileManageFallback.class,
            configuration = FeignConfiguration.class)
public interface FeignFileManage {

    @RequestMapping(value = "/file/upload", method = {RequestMethod.POST}, consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
    ApiBaseMessage fileUpload(@RequestPart("file") MultipartFile file, @RequestParam("fileName") String fileName) ;



}
public class FeignManageFallback implements FeignManage{
 
    @Override
    public ApiBaseMessage fileUpload(MultipartFile file, String type) {
        return ApiBaseMessage.getOperationSucceedInstance("400","失败");
    }



}
FeignFileManageFallback.java
FeignConfiguration.java
@Configuration
@Import(FeignClientsConfiguration.class)
public class FeignConfiguration {
    /**
      删除请求头文件
     */
    final String[] copyHeaders = new String[]{"transfer-encoding","Content-Length"};

    @Bean
    public FeignFileManageFallback echoServiceFallback(){
        return new FeignFileManageFallback();
    }


    @Bean
    public FeignBasicAuthRequestInterceptor getFeignBasicAuthRequestInterceptor(){
        return new FeignBasicAuthRequestInterceptor();
    }

    /**
     *    feign 调用,添加CurrentUser
     */
    private class FeignBasicAuthRequestInterceptor implements RequestInterceptor {
        @Override
        public void apply(RequestTemplate template) {
            //1、使用RequestContextHolder拿到刚进来的请求数据
            ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

            if (requestAttributes != null) {
                HttpServletRequest request = requestAttributes.getRequest();
                Enumeration<String> headerNames = request.getHeaderNames();
                if (headerNames != null) {
                    while (headerNames.hasMoreElements()) {
                        String name = headerNames.nextElement();
                        String values = request.getHeader(name);
                        //删除的请求头
                        if (!Arrays.asList(copyHeaders).contains(name)) {
                            template.header(name, values);
                        }
                    }
                }
            }else{
                template.header("Accept", "*/*");
                template.header("Accept-Encoding", "gzip, deflate, br");
                template.header("Content-Type", "application/json");
            }
            //增加用户信息
            if(requestAttributes!=null && SessionHelper.getCurrentUser()!=null){
                try {
                    template.header("TaobaoUser", URLEncoder.encode(JSON.toJSONString(SessionHelper.getCurrentUser()),"utf-8") );
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}

5、主服务session文件

SessionUtil.java

public class SessionUtil {
    public static CurrentUser getCurrentUser() {
        HttpSession session = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getSession();
        CurrentUser user = (CurrentUser)session.getAttribute("TaobaoUser");
        return user;
    }

    public static void setCurrentUser(String userName){
        HttpSession session = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest().getSession();
        Collection<GrantedAuthority> authorities = new ArrayList<>();
        session.setAttribute("TaobaoUser",new CurrentUser(userName, "", authorities));
    }
}

三、完成配置后

1、直接访问主服务接口,不登录无法访问

2、直接访问自服务,不登录无法访问,(可通过nacos配置用户密码实现登录)

3、主服务通过feign调用子服务接口正常(session已共享)

4、子服务登陆之后,调用主服务理论也可以,需校验下主服务用户侧


http://www.niftyadmin.cn/n/5270807.html

相关文章

正点原子驱动开发BUG(一)--SPI无法正常通信

目录 一、问题描述二、讲该问题的解决方案三、imx6ull的spi适配器驱动程序控制片选分析3.1 设备icm20608的驱动程序分析3.2 imx的spi适配器的驱动程序分析 四、BUG修复测试五、其他问题 一、问题描述 使用正点的im6ull开发板进行spi通信驱动开发实验的时候&#xff0c;主机无法…

Golang 数组 移除元素 双指针法 leetcode27 小记

文章目录 移除元素 leetcode27暴力解法双指针法1. 快慢指针2. 双向指针 移除元素 leetcode27 go中数据类型的分类&#xff1a; 1.值类型&#xff1a;int、float、bool、string、数组、结构体 2.引用类型&#xff1a;指针、切片、map、管道、接口 由于切片为引用类型&#xff0c…

NGINX 使用及部署文档

1. 安装 NGINX 在 Ubuntu 上安装 NGINX sudo apt update sudo apt install nginx在 CentOS 上安装 NGINX sudo yum install epel-release sudo yum install nginx2. 启动 NGINX sudo systemctl start nginx3. 基本配置 配置文件位置 NGINX的主要配置文件&#xff1a;/etc…

Sherlock:社交媒体账号搜索工具 | 开源日报 No.111

sherlock-project/sherlock Stars: 45.1k License: MIT 这个项目是一个社交媒体账号搜索工具&#xff0c;名为 Sherlock。它可以通过用户名在不同的社交网络上追踪用户的账号。 支持单个或多个用户搜索提供 Docker 容器化部署方式开放源代码并欢迎贡献者参与开发包含详细测试…

C++计算(a+b)*(c-b)的值 2023年9月c++一级 电子学会中小学生软件编程C++等级考试一级真题答案解析

目录 C计算(ab)*(c-b)的值 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、程序说明 五、运行结果 六、考点分析 C计算(ab)*(c-b)的值 2023年9月 C编程等级考试一级编程题 一、题目要求 1、编程实现 给定3个整数a、b、c&#xff0c;计算表达…

6.鸿蒙app_hap_DevEco如何真机调试模式_app安装在手机中

真机调试 手机》设置》关于手机》HarmonyOS版本》软件版本&#xff0c;连续单击10次启动开发者模式 然后&#xff1a;设置》系统和更新》开发人员选项》打开USB调试功能。 电脑USB连接手机&#xff0c;手机USB连接类型&#xff0c;传文件&#xff08;不要选择仅充电&#xf…

04_Web框架之Django一

Web框架之Django一 学习目标和内容 1、能够描述Django的作用 2、能够使用Django创建应用 3、能够使用GET和POST请求方式进行传参 4、能够使用Django的函数式方法定义视图 5、能够进行Django的配置文件修改 6、能够基本使用Django的路由定义 一、Django相关介绍 1、什么是Djan…

Pytorch中的层和块(nn.Module和nn.Sequential的用法)

一、层和块 在构造自定义块之前&#xff0c;我们先回顾一下多层感知机的代码。下面的代码生成一个网络&#xff0c;其中包含一个具有256个单元和ReLU激活函数的全连接隐藏层&#xff0c;然后是一个具有10个隐藏单元且不带激活函数的全连接输出层。 import torch from torch im…