springcloud微服務基於redis集群的單點登錄
yls
2019-9-23
簡介
本文介紹微服務架構中如何實現單點登錄功能
創建三個服務:
- 操作redis集群的服務,用於多個服務之間共享數據
- 統一認證中心服務,用於整個系統的統一登錄認證
- 服務消費者,用於測試單點登錄
大體思路:每個服務都設置一個攔截器檢查cookie中是否有token,若有token,則放行,若沒有token,重定向到統一認證中心服務進行登錄,登錄成功后返回到被攔截的服務。
搭建redis集群服務
搭建統一認證中心
- 主函數添加注解
/**
* 單點登錄既要注冊到服務注冊中心,又要向redis服務系統獲取鼓舞
* 所以要添加 @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服務和熔斷器
@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,"內部錯誤"));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
}
- 登錄服務
@Service
public class LoginServiceImpl implements LoginService {
@Autowired
private UserMapper userMapper;
@Autowired
private RedisService redisService;
@Override
public User login(String loginCode, String plantPassword) {
//從緩存中獲取登錄用戶的數據
String json = redisService.get(loginCode);
User user = null;
//如果緩存中沒有數據,從數據庫取數據
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;
}
}
//如果緩存中有數據
else {
try {
user = JsonUtil.stringToObject(json, User.class);
} catch (IOException e) {
e.printStackTrace();
}
}
return user;
}
}
-
contoller層,處理登錄業務和登錄跳轉
-
登錄業務
/** * 登錄業務 * * @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,才能執行 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", "服務器異常"); } } //登錄失敗 else { redirectAttributes.addFlashAttribute("message", "用戶名或密碼錯誤"); } return "redirect:/login"; }
-
登錄跳轉
@Autowired private LoginService loginService; @Autowired private RedisService redisService; /** * 跳轉登錄頁 */ @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"; }
-
-
搭建服務消費者:添加一個攔截器,判斷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"); //已登陸狀態 if (user != null) { if (modelAndView != null) { modelAndView.addObject("user", user); } } //未登錄狀態 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()) { //已登錄狀態,創建局部會話 user = JsonUtil.stringToObject(json, User.class); if (modelAndView != null) { modelAndView.addObject("user", user); } request.getSession().setAttribute("user", user); } } } } //二次確認是否有用戶信息 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 { //將攔截器設置為Bean,在攔截其中才能使用@AutoWired注解自動注入 @Bean WebAdminInterceptor webAdminInterceptor() { return new WebAdminInterceptor(); } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(webAdminInterceptor()) .addPathPatterns("/**") .excludePathPatterns("/static"); } }
-
任意寫一個接口,觸發攔截器進行測試
@RequestMapping(value = {"/login"})
public String index(){
return "index";
}