1, an overview of the

Single sign-on (SSO) SSO allows users to log in once to access all trusted applications in multiple applications. For example, Tmall and Taobao, have entered the login page, all require you to log in, now you log in taobao, directly in the Tmall refresh, you will find that you have logged in.

2. Sso implementation schematic diagram

1. Schematic diagram of this Demo

2. Schematic Diagram 2

3. Problems encountered

1. Same-origin policy

The same origin policy, which is a famous security policy proposed by Netscape. This strategy is now used by all browsers that support JavaScript. Same name means same domain name, same protocol, same port. When a script is executed on the Baidu TAB page of the browser, the system checks which page the script belongs to, that is, check whether the script is of the same origin. Only the script with the same origin as Baidu is executed. If it is non-same-origin, the browser raises an exception in the console to deny access when the data is requested. The same origin policy is a behavior of the browser to protect local data from being contaminated by data retrieved by JavaScript code. Therefore, the same origin policy intercepts data received from the client. That is, the request is sent and the server responds, but the browser cannot receive the request.

2, the cookie domain

The domain of cookies (usually the domain name of a website) that the browser automatically carries with it when sending an HTTP request, instead of all cookies, matching the domain.

The solution

  • 1. Use nginx reverse proxy to source all services from the same source

  • Create session for all services (resource waste)

  • 3. Cross-domain cookie redirection parameter synchronization

    Cookie is first placed in a domain. When the login request accesses this domain and obtains the parameters in this domain, the parameter is redirected to the original system domain for reference

    【 Demo schematic diagram 】

3. Cross-domain requests

Access to resources that are not in the same domain is denied due to browser security restrictions

Cross-domain request solution

Springboot allows cross-domain requests

4. After the redis value is written to the redisTemplate to set the expiration time, the data obtained will get control characters and cannot be converted into Bean objects

The solution

Replacement control character

replaceAll("[\\x00-\\x09\\x11\\x12\\x14-\\x1F\\x7F]"."");
Copy the code

Refer to the link

RedisTemplate failed to serialize using the default jdkSerializeable serializer

The solution

Configure redisTemplate using the StringRedisSerializer serializer

@Configuration
public class RedisConfig {
    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Bean
    public RedisTemplate<String,String> redisTemplate(){
        RedisTemplate<String,String> redisTemplate = new RedisTemplate<>();
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        returnredisTemplate; }}Copy the code

The @AutoWired injection bean is not available in spring interceptors

The solution

The interceptor is first hosted by Spring when it is initialized, and the bean is injected into the interceptor

*/ @configuration Public class InterceptorConfig implements WebMvcConfigurer {/** *return
     */
    @Bean
    WebInterceptor WebInterceptor() {return new WebInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(WebInterceptor()).addPathPatterns("/ * *"); }}Copy the code

4. Implementation process

1. Create spring Boot integrated redisTemplate to provide redis service

1. Modify POM to increase dependency

Pom mainly increases dependencies

   <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
Copy the code

Cloud service unified dependency

<! -- Spring Cloud eureka Begin --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId> </dependency> <! -- zipkin begin--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zipkin</artifactId> </dependency> <! -- Spring Cloud eureka End --> <! -- config begin--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> <! -- admin begin--> <dependency> <groupId>org.jolokia</groupId> <artifactId>jolokia-core</artifactId> </dependency> <dependency> <groupId>de.codecentric</groupId> <artifactId>spring-boot-admin-starter-client</artifactId> <version>${spring-cloud-admin.version}</version> </dependency> <! --feign Begin--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <! --feign End--> <! - config does not get configuration automatically retry the begin -- > < the dependency > < groupId > org. Springframework. Retry < / groupId > <artifactId>spring-retry</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <! End --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>${fastjson.version}</version>
    </dependency>
Copy the code

2. Application. yml Configure redis parameters

# redis configuration
spring:
  redis:
    host: xxx.xxx.xxx.xxx
    port: 6379
    lettuce:
      pool:
        max-active: 8
        max-idle: 8
        max-wait: -1ms
        min-idle: 0
Copy the code

3. Modify the redisTemplate default serializer

The demo failed to serialize the default jdkSerializeable serializer, so the serializer was changed

StringRedisSerializer is converted only between byte and String

@Configuration
public class RedisConfig {
    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Bean
    public RedisTemplate<String,String> redisTemplate(){
        RedisTemplate<String,String> redisTemplate = new RedisTemplate<>();
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        returnredisTemplate; }}Copy the code

4. Create redis Restfull service

@RestController public class RedisController { @Autowired private RedisTemplate redisTemplate; /** * The value is @param key * @return
     */
    @RequestMapping(value = "get")
    public String get(String key){
        String value;
        try {
            value = (String) redisTemplate.opsForValue().get(key);
            if(stringutils.isnotBlank (value)){// Replace control character value = value.replaceAll(stringutils.isnotBlank (value))"[\\x00-\\x09\\x11\\x12\\x14-\\x1F\\x7F]"."");
            }
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
        returnvalue; } /** * write value * @param key * @param value * @param seconds * @return
     */
    @RequestMapping(value = "put")
    public String put(String key,String value,@RequestParam(required = false) Long seconds){
        try {
            if (seconds == null){
                redisTemplate.opsForValue().set(key,value);
            }else {
                redisTemplate.opsForValue().set(key,value,seconds);
            }
        }catch (Exception e){
            e.printStackTrace();
            return "ERROR";
        }
        return "OK"; }}Copy the code

2. Create an SSO unified authentication center

1. Implementation of login method

Since the VUE and authentication authority are no longer in the same domain, the cookie cannot be shared, so the token is returned directly in the parameter

/** * log in ** @param sysUser * @return
     */
    @RequestMapping(value = "login")
    public Map<String, Object> login(@RequestBody SysUser sysUser, HttpServletRequest request, HttpServletResponse response) {
        Map<String, Object> resultMap = new HashMap<>();
        try {
            if(sysUser ! = null && StringUtils.isNotBlank(sysUser.getUserName()) && StringUtils.isNotBlank(sysUser.getPassword())) { SysUser result = sysUserService.getUserByLoginName(sysUser); // Login succeededif(result ! = null && StringUtils.isNotBlank(result.getPassword()) && sysUser.getPassword().equals(result.getPassword())) { Redis String token = uid.randomuuid ().toString(); String userJson = JSON.toJSONString(result); String flag = loginService.redisPut(token, userJson, 60*60*2L);if("ERROR".equals(flag)){
                        throw new RuntimeException("Redis call exception 1");
                    }
                    resultMap.put("code"."1");
                    resultMap.put("data", result); // Return token resultmap. put("token",token);
                } else {
                    resultMap.put("code"."1");
                    resultMap.put("message"."User or password error"); }}else {
                resultMap.put("code"."99");
                resultMap.put("message"."Parameter error");
            }
            return resultMap;
        } catch (Exception e) {
            e.printStackTrace();
            resultMap.clear();
            resultMap.put("code"."999");
            resultMap.put("message"."System error retry later");
            returnresultMap; }}Copy the code

3. Vue key codes

Vue global method

// setCookie vue.prototype. setCookie =function(c_name,value,expiredays) {
  var exdate=new Date()
  exdate.setDate(exdate.getDate()+expiredays)
  document.cookie=c_name+ "=" +escape(value)+
    ((expiredays==null) ? "" : "; expires="+exdate.toGMTString()) }; // getCookie vue.prototype.getcookie =function(c_name) {
  if (document.cookie.length>0)
  {
    var  c_start=document.cookie.indexOf(c_name + "=")
    if(c_start! =-1) { c_start=c_start + c_name.length+1 var c_end=document.cookie.indexOf(";",c_start)
      if (c_end==-1) c_end=document.cookie.length
      return unescape(document.cookie.substring(c_start,c_end))
    }
  }
  return ""}; // Get the url parameter vue.prototype. getUrlKey=function(name) {
    return decodeURIComponent((new RegExp('[? | &]' + name + '=' + '([^ &;] +? (& # | |; | $) ').exec(location.href) || [, ""])[1].replace(/\+/g, '% 20')) || null
};
Copy the code

Writes the token returned by the authentication authority to the cookie

// Write token to cookie this.setcookie ("token",repos.data.token);
Copy the code

Vue key core code

<template>
    
</template>

<script>
    export default {
      name: "SsoIndex", // The hook function is used to synchronize cookies between different fields.function () {
        let token = this.getCookie("token");
        let url = this.getUrlKey("redirect"); // If there is a token, it responds directly to the backgroundif(token){
          location.href = url+"? token="+token; } // Otherwise the return does not existelse{
          location.href = url+"? token=not";
        }

      }
    }
</script>

<style scoped>

</style>

Copy the code

4, System A, system B interceptor code

Config initializes the interceptor code

*/ @configuration Public class InterceptorConfig implements WebMvcConfigurer {/** *return
     */
    @Bean
    WebInterceptor WebInterceptor() {return new WebInterceptor();
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(WebInterceptor()).addPathPatterns("/ * *"); }}Copy the code

Interceptor code

*/ @component public class WebInterceptor implements HandlerInterceptor {@autoWired private RedisService  redisService; * @param request * @param response * @param handler * @return
     * @throws IOException
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
        SysUser sysUser = (SysUser) request.getSession().getAttribute("loginUser"); // No local session exists in the subsystem. Attempts to obtain session information of the unified authentication centerif(sysUser == null){
            String token = request.getParameter("token"); // If no token is available, go to the unified authentication page to obtain itif(StringUtils.isBlank(token)){
                response.sendRedirect("http://localhost:8080/ssoIndex? redirect="+request.getRequestURL());
                return false; } // If the token is not, sso login is not performedelse if("not".equals(token)){
                response.sendRedirect("http://localhost:8080/login? redirect="+request.getRequestURL());
                return false; } // Obtain redis login data based on token String json = redisservice. redisGet(token); // The token is logged inif(StringUtils.isNotBlank(json)){ try { SysUser user = MapperUtils.json2pojo(json,SysUser.class); Request.getsession ().setattribute ()"loginUser",user); } catch (Exception e) { e.printStackTrace(); SysUser = (sysUser) request.getSession().getAttribute()"loginUser"); // No local session indicates sso re-authenticationif(sysUser == null){
                response.sendRedirect("http://localhost:8080/login? redirect="+request.getRequestURL());
                return false; }}return true; }}Copy the code

5, achieve the effect

1. System 1

2. System 2

The login succeeds because system 1 has logged in