跳转到主内容
极星编程网:以代码为星,赴技术山海!

微服务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#连接池中的最大空闲连接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 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 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 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 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.javapublic 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 authorities = new ArrayList<>();session.setAttribute("TaobaoUser",new CurrentUser(userName, "", authorities));}}

三、完成配置后

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

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

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

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

到此这篇关于微服务Redis-Session共享登录状态的文章就介绍到这了,更多相关Redis Session共享登录状态内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:微服务Redis-Session共享登录状态的过程详解Redis的共享session应用实现短信登录Redis实现Session共享与单点登录SpringBoot+SpringSession+Redis实现session共享及唯一登录示例基于SpringBoot+Redis的Session共享与单点登录详解

相关文章