开发者

微服务Redis-Session共享登录状态的过程详解

目录
  • 一、背景
  • 二、代码
    • 1、服务依赖文件
    • 2、服务配置文件
    • 3、登录校验文件
    • 4、主服务feign配置
    • 5、主服务session文件
  • 三、完成配置后

    一、背景

            随着项目越来越大,需要将多个服务拆分成微服务,使代码看起来不要过于臃肿,庞大。微服务之间通常采取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

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

    spri编程客栈ng.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.getCredentipythonals();
                //加密过程在这里体现
                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(","));
                     xUPxmR 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);
     php                       }
                        }
                    }
                }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、子服务登陆之后,调用主服务理论也可以,需校验下主服务用户侧

    到此这篇关于微服务Redis-Session共享登录状态的文章就介绍到这了,更多相关Redis Session共享登录状态内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!

    0

    上一篇:

    下一篇:

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    最新开发

    开发排行榜