yls
2019-9-23
brief introduction
This paper introduces how to implement single sign-on function in micro-service architecture.
Create three services:
- Services that operate redis clusters for sharing data among multiple services
- Unified Authentication Center Service for Unified Login Authentication of the Whole System
- Service consumers for testing single sign-on
General idea: Each service sets up an interceptor to check whether there is token in cookie. If there is token, it will be released. If there is no token, it will be redirected to the Unified Authentication Center service for login. After successful login, it will return to the intercepted service.
Building redis cluster service
Building a redis cluster reference document
Building a Unified Certification Center
- Adding annotations to main functions
/** * Single sign-on not only registers with the service registry, but also gets inspiration from redis service system. * So add two annotations @Enable Discovery Client @Enable EurekaClient * */ @EnableDiscoveryClient @EnableEurekaClient @EnableFeignClients @MapperScan(basePackages = "com.example.itokenservicesso.mapper") @SpringBootApplication public class ItokenServiceSsoApplication { public static void main(String[] args) { SpringApplication.run(ItokenServiceSsoApplication.class, args); } }
- Consumer redis services and fuses
@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,"internal error")); } catch (JsonProcessingException e) { e.printStackTrace(); } return null; } }
- Login service
@Service public class LoginServiceImpl implements LoginService { @Autowired private UserMapper userMapper; @Autowired private RedisService redisService; @Override public User login(String loginCode, String plantPassword) { //Getting logged-in user data from the cache String json = redisService.get(loginCode); User user = null; //If there is no data in the cache, fetch data from the database if (json == null) { user = userMapper.selectAll(loginCode); String passwordMd5 = DigestUtils.md5DigestAsHex(plantPassword.getBytes()); if (user != null && passwordMd5.equals(user.getPassword())) { //Log in successfully, refresh cache try { redisService.put(loginCode, JsonUtil.objectToString(user), 60 * 60 * 24); } catch (JsonProcessingException e) { e.printStackTrace(); } return user; } else { return null; } } //If there is data in the cache else { try { user = JsonUtil.stringToObject(json, User.class); } catch (IOException e) { e.printStackTrace(); } } return user; } }
-
contoller layer, handling login business and login jump
-
Login service
/** * Login service * * @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); //Login successfully if (user != null) { String token = UUID.randomUUID().toString(); //Put token in the cache String result = redisService.put(token, loginCode, 60 * 60 * 24); //If redisService does not fuse, it returns ok to execute. if (result != null && result.equals("ok")) { CookieUtil.setCookie(response, "token", token, 60 * 60 * 24); if (url != null && !url.trim().equals("")) return "redirect:" + url; } //Return error message after fuse else { redirectAttributes.addFlashAttribute("message", "Server exception"); } } //Login failed else { redirectAttributes.addFlashAttribute("message", "Error in username or password"); } return "redirect:/login"; }
Login jump
@Autowired private LoginService loginService; @Autowired private RedisService redisService; /** * Jump to the login page */ @GetMapping("login") public String login(HttpServletRequest request, Model model, @RequestParam(required = false) String url ) { String token = CookieUtil.getCookie(request, "token"); //token may be logged in if it is not empty and get an account from redis if (token != null && token.trim().length() != 0) { String loginCode = redisService.get(token); //If the account is not empty, get personal information from 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); //Already logged in if (user != null) { if (url != null && url.trim().length() != 0) { return "redirect:" + url; } } //Pass login information to login page model.addAttribute("user", user); } catch (IOException e) { e.printStackTrace(); } } } } return "login"; }
-
-
Build a service consumer: Add an interceptor to determine whether token is empty
- Interceptor
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 is empty and must not be logged in 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"); //Landed status if (user != null) { if (modelAndView != null) { modelAndView.addObject("user", user); } } //Unlogged status 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()) { //Logged in to create a local session user = JsonUtil.stringToObject(json, User.class); if (modelAndView != null) { modelAndView.addObject("user", user); } request.getSession().setAttribute("user", user); } } } } //Secondary confirmation of user information 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 { } }
- Configuring Interceptors
@Configuration public class WebAdminInterceptorConfig implements WebMvcConfigurer { //Set the interceptor to Bean and use the @AutoWired annotation to inject automatically in the interceptor @Bean WebAdminInterceptor webAdminInterceptor() { return new WebAdminInterceptor(); } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(webAdminInterceptor()) .addPathPatterns("/**") .excludePathPatterns("/static"); } }
Write any interface and trigger the interceptor to test
@RequestMapping(value = {"/login"}) public String index(){ return "index"; }