微服務(wù)Redis-Session共享登錄狀態(tài)的過程詳解
一、背景
隨著項(xiàng)目越來越大,需要將多個(gè)服務(wù)拆分成微服務(wù),使代碼看起來不要過于臃腫,龐大。微服務(wù)之間通常采取feign交互,為了保證不同微服務(wù)之間增加授權(quán)校驗(yàn),需要增加Spring Security登錄驗(yàn)證,為了多個(gè)服務(wù)之間session可以共享,可以通過數(shù)據(jù)庫實(shí)現(xiàn)session共享,也可以采用redis-session實(shí)現(xiàn)共享。
本文采取Spring security做登錄校驗(yàn),用redis做session共享。實(shí)現(xiàn)單服務(wù)登錄可靠性,微服務(wù)之間調(diào)用的可靠性與通用性
二、代碼
本文項(xiàng)目采取 主服務(wù)一服務(wù)、子服務(wù)二 來舉例
1、服務(wù)依賴文件
主服務(wù)依賴
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'子服務(wù)依賴
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、服務(wù)配置文件
主服務(wù)配置文件
#redis連接
spring.redis.host=1.2.3.4
#Redis服務(wù)器連接端口
spring.redis.port=6379
#Redis服務(wù)器連接密碼
spring.redis.password=password
#連接池最大連接數(shù)(使用負(fù)值表示沒有限制)
spring.redis.pool.max-active=8
#連接池最大阻塞等待時(shí)間(使用負(fù)值表示沒有限制)
spring.redis.pool.max-wait=-1
#連接池中的最大空閑連接
spring.redis.pool.max-idle=8
#連接池中的最小空閑連接
spring.redis.pool.min-idle=0
#連接超時(shí)時(shí)間(毫秒)
spring.redis.timeout=30000
#數(shù)據(jù)庫
spring.redis.database=0
#redis-session配置
spring.session.store-type=redis
#部分post請求過長會報(bào)錯(cuò),需加配置
server.tomcat.max-http-header-size=65536
子服務(wù)配置文件
#單獨(dú)登錄秘鑰
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服務(wù)器連接端口
spring.redis.port=6379
#Redis服務(wù)器連接密碼
spring.redis.password=password
#連接池最大連接數(shù)(使用負(fù)值表示沒有限制)
spring.redis.pool.max-active=8
#連接池最大阻塞等待時(shí)間(使用負(fù)值表示沒有限制)
spring.redis.pool.max-wait=-1
#連接池中的最大空閑連接
spring.redis.pool.max-idle=8
#連接池中的最小空閑連接
spring.redis.pool.min-idle=0
#連接超時(shí)時(shí)間(毫秒)
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、登錄校驗(yàn)文件
主服務(wù)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);
/**
* 自定義驗(yàn)證方式
*/
@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);
//校驗(yàn),用戶名是否存在
if(user==null){
throw new DisabledException("用戶名或密碼錯(cuò)誤");
}
//校驗(yàn)登錄狀態(tài)
checkPassword()
Collection<GrantedAuthority> authorities = new ArrayList<>();
return new UsernamePasswordAuthenticationToken(userCurrent, credential, authorities);
} catch (Exception ex) {
ex.printStackTrace();
throw new DisabledException("登錄發(fā)生錯(cuò)誤 : " + ex.getMessage());
}
}
@Override
public boolean supports(Class<?> arg0) {
return true;
}子服務(wù)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;
/**
* 自定義驗(yàn)證方式
*/
@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("無權(quán)登陸!");
}
}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ā)生錯(cuò)誤 : " + ex.getMessage());
}
}
@Override
public boolean supports(Class<?> arg0) {
return true;
}
}4、主服務(wù)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拿到剛進(jìn)來的請求數(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、主服務(wù)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、直接訪問主服務(wù)接口,不登錄無法訪問
2、直接訪問自服務(wù),不登錄無法訪問,(可通過nacos配置用戶密碼實(shí)現(xiàn)登錄)
3、主服務(wù)通過feign調(diào)用子服務(wù)接口正常(session已共享)
4、子服務(wù)登陸之后,調(diào)用主服務(wù)理論也可以,需校驗(yàn)下主服務(wù)用戶側(cè)
到此這篇關(guān)于微服務(wù)Redis-Session共享登錄狀態(tài)的文章就介紹到這了,更多相關(guān)Redis Session共享登錄狀態(tài)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
mybatis取別名typeAliases標(biāo)簽的位置放錯(cuò)導(dǎo)致報(bào)錯(cuò)的解決
這篇文章主要介紹了mybatis取別名typeAliases標(biāo)簽的位置放錯(cuò)導(dǎo)致報(bào)錯(cuò)的解決方案,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09
SSM框架+Plupload實(shí)現(xiàn)分塊上傳大文件示例
這篇文章主要介紹了SSM框架+Plupload實(shí)現(xiàn)分塊上傳示例(Spring+SpringMVC+MyBatis+Plupload),將用戶選中的文件(可多個(gè))分隔成一個(gè)個(gè)小塊,依次向服務(wù)器上傳,有興趣的可以了解一下。2017-03-03
MyBatis?Generator?ORM層面的代碼自動(dòng)生成器(推薦)
Mybatis?Generator是一個(gè)專門為?MyBatis和?ibatis框架使用者提供的代碼生成器,也可以快速的根據(jù)數(shù)據(jù)表生成對應(yīng)的pojo類、Mapper接口、Mapper文件,甚至生成QBC風(fēng)格的查詢對象,這篇文章主要介紹了MyBatis?Generator?ORM層面的代碼自動(dòng)生成器,需要的朋友可以參考下2023-01-01
向Spring IOC 容器動(dòng)態(tài)注冊bean實(shí)現(xiàn)方式
這篇文章主要為大家介紹了向Spring IOC 容器動(dòng)態(tài)注冊bean實(shí)現(xiàn)方式詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07
SparkSQL讀取hive數(shù)據(jù)本地idea運(yùn)行的方法詳解
這篇文章主要介紹了SparkSQL讀取hive數(shù)據(jù)本地idea運(yùn)行的方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09
Struts2源碼分析之ParametersInterceptor攔截器
這篇文章主要介紹了Struts2源碼分析之ParametersInterceptor攔截器,ParametersInterceptor攔截器其主要功能是把ActionContext中的請求參數(shù)設(shè)置到ValueStack中,,需要的朋友可以參考下2019-06-06
spring使用Filter過濾器對Response返回值進(jìn)行修改的方法
這篇文章主要介紹了spring使用Filter過濾器對Response返回值進(jìn)行修改,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-09-09

