springcloud微服務(wù)基于redis集群的單點登錄實現(xiàn)解析
簡介
本文介紹微服務(wù)架構(gòu)中如何實現(xiàn)單點登錄功能
創(chuàng)建三個服務(wù):
- 操作redis集群的服務(wù),用于多個服務(wù)之間共享數(shù)據(jù)
- 統(tǒng)一認(rèn)證中心服務(wù),用于整個系統(tǒng)的統(tǒng)一登錄認(rèn)證
- 服務(wù)消費者,用于測試單點登錄
大體思路:每個服務(wù)都設(shè)置一個攔截器檢查cookie中是否有token,若有token,則放行,若沒有token,重定向到統(tǒng)一認(rèn)證中心服務(wù)進行登錄,登錄成功后返回到被攔截的服務(wù)。
搭建redis集群服務(wù)
搭建統(tǒng)一認(rèn)證中心
主函數(shù)添加注解
/**
* 單點登錄既要注冊到服務(wù)注冊中心,又要向redis服務(wù)系統(tǒng)獲取鼓舞
* 所以要添加 @EnableDiscoveryClient @EnableEurekaClient 兩個注解
*
*/
@EnableDiscoveryClient
@EnableEurekaClient
@EnableFeignClients
@MapperScan(basePackages = "com.example.itokenservicesso.mapper")
@SpringBootApplication
public class ItokenServiceSsoApplication {
public static void main(String[] args) {
SpringApplication.run(ItokenServiceSsoApplication.class, args);
}
}
消費redis服務(wù)和熔斷器
@FeignClient(value = "itoken-service-redis", fallback = RedisServiceFallBack.class)
public interface RedisService {
@PostMapping(value = "put")
public String put(@RequestParam(value = "key") String key, @RequestParam(value = "value") String value, @RequestParam(value = "seconds") long seconds);
@GetMapping(value = "get")
public String get(@RequestParam(value = "key") String key);
}
@Component
public class RedisServiceFallBack implements RedisService {
@Override
public String put(String key, String value, long seconds) {
return FallBack.badGateWay();
}
@Override
public String get(String key) {
return FallBack.badGateWay();
}
}
public class FallBack {
public static String badGateWay(){
try {
return JsonUtil.objectToString(ResultUtil.error(502,"內(nèi)部錯誤"));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
}
登錄服務(wù)
@Service
public class LoginServiceImpl implements LoginService {
@Autowired
private UserMapper userMapper;
@Autowired
private RedisService redisService;
@Override
public User login(String loginCode, String plantPassword) {
//從緩存中獲取登錄用戶的數(shù)據(jù)
String json = redisService.get(loginCode);
User user = null;
//如果緩存中沒有數(shù)據(jù),從數(shù)據(jù)庫取數(shù)據(jù)
if (json == null) {
user = userMapper.selectAll(loginCode);
String passwordMd5 = DigestUtils.md5DigestAsHex(plantPassword.getBytes());
if (user != null && passwordMd5.equals(user.getPassword())) {
//登錄成功,刷新緩存
try {
redisService.put(loginCode, JsonUtil.objectToString(user), 60 * 60 * 24);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return user;
} else {
return null;
}
}
//如果緩存中有數(shù)據(jù)
else {
try {
user = JsonUtil.stringToObject(json, User.class);
} catch (IOException e) {
e.printStackTrace();
}
}
return user;
}
}
contoller層,處理登錄業(yè)務(wù)和登錄跳轉(zhuǎn)
登錄業(yè)務(wù)
/**
* 登錄業(yè)務(wù)
*
* @param loginCode
* @param password
* @return
*/
@PostMapping("login")
public String login(String loginCode,
String password,
@RequestParam(required = false) String url,
HttpServletRequest request,
HttpServletResponse response,
RedirectAttributes redirectAttributes) {
User user = loginService.login(loginCode, password);
//登錄成功
if (user != null) {
String token = UUID.randomUUID().toString();
//將token放入緩存
String result = redisService.put(token, loginCode, 60 * 60 * 24);
//如果redisService沒有熔斷,也就是返回ok,才能執(zhí)行
if (result != null && result.equals("ok")) {
CookieUtil.setCookie(response, "token", token, 60 * 60 * 24);
if (url != null && !url.trim().equals(""))
return "redirect:" + url;
}
//熔斷后返回錯誤提示
else {
redirectAttributes.addFlashAttribute("message", "服務(wù)器異常");
}
}
//登錄失敗
else {
redirectAttributes.addFlashAttribute("message", "用戶名或密碼錯誤");
}
return "redirect:/login";
}
登錄跳轉(zhuǎn)
@Autowired
private LoginService loginService;
@Autowired
private RedisService redisService;
/**
* 跳轉(zhuǎn)登錄頁
*/
@GetMapping("login")
public String login(HttpServletRequest request,
Model model,
@RequestParam(required = false) String url
) {
String token = CookieUtil.getCookie(request, "token");
//token不為空可能已登錄,從redis獲取賬號
if (token != null && token.trim().length() != 0) {
String loginCode = redisService.get(token);
//如果賬號不為空,從redis獲取該賬號的個人信息
if (loginCode != null && loginCode.trim().length() != 0) {
String json = redisService.get(loginCode);
if (json != null && json.trim().length() != 0) {
try {
User user = JsonUtil.stringToObject(json, User.class);
//已登錄
if (user != null) {
if (url != null && url.trim().length() != 0) {
return "redirect:" + url;
}
}
//將登錄信息傳到登錄頁
model.addAttribute("user", user);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
return "login";
}
搭建服務(wù)消費者:添加一個攔截器,判斷token是否為空
攔截器
public class WebAdminInterceptor implements HandlerInterceptor {
@Autowired
private RedisService redisService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = CookieUtil.getCookie(request, "token");
//token為空,一定沒有登錄
if (token == null || token.isEmpty()) {
response.sendRedirect("http://localhost:8503/login?url=http://localhost:8601/login");
return false;
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HttpSession session = request.getSession();
User user = (User) session.getAttribute("user");
//已登陸狀態(tài)
if (user != null) {
if (modelAndView != null) {
modelAndView.addObject("user", user);
}
}
//未登錄狀態(tài)
else {
String token = CookieUtil.getCookie(request, "token");
if (token != null && !token.isEmpty()) {
String loginCode = redisService.get(token);
if (loginCode != null && !loginCode.isEmpty()) {
String json = redisService.get(loginCode);
if (json != null && !json.isEmpty()) {
//已登錄狀態(tài),創(chuàng)建局部會話
user = JsonUtil.stringToObject(json, User.class);
if (modelAndView != null) {
modelAndView.addObject("user", user);
}
request.getSession().setAttribute("user", user);
}
}
}
}
//二次確認(rèn)是否有用戶信息
if (user == null) {
response.sendRedirect("http://localhost:8503/login?url=http://localhost:8601/login");
}
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
配置攔截器
@Configuration
public class WebAdminInterceptorConfig implements WebMvcConfigurer {
//將攔截器設(shè)置為Bean,在攔截其中才能使用@AutoWired注解自動注入
@Bean
WebAdminInterceptor webAdminInterceptor() {
return new WebAdminInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(webAdminInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/static");
}
}
任意寫一個接口,觸發(fā)攔截器進行測試
@RequestMapping(value = {"/login"})
public String index(){
return "index";
}
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Java NIO.2 使用Path接口來監(jiān)聽文件、文件夾變化
Java7對NIO進行了大的改進,新增了許多功能,接下來通過本文給大家介紹Java NIO.2 使用Path接口來監(jiān)聽文件、文件夾變化 ,需要的朋友可以參考下2019-05-05
解決mysql字符串類型的數(shù)字排序出錯:cast(year as signed)
這篇文章主要介紹了解決mysql字符串類型的數(shù)字排序出錯問題 :cast(year as signed),如有錯誤或未考慮完全的地方,望不吝賜教2023-08-08
Java中spring boot validation自定義注解使用方式
這篇文章主要介紹了Java中spring boot validation自定義注解使用方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-08-08

