Spring cloud micro-service single sign-on based on redis cluster

Keywords: Java JSON Redis Session Database

yls
2019-9-23

brief introduction

This paper introduces how to implement single sign-on function in micro-service architecture.
Create three services:

  1. Services that operate redis clusters for sharing data among multiple services
  2. Unified Authentication Center Service for Unified Login Authentication of the Whole System
  3. 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";
    }

Posted by Rommeo on Wed, 02 Oct 2019 04:52:49 -0700