微服務Redis-Session共享登錄狀態(tài)的過程詳解
一、背景
隨著項目越來越大,需要將多個服務拆分成微服務,使代碼看起來不要過于臃腫,龐大。微服務之間通常采取feign交互,為了保證不同微服務之間增加授權校驗,需要增加Spring Security登錄驗證,為了多個服務之間session可以共享,可以通過數(shù)據(jù)庫實現(xiàn)session共享,也可以采用redis-session實現(xiàn)共享。
本文采取Spring security做登錄校驗,用redis做session共享。實現(xiàn)單服務登錄可靠性,微服務之間調(diào)用的可靠性與通用性
二、代碼
本文項目采取 主服務一服務、子服務二 來舉例
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
#連接池最大連接數(shù)(使用負值表示沒有限制)
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
#數(shù)據(jù)庫
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
#連接池最大連接數(shù)(使用負值表示沒有限制)
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
#數(shù)據(jù)庫
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(); //加密過程在這里體現(xiàn) TaobaoUser user= userService.getUserData(username); //校驗,用戶名是否存在 if(user==null){ throw new DisabledException("用戶名或密碼錯誤"); } //校驗登錄狀態(tài) checkPassword() Collection<GrantedAuthority> authorities = new ArrayList<>(); return new UsernamePasswordAuthenticationToken(userCurrent, credential, authorities); } catch (Exception ex) { ex.printStackTrace(); throw new DisabledException("登錄發(fā)生錯誤 : " + 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為{}-通過用戶{}調(diào)用接口",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("登錄發(fā)生錯誤 : " + 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 調(diào)用,添加CurrentUser */ private class FeignBasicAuthRequestInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate template) { //1、使用RequestContextHolder拿到剛進來的請求數(shù)據(jù) 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配置用戶密碼實現(xiàn)登錄)
3、主服務通過feign調(diào)用子服務接口正常(session已共享)
4、子服務登陸之后,調(diào)用主服務理論也可以,需校驗下主服務用戶側(cè)
到此這篇關于微服務Redis-Session共享登錄狀態(tài)的文章就介紹到這了,更多相關Redis Session共享登錄狀態(tài)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
mybatis取別名typeAliases標簽的位置放錯導致報錯的解決
這篇文章主要介紹了mybatis取別名typeAliases標簽的位置放錯導致報錯的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09SSM框架+Plupload實現(xiàn)分塊上傳大文件示例
這篇文章主要介紹了SSM框架+Plupload實現(xiàn)分塊上傳示例(Spring+SpringMVC+MyBatis+Plupload),將用戶選中的文件(可多個)分隔成一個個小塊,依次向服務器上傳,有興趣的可以了解一下。2017-03-03MyBatis?Generator?ORM層面的代碼自動生成器(推薦)
Mybatis?Generator是一個專門為?MyBatis和?ibatis框架使用者提供的代碼生成器,也可以快速的根據(jù)數(shù)據(jù)表生成對應的pojo類、Mapper接口、Mapper文件,甚至生成QBC風格的查詢對象,這篇文章主要介紹了MyBatis?Generator?ORM層面的代碼自動生成器,需要的朋友可以參考下2023-01-01向Spring IOC 容器動態(tài)注冊bean實現(xiàn)方式
這篇文章主要為大家介紹了向Spring IOC 容器動態(tài)注冊bean實現(xiàn)方式詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-07-07SparkSQL讀取hive數(shù)據(jù)本地idea運行的方法詳解
這篇文章主要介紹了SparkSQL讀取hive數(shù)據(jù)本地idea運行的方法,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-09-09Struts2源碼分析之ParametersInterceptor攔截器
這篇文章主要介紹了Struts2源碼分析之ParametersInterceptor攔截器,ParametersInterceptor攔截器其主要功能是把ActionContext中的請求參數(shù)設置到ValueStack中,,需要的朋友可以參考下2019-06-06spring使用Filter過濾器對Response返回值進行修改的方法
這篇文章主要介紹了spring使用Filter過濾器對Response返回值進行修改,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-09-09