Grain mall -- Certification Center -- advanced notes 8

Posted by uramagget on Mon, 13 Dec 2021 02:38:29 +0100

Cereals Mall - Certification Center - Advanced Chapter 8 notes

1. Environmental construction

1.1 create a new module gulimall auth server

1.2 pom documents

If you don't choose to directly copy the pom file below, remember to exclude the mybatis plus of the gulimall common package

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
		 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.3.2.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.atguigu.gulimall</groupId>
	<artifactId>gulimall-auth-server</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>gulimall-auth-server</name>
	<description>Certification Center (SOCIAL login OAuth2.0,Single sign on)</description>

	<properties>
		<java.version>1.8</java.version>
		<spring-cloud.version>Hoxton.SR6</spring-cloud.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>com.zhourui.gulimall</groupId>
			<artifactId>gulimall-common</artifactId>
			<version>0.0.1-SNAPSHOT</version>
			<exclusions>
                <!--Database operation removal is not required mybatis-plus,Prevent error reporting-->
				<exclusion>
					<groupId>com.baomidou</groupId>
					<artifactId>mybatis-plus-boot-starter</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-openfeign</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintage</groupId>
					<artifactId>junit-vintage-engine</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
	</dependencies>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>${spring-cloud.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

1.3 modify main class

//It can be called remotely so that the service can be discovered by nacos
@EnableFeignClients
@EnableDiscoveryClient

1.4 configure service name, port and registry address

spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
  application:
    name: gulimall-auth-server
server:
  port: 20000

1.5 new domain name mapping for host

192.168.157.128 auth.gulimall.com

1.6 static resource upload

1.7 modify reg html login. HTML address

Just correspond to the static address on nginx

1.8 configure gateway

gulimall-gateway/src/main/resources/application.yml

        #Certification services
        - id: gulimall_auth_host
          uri: lb://gulimall-auth-server
          predicates:
            - Host=auth.gulimall.com

1.9 new view mapping

Previously, adding a request in the controller can be done without any processing and only returning the corresponding view, but this time, there are empty methods in the controller, such as:

@GetMapping(value = "/login.html")
    public String loginPage(HttpSession session) {
		return "login";
   }

gulimall-auth-server/src/main/java/site/zhourui/gulimall/auth/config/GulimallWebConfig.java

package site.zhourui.gulimall.auth.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author zr
 * @date 2021/11/29 16:33
 */
@Configuration
public class GulimallWebConfig implements WebMvcConfigurer {

    /**
     * View mapping: Send a request and jump directly to a page
     */
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {

         registry.addViewController("/login.html").setViewName("login");
        registry.addViewController("/reg.html").setViewName("reg");
    }
}

1.10 testing

http://auth.gulimall.com/login.html

http://auth.gulimall.com/reg.html

1.11 front end code of this part

Verification code countdown

login.html :https://gitee.com/zhourui815/gulimall/blob/master/gulimall-auth-server/src/main/resources/templates/login.html

reg.html: https://gitee.com/zhourui815/gulimall/blob/master/gulimall-auth-server/src/main/resources/templates/reg.html

2. Integrate SMS verification code

2.1 purchase Alibaba cloud SMS service

Purchase address:

https://market.aliyun.com/products/57126001/cmapi024822.html?spm=5176.21213303.J_6704733920.11.49fb3edaM8bteY&scm=20140722.S_market%40%40API%E5%B8%82%E5%9C%BA%40%40cmapi024822..ID_market%40%40API%E5%B8%82%E5%9C%BA%40%40cmapi024822-RL%E4%B8%89%E5%90%88%E4%B8%80%E7%9F%AD%E4%BF%A1-OR_main-V_2-P0_2#sku=yuncode18822000012

2.2 postMan test

2.3 Integrated SMS service

2.3. 1. All dependencies required for importing HttpUtils

        <!--HttpUtils All dependencies required-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.15</version>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.2.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpcore</artifactId>
            <version>4.2.1</version>
        </dependency>
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>
        <dependency>
            <groupId>org.eclipse.jetty</groupId>
            <artifactId>jetty-util</artifactId>
            <version>9.3.7.v20160115</version>
        </dependency>

2.3. 2. Add HttpUtils

gulimall-common/src/main/java/site/zhourui/common/utils/HttpUtils.java

package site.zhourui.common.utils;

import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class HttpUtils {

    /**
     * get
     *
     * @param host
     * @param path
     * @param method
     * @param headers
     * @param querys
     * @return
     * @throws Exception
     */
    public static HttpResponse doGet(String host, String path, String method,
                                     Map<String, String> headers,
                                     Map<String, String> querys)
            throws Exception {
        HttpClient httpClient = wrapClient(host);

        HttpGet request = new HttpGet(buildUrl(host, path, querys));
        for (Map.Entry<String, String> e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue());
        }

        return httpClient.execute(request);
    }

    /**
     * post form
     *
     * @param host
     * @param path
     * @param method
     * @param headers
     * @param querys
     * @param bodys
     * @return
     * @throws Exception
     */
    public static HttpResponse doPost(String host, String path, String method,
                                      Map<String, String> headers,
                                      Map<String, String> querys,
                                      Map<String, String> bodys)
            throws Exception {
        HttpClient httpClient = wrapClient(host);

        HttpPost request = new HttpPost(buildUrl(host, path, querys));
        for (Map.Entry<String, String> e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue());
        }

        if (bodys != null) {
            List<NameValuePair> nameValuePairList = new ArrayList<NameValuePair>();

            for (String key : bodys.keySet()) {
                nameValuePairList.add(new BasicNameValuePair(key, bodys.get(key)));
            }
            UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(nameValuePairList, "utf-8");
            formEntity.setContentType("application/x-www-form-urlencoded; charset=UTF-8");
            request.setEntity(formEntity);
        }

        return httpClient.execute(request);
    }

    /**
     * Post String
     *
     * @param host
     * @param path
     * @param method
     * @param headers
     * @param querys
     * @param body
     * @return
     * @throws Exception
     */
    public static HttpResponse doPost(String host, String path, String method,
                                      Map<String, String> headers,
                                      Map<String, String> querys,
                                      String body)
            throws Exception {
        HttpClient httpClient = wrapClient(host);

        HttpPost request = new HttpPost(buildUrl(host, path, querys));
        for (Map.Entry<String, String> e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue());
        }

        if (StringUtils.isNotBlank(body)) {
            request.setEntity(new StringEntity(body, "utf-8"));
        }

        return httpClient.execute(request);
    }

    /**
     * Post stream
     *
     * @param host
     * @param path
     * @param method
     * @param headers
     * @param querys
     * @param body
     * @return
     * @throws Exception
     */
    public static HttpResponse doPost(String host, String path, String method,
                                      Map<String, String> headers,
                                      Map<String, String> querys,
                                      byte[] body)
            throws Exception {
        HttpClient httpClient = wrapClient(host);

        HttpPost request = new HttpPost(buildUrl(host, path, querys));
        for (Map.Entry<String, String> e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue());
        }

        if (body != null) {
            request.setEntity(new ByteArrayEntity(body));
        }

        return httpClient.execute(request);
    }

    /**
     * Put String
     * @param host
     * @param path
     * @param method
     * @param headers
     * @param querys
     * @param body
     * @return
     * @throws Exception
     */
    public static HttpResponse doPut(String host, String path, String method,
                                     Map<String, String> headers,
                                     Map<String, String> querys,
                                     String body)
            throws Exception {
        HttpClient httpClient = wrapClient(host);

        HttpPut request = new HttpPut(buildUrl(host, path, querys));
        for (Map.Entry<String, String> e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue());
        }

        if (StringUtils.isNotBlank(body)) {
            request.setEntity(new StringEntity(body, "utf-8"));
        }

        return httpClient.execute(request);
    }

    /**
     * Put stream
     * @param host
     * @param path
     * @param method
     * @param headers
     * @param querys
     * @param body
     * @return
     * @throws Exception
     */
    public static HttpResponse doPut(String host, String path, String method,
                                     Map<String, String> headers,
                                     Map<String, String> querys,
                                     byte[] body)
            throws Exception {
        HttpClient httpClient = wrapClient(host);

        HttpPut request = new HttpPut(buildUrl(host, path, querys));
        for (Map.Entry<String, String> e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue());
        }

        if (body != null) {
            request.setEntity(new ByteArrayEntity(body));
        }

        return httpClient.execute(request);
    }

    /**
     * Delete
     *
     * @param host
     * @param path
     * @param method
     * @param headers
     * @param querys
     * @return
     * @throws Exception
     */
    public static HttpResponse doDelete(String host, String path, String method,
                                        Map<String, String> headers,
                                        Map<String, String> querys)
            throws Exception {
        HttpClient httpClient = wrapClient(host);

        HttpDelete request = new HttpDelete(buildUrl(host, path, querys));
        for (Map.Entry<String, String> e : headers.entrySet()) {
            request.addHeader(e.getKey(), e.getValue());
        }

        return httpClient.execute(request);
    }

    private static String buildUrl(String host, String path, Map<String, String> querys) throws UnsupportedEncodingException {
        StringBuilder sbUrl = new StringBuilder();
        sbUrl.append(host);
        if (!StringUtils.isBlank(path)) {
            sbUrl.append(path);
        }
        if (null != querys) {
            StringBuilder sbQuery = new StringBuilder();
            for (Map.Entry<String, String> query : querys.entrySet()) {
                if (0 < sbQuery.length()) {
                    sbQuery.append("&");
                }
                if (StringUtils.isBlank(query.getKey()) && !StringUtils.isBlank(query.getValue())) {
                    sbQuery.append(query.getValue());
                }
                if (!StringUtils.isBlank(query.getKey())) {
                    sbQuery.append(query.getKey());
                    if (!StringUtils.isBlank(query.getValue())) {
                        sbQuery.append("=");
                        sbQuery.append(URLEncoder.encode(query.getValue(), "utf-8"));
                    }
                }
            }
            if (0 < sbQuery.length()) {
                sbUrl.append("?").append(sbQuery);
            }
        }

        return sbUrl.toString();
    }

    private static HttpClient wrapClient(String host) {
        HttpClient httpClient = new DefaultHttpClient();
        if (host.startsWith("https://")) {
            sslClient(httpClient);
        }

        return httpClient;
    }

    private static void sslClient(HttpClient httpClient) {
        try {
            SSLContext ctx = SSLContext.getInstance("TLS");
            X509TrustManager tm = new X509TrustManager() {
                public X509Certificate[] getAcceptedIssuers() {
                    return null;
                }
                public void checkClientTrusted(X509Certificate[] xcs, String str) {

                }
                public void checkServerTrusted(X509Certificate[] xcs, String str) {

                }
            };
            ctx.init(null, new TrustManager[] { tm }, null);
            SSLSocketFactory ssf = new SSLSocketFactory(ctx);
            ssf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
            ClientConnectionManager ccm = httpClient.getConnectionManager();
            SchemeRegistry registry = ccm.getSchemeRegistry();
            registry.register(new Scheme("https", 443, ssf));
        } catch (KeyManagementException ex) {
            throw new RuntimeException(ex);
        } catch (NoSuchAlgorithmException ex) {
            throw new RuntimeException(ex);
        }
    }
}

2.3. 3 integration test

gulimall-third-party/src/test/java/site/zhourui/gulimall/thirdparty/SMSTest.java

package site.zhourui.gulimall.thirdparty;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.List;
import java.util.Map;

public class SMSTest {
    public static void main(String[] args) {
        String host = "https://fesms.market.alicloudapi.com "; / / [1] the requested address supports http, https and WEBSOCKET
        String path = "/sms/";// [2] Suffix
        String appcode = "004c4072d4ed40b48xxx"; // [3] After opening the service, the buyer Center - view AppCode
        String code = "123456"; // [4] The request parameters are described in the document
        String phone = "17748781xxx"; // [4] The request parameters are described in the document
        String sign = "1"; // [4] The request parameters are described in the document
        String skin = "1"; // [4] The request parameters are described in the document
        String urlSend = host + path + "?code=" + code + "&phone=" + phone + "&sign=" + sign + "&skin=" + skin ; // [5] Splice request link
        try {
            URL url = new URL(urlSend);
            HttpURLConnection httpURLCon = (HttpURLConnection) url.openConnection();
            httpURLCon.setRequestProperty("Authorization", "APPCODE " + appcode);// Format Authorization:APPCODE
            // (English space in the middle)
            int httpCode = httpURLCon.getResponseCode();
            if (httpCode == 200) {
                String json = read(httpURLCon.getInputStream());
                System.out.println("Normal request billing(Others are not charged)");
                System.out.println("Get returned json:");
                System.out.print(json);
            } else {
                Map<String, List<String>> map = httpURLCon.getHeaderFields();
                String error = map.get("X-Ca-Error-Message").get(0);
                if (httpCode == 400 && error.equals("Invalid AppCode `not exists`")) {
                    System.out.println("AppCode error ");
                } else if (httpCode == 400 && error.equals("Invalid Url")) {
                    System.out.println("Requested Method,Path Or environmental error");
                } else if (httpCode == 400 && error.equals("Invalid Param Location")) {
                    System.out.println("Parameter error");
                } else if (httpCode == 403 && error.equals("Unauthorized")) {
                    System.out.println("The service is not authorized (or URL and Path (incorrect)");
                } else if (httpCode == 403 && error.equals("Quota Exhausted")) {
                    System.out.println("Package times used up ");
                } else {
                    System.out.println("Wrong parameter name or other error");
                    System.out.println(error);
                }
            }

        } catch (MalformedURLException e) {
            System.out.println("URL Format error");
        } catch (UnknownHostException e) {
            System.out.println("URL Address error");
        } catch (Exception e) {
            // Open the comment to view the detailed error reporting exception information
            // e.printStackTrace();
        }

    }

    /*
     * Read return result
     */
    private static String read(InputStream is) throws IOException {
        StringBuffer sb = new StringBuffer();
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        String line = null;
        while ((line = br.readLine()) != null) {
            line = new String(line.getBytes(), "utf-8");
            sb.append(line);
        }
        br.close();
        return sb.toString();
    }
}

3.3.4 three party service integration SMS service

3.3.4.1 add SMS service component

gulimall-third-party/src/main/java/site/zhourui/gulimall/thirdparty/component/SmsComponent.java

package site.zhourui.gulimall.thirdparty.component;

import lombok.Data;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;

import java.util.List;
import java.util.Map;

/**
 * @author zr
 * @date 2021/11/29 18:15
 */
@ConfigurationProperties(prefix = "spring.cloud.alicloud.sms")
@Data
@Component
public class SmsComponent {

    private String host;
    private String path;
    private String skin;
    private String sign;
    private String appcode;

    public void sendCode(String phone,String code) {

        String urlSend = host + path + "?code=" + code + "&phone=" + phone + "&sign=" + sign + "&skin=" + skin ; // [5] Splice request link
        try {
            URL url = new URL(urlSend);
            HttpURLConnection httpURLCon = (HttpURLConnection) url.openConnection();
            httpURLCon.setRequestProperty("Authorization", "APPCODE " + appcode);// Format Authorization:APPCODE
            // (English space in the middle)
            int httpCode = httpURLCon.getResponseCode();
            if (httpCode == 200) {
                String json = read(httpURLCon.getInputStream());
                System.out.println("Normal request billing(Others are not charged)");
                System.out.println("Get returned json:");
                System.out.print(json);
            } else {
                Map<String, List<String>> map = httpURLCon.getHeaderFields();
                String error = map.get("X-Ca-Error-Message").get(0);
                if (httpCode == 400 && error.equals("Invalid AppCode `not exists`")) {
                    System.out.println("AppCode error ");
                } else if (httpCode == 400 && error.equals("Invalid Url")) {
                    System.out.println("Requested Method,Path Or environmental error");
                } else if (httpCode == 400 && error.equals("Invalid Param Location")) {
                    System.out.println("Parameter error");
                } else if (httpCode == 403 && error.equals("Unauthorized")) {
                    System.out.println("The service is not authorized (or URL and Path (incorrect)");
                } else if (httpCode == 403 && error.equals("Quota Exhausted")) {
                    System.out.println("Package times used up ");
                } else {
                    System.out.println("Wrong parameter name or other error");
                    System.out.println(error);
                }
            }

        } catch (MalformedURLException e) {
            System.out.println("URL Format error");
        } catch (UnknownHostException e) {
            System.out.println("URL Address error");
        } catch (Exception e) {
            // Open the comment to view the detailed error reporting exception information
            // e.printStackTrace();
        }

    }

    /*
     * Read return result
     */
    private static String read(InputStream is) throws IOException {
        StringBuffer sb = new StringBuffer();
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        String line = null;
        while ((line = br.readLine()) != null) {
            line = new String(line.getBytes(), "utf-8");
            sb.append(line);
        }
        br.close();
        return sb.toString();
    }

}

3.3.4.2 extract component related configuration to configuration file

Import configuration prompt dependency

gulimall-third-party/pom.xml

        <!--Custom configuration tips        -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

New configuration

gulimall-third-party/src/main/resources/application.yml

      sms:
        host: http://fesms.market.alicloudapi.com
        path: /sms/
        skin: 1
        sign: 1
        appcode: 004c4072d4ed40b489d77b987ad3404d
3.3.4.3 add SmsSendController to call other services

gulimall-third-party/src/main/java/site/zhourui/gulimall/thirdparty/controller/SmsSendController.java

package site.zhourui.gulimall.thirdparty.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import site.zhourui.common.utils.R;
import site.zhourui.gulimall.thirdparty.component.SmsComponent;


/**
 * @author zr
 * @date 2021/11/29 18:28
 */
@RestController
@RequestMapping(value = "/sms")
public class SmsSendController {
    @Autowired
    private SmsComponent smsComponent;

    /**
     * Provided to other services for calling
     * @param phone
     * @param code
     * @return
     */
    @GetMapping(value = "/sendCode")
    public R sendCode(@RequestParam("phone") String phone, @RequestParam("code") String code) {
        //Send verification code
        smsComponent.sendCode(phone,code);
        return R.ok();
    }
}

3.3.4.4 testing

Test: localhost:30000/sms/sendCode?phone=17748781568&code=8888

test result

Successfully received SMS at the same time

3. The authentication service calls the SMS service

3.1 the authentication service remotely calls the third-party service to send SMS

gulimall-auth-server/src/main/java/site/zhourui/gulimall/auth/feign/ThirdPartFeignService.java

package site.zhourui.gulimall.auth.feign;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import site.zhourui.common.utils.R;

/**
 * @author zr
 * @date 2021/11/29 22:13
 */
@FeignClient("gulimall-third-party")
public interface ThirdPartFeignService {

    @GetMapping(value = "/sms/sendCode")
    public R sendCode(@RequestParam("phone") String phone, @RequestParam("code") String code);
}

3.2 SMS sending interface of authentication service

gulimall-auth-server/src/main/java/site/zhourui/gulimall/auth/controller/LoginController.java

package site.zhourui.gulimall.auth.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import site.zhourui.common.utils.R;
import site.zhourui.gulimall.auth.feign.ThirdPartFeignService;

import java.util.UUID;

/**
 * @author zr
 * @date 2021/11/29 22:15
 */
@Controller
public class LoginController {
    @Autowired
    ThirdPartFeignService thirdPartFeignService;

    @GetMapping(value = "/sms/sendCode")
    public R sendCode(@RequestParam("phone") String phone)
    {
        String code = UUID.randomUUID().toString().substring(0, 5);
        thirdPartFeignService.sendCode(phone,code);
        return R.ok();
    }
}

3.3 front end calling interface

Sending verification code SMS succeeded

3.4 defects at this time

The interface is written in the front-end js code and can still be stolen by others

Due to the exposure of the interface that sends the verification code, in order to prevent malicious attacks, we can't let the interface be called at will.

  • Store the phone number and verification code in redis with phone code, and store the current time together with code
    • If the v retrieved from the current phone is not empty and the current time is within 60s of the storage time, it indicates that the number has been called within 60s, and an error message is returned
    • When calling again after 60s, you need to delete the previously stored phone code
    • Code has an expiration time. We set it to 10min and verify that the verification code is valid within 10min

3.5 interface anti brush function

Added interface anti brush function:
1) Query redis first to see if it exceeds 60s, otherwise it is not allowed to send SMS
2) It is stored in redis, the expiration time is 10min, and the current system time is stored

3.5.1 introducing redis related dependencies

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

3.5.2 configure redis related information

  redis:
    host: 192.168.157.128
    port: 6379

3.5.3 realization of sending SMS verification code by authentication service

gulimall-common/src/main/java/site/zhourui/common/constant/AuthServerConstant.java

This constant is used for the prefix of redis SMS verification code

package site.zhourui.common.constant;

/**
 * @author zr
 * @date 2021/11/29 22:47
 */
public class AuthServerConstant {
    public static final String SMS_CODE_CACHE_PREFIX = "sms:code:";
}

package site.zhourui.gulimall.auth.controller;

import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import site.zhourui.common.constant.AuthServerConstant;
import site.zhourui.common.exception.BizCodeEnume;
import site.zhourui.common.utils.R;
import site.zhourui.gulimall.auth.feign.ThirdPartFeignService;

import java.util.concurrent.TimeUnit;

/**
 * @author zr
 * @date 2021/11/29 22:15
 */
@Controller
public class LoginController {
    @Autowired
    private ThirdPartFeignService thirdPartFeignService;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @ResponseBody
    @GetMapping(value = "/sms/sendCode")
    public R sendCode(@RequestParam("phone") String phone) {

        //1. Interface anti brush
        String redisCode = stringRedisTemplate.opsForValue().get(AuthServerConstant.SMS_CODE_CACHE_PREFIX + phone);
        if (!StringUtils.isEmpty(redisCode)) {
            //The time when the activity is saved in redis. Subtract the time saved in redis from the current time to judge whether the user's mobile phone number sends the verification code within 60s
            long currentTime = Long.parseLong(redisCode.split("_")[1]);
            if (System.currentTimeMillis() - currentTime < 60000) {
                //It cannot be sent again within 60s
                return R.error(BizCodeEnume.SMS_CODE_EXCEPTION.getCode(),BizCodeEnume.SMS_CODE_EXCEPTION.getMsg());
            }
        }

        //2. Re validation of verification code redis. Save key phone, value code
//        String code = UUID.randomUUID().toString().substring(0, 5);
//        String redisValue = code+"_"+System.currentTimeMillis();
        int code = (int) ((Math.random() * 9 + 1) * 100000);// The verification code can only be numeric
        String codeNum = String.valueOf(code);
        String redisStorage = codeNum + "_" + System.currentTimeMillis();

        //Store in redis to prevent the same mobile phone number from sending verification code again within 60 seconds
        stringRedisTemplate.opsForValue().set(AuthServerConstant.SMS_CODE_CACHE_PREFIX + phone,
                redisStorage,365, TimeUnit.DAYS);

        thirdPartFeignService.sendCode(phone, codeNum);

        return R.ok();
    }
}

Send the verification code of the number again within 60 seconds

4. Realization of relevant functions of registration page

4.1 background JSR 303 verification

Write the registered body logic in the GUI mall auth server service

  • If the JSR303 verification fails, the error message is encapsulated through BindingResult and redirected to the registration page
  • If it passes the JSR303 verification, you need to take a value from redis to judge whether the verification code is correct. If it is correct, you need to register with the member service (check that the verification code, user name and mobile phone number are unique). After passing the verification, the member information is stored
  • If the member service call is successful, it will be redirected to the login page, otherwise the error information returned by the encapsulated remote service will be returned to the registration page

Note: RedirectAttributes can save information through session and carry the past information during redirection

gulimall-auth-server/src/main/java/site/zhourui/gulimall/auth/vo/UserRegisterVo.java

package site.zhourui.gulimall.auth.vo;
import lombok.Data;

import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;
import org.hibernate.validator.constraints.Length;

/**
 * @author zr
 * @date 2021/11/30 10:39
 */
/**
 * vo used for registration
 */
@Data
public class UserRegisterVo {

    @NotEmpty(message = "User name must be submitted")
    @Length(min = 6, max = 19, message="Username length must be 6-18 character")
    private String userName;

    @NotEmpty(message = "Password must be filled in")
    @Length(min = 6,max = 18,message = "Password length must be 6-18 characters")
    private String password;

    @NotEmpty(message = "Mobile phone number must be filled in")
    @Pattern(regexp = "^[1]([3-9])[0-9]{9}$", message = "Incorrect mobile phone number format")
    private String phone;

    @NotEmpty(message = "Verification code must be filled in")
    private String code;

}

4.2 member service, storing member information

4.2.1 receiving the registration object transmitted by the front end

gulimall-member/src/main/java/site/zhourui/gulimall/member/vo/MemberUserRegisterVo.java

package site.zhourui.gulimall.member.vo;

/**
 * @author zr
 * @date 2021/11/30 11:01
 */

import lombok.Data;

/**
 * Member registration Vo
 */
@Data
public class MemberUserRegisterVo {
    private String userName;
    private String password;
    private String phone;
}

4.2.2 abnormal duplication of user name and mobile phone number

gulimall-member/src/main/java/site/zhourui/gulimall/member/exception/PhoneException.java

package site.zhourui.gulimall.member.exception;

/**
 * @author zr
 * @date 2021/11/30 11:02
 */
public class PhoneException extends RuntimeException {
    public PhoneException() {
        super("The same phone number exists");
    }
}

gulimall-member/src/main/java/site/zhourui/gulimall/member/exception/UsernameException.java

package site.zhourui.gulimall.member.exception;

/**
 * @author zr
 * @date 2021/11/30 11:02
 */
public class UsernameException extends RuntimeException {
    public UsernameException() {
        super("The same user name exists");
    }
}

gulimall-member/src/main/java/com/atguigu/gulimall/member/controller/MemberController.java

Register logic through gulimall Member Member Member Service

  • Judge whether the current registered member name and phone number have been registered through the exception mechanism. If they have been registered, throw the corresponding user-defined exception and encapsulate the corresponding error information when returning
  • If there is no registration, encapsulate the passed member information, and set the default member level and creation time
    @PostMapping(value = "/register")
    public R register(@RequestBody MemberUserRegisterVo vo) {
        try {
            memberService.register(vo);
            //Exception mechanism: judge what kind of error occurs by capturing the corresponding user-defined exception and encapsulate the error information
        } catch (PhoneException e) {
            return R.error(BizCodeEnume.PHONE_EXIST_EXCEPTION.getCode(),BizCodeEnume.PHONE_EXIST_EXCEPTION.getMsg());
        } catch (UsernameException e) {
            return R.error(BizCodeEnume.USER_EXIST_EXCEPTION.getCode(),BizCodeEnume.USER_EXIST_EXCEPTION.getMsg());
        }
        return R.ok();
    }

4.2.3 MD5 & salt value & bcrypt

4.2.3.1 MD5
  • Message Digest algorithm 5, message digest algorithm
  • Compressibility: for any length of data, the calculated MD5 value length is fixed.
  • Easy to calculate: it is easy to calculate the MD5 value from the original data.
  • Resistance to modification: any change to the original data, even if only one byte is modified, the resulting MD5 value is very different.
  • Strong anti-collision: it is very difficult to find two different data and make them have the same MD5 value.
  • Irreversible
4.2.3.2 adding salt
  • Combine the generated random number with the MD5 generated string
  • The database stores MD5 value and salt value at the same time. When verifying the correctness, use salt for MD5
4.2.3.3 BCryptPasswordEncoder

Compared with other encryption methods, BCryptPasswordEncoder has its own advantages. First, the encrypted hash value is different every time, just like the salt value encryption of md5, but the salt value encryption uses random numbers. The former uses its built-in algorithm rules. After all, if the random number is not set properly, it still has a certain chance to be broken. Secondly, the encrypted storage string generated by BCryptPasswordEncoder also has as many as 60 bits. The most important point is that md5 encryption is not the encryption method advocated by spring security, so we still need to know more about the new encryption method.

BCryptPasswordEncoder encrypts the same value every time, it will get different ciphertext

4.2.3.4 BCryptPasswordEncoder encryption (encode) and decryption (matches)
4.2. 3.4. 1. Encrypt encode
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        String encode = bCryptPasswordEncoder.encode(vo.getPassword());

4.2. 3.4. 2 decryption

Although the value after each encryption is different, matches can match

			BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
            //For password matching, parameter 1 is the plaintext password entered by the user, and parameter 2 is the encrypted password of the corresponding user obtained by querying the data
            boolean matches = passwordEncoder.matches(password, password1);

4.2. 4 user service registration interface

gulimall-member/src/main/java/site/zhourui/gulimall/member/service/MemberService.java

    /**
     * User registration
     * @param vo
     */
    void register(MemberUserRegisterVo vo);

    /**
     * Determine whether the mailbox is duplicate
     * @param phone
     * @return
     */
    void checkPhoneUnique(String phone) throws PhoneException;

    /**
     * Determine whether the user name is duplicate
     * @param userName
     * @return
     */
    void checkUserNameUnique(String userName) throws UsernameException;

gulimall-member/src/main/java/site/zhourui/gulimall/member/service/impl/MemberServiceImpl.java

    @Override
    public void register(MemberUserRegisterVo vo) {
        MemberEntity memberEntity = new MemberEntity();

        //Set default level [ordinary member]
        MemberLevelEntity levelEntity = memberLevelDao.getDefaultLevel();
        memberEntity.setLevelId(levelEntity.getId());

        //Set other default information
        //Check whether the user name and mobile phone number are unique. Perceptual anomaly
        checkPhoneUnique(vo.getPhone());
        checkUserNameUnique(vo.getUserName());

        memberEntity.setNickname(vo.getUserName());
        memberEntity.setUsername(vo.getUserName());
        //MD5 encryption of password
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        String encode = bCryptPasswordEncoder.encode(vo.getPassword());
        memberEntity.setPassword(encode);
        memberEntity.setMobile(vo.getPhone());
        memberEntity.setGender(0);
        memberEntity.setCreateTime(new Date());

        //Save data
        baseMapper.insert(memberEntity);
    }

    @Override
    public void checkPhoneUnique(String phone) throws PhoneException {
        Integer phoneCount = baseMapper.selectCount(new QueryWrapper<MemberEntity>().eq("mobile", phone));
        if (phoneCount > 0) {
            throw new PhoneException();
        }
    }

    @Override
    public void checkUserNameUnique(String userName) throws UsernameException {
        Integer usernameCount = baseMapper.selectCount(new QueryWrapper<MemberEntity>().eq("username", userName));
        if (usernameCount > 0) {
            throw new UsernameException();
        }
    }

4.3 authentication service registration interface

feign interface

gulimall-auth-server/src/main/java/site/zhourui/gulimall/auth/feign/MemberFeignService.java

package site.zhourui.gulimall.auth.feign;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import site.zhourui.common.utils.R;
import site.zhourui.gulimall.auth.vo.UserRegisterVo;

/**
 * @author zr
 * @date 2021/11/30 11:12
 */
@FeignClient("gulimall-member")
public interface MemberFeignService {
    @PostMapping(value = "/member/member/register")
    R register(@RequestBody UserRegisterVo vo);
}

gulimall-auth-server/src/main/java/site/zhourui/gulimall/auth/controller/LoginController.java

Add @ Valid when JSR303 is enabled

    /**
     *
     * TODO: Redirection carries data: use the session principle to put the data in the session.
     * TODO:As long as you jump to the next page and take out the data, the data in the session will be deleted
     * TODO: session problem under distribution
     * RedirectAttributes: Redirection can also retain data without loss
     * User registration
     * @return
     */
    @PostMapping(value = "/register")
    public String register(@Valid UserRegisterVo vos, BindingResult result, RedirectAttributes attributes) {

        //If there is an error, go back to the registration page
        if (result.hasErrors()) {
            Map<String, String> errors = result.getFieldErrors().stream().collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
            attributes.addFlashAttribute("errors", errors);

            //Validation error return to registration page
            return "redirect:http://auth.gulimall.com/reg.html";
            // 1,return "reg";  Request forwarding [use Model to share data] [exception:, 405 POST not support]
            // 2. "redirect:http:/reg.html" redirect [share data using RedirectAttributes] [bug: redirect with ip+port]
            // 3,redirect:http://auth.gulimall.com/reg.html Redirect [share data using RedirectAttributes]
        }

        //1. Validation code
        String code = vos.getCode();

        //Obtain the verification code stored in Redis
        String redisCode = stringRedisTemplate.opsForValue().get(AuthServerConstant.SMS_CODE_CACHE_PREFIX + vos.getPhone());
        if (!StringUtils.isEmpty(redisCode)) {
            // Judge whether the verification code is correct [there is a BUG. If there is a problem with string storage, the code is not parsed, and the data is empty, resulting in a permanent error of the verification code]
            if (code.equals(redisCode.split("_")[0])) {
                //Delete the verification code (not reusable); Token mechanism
                stringRedisTemplate.delete(AuthServerConstant.SMS_CODE_CACHE_PREFIX+vos.getPhone());
                //After the verification code passes, you can register in real time and call the remote service to register [ member service ]
                R register = memberFeignService.register(vos);
                if (register.getCode() == 0) {
                    //success
                    return "redirect:http://auth.gulimall.com/login.html";
                } else {
                    //fail
                    Map<String, String> errors = new HashMap<>();
                    errors.put("msg", register.getData("msg",new TypeReference<String>(){}));
                    attributes.addFlashAttribute("errors",errors);
                    return "redirect:http://auth.gulimall.com/reg.html";
                }
            } else {
                //Verification code error
                Map<String, String> errors = new HashMap<>();
                errors.put("code","Verification code error");
                attributes.addFlashAttribute("errors",errors);
                return "redirect:http://auth.gulimall.com/reg.html";
            }
        } else {
            // The verification code in redis has expired
            Map<String, String> errors = new HashMap<>();
            errors.put("code","Verification code expired");
            attributes.addFlashAttribute("errors",errors);
            return "redirect:http://auth.gulimall.com/reg.html";
        }
    }

5. Login with user name and password

5.1 receiving the login object transmitted from the front end

gulimall-auth-server/src/main/java/site/zhourui/gulimall/auth/vo/UserLoginVo.java

vo sent by user login front end

package site.zhourui.gulimall.auth.vo;

import lombok.Data;

/**
 * @author zr
 * @date 2021/11/30 13:51
 */
@Data
public class UserLoginVo {
    private String loginacct;
    private String password;
}

5.2 login interface of authentication service for front-end call

gulimall-auth-server/src/main/java/com/atguigu/gulimall/auth/controller/LoginController.java

    @PostMapping(value = "/login")
    public String login(UserLoginVo vo, RedirectAttributes attributes, HttpSession session) {
        //Remote login
        R login = memberFeignService.login(vo);
        if (login.getCode() == 0) {
            MemberResponseVo data = login.getData("data", new TypeReference<MemberResponseVo>() {});
            session.setAttribute(AuthServerConstant.LOGIN_USER, data);
            return "redirect:http://gulimall.com";
        } else {
            Map<String,String> errors = new HashMap<>();
            errors.put("msg",login.getData("msg",new TypeReference<String>(){}));
            attributes.addFlashAttribute("errors",errors);
            return "redirect:http://auth.gulimall.com/login.html";
        }
    }

5.3 login interface of user service for remote call of other services

gulimall-member/src/main/java/site/zhourui/gulimall/member/vo/MemberUserLoginVo.java

package site.zhourui.gulimall.member.vo;

import lombok.Data;

/**
 * @author zr
 * @date 2021/11/30 13:42
 */
@Data
public class MemberUserLoginVo {
    private String loginacct;
    private String password;
}

gulimall-member/src/main/java/site/zhourui/gulimall/member/controller/MemberController.java

    /**
     * Login interface
     */
    @PostMapping(value = "/login")
    public R login(@RequestBody MemberUserLoginVo vo) {

        MemberEntity memberEntity = memberService.login(vo);

        if (memberEntity != null) {
            return R.ok().setData(memberEntity);
        } else {
            return R.error(BizCodeEnume.LOGINACCT_PASSWORD_EXCEPTION.getCode(),BizCodeEnume.LOGINACCT_PASSWORD_EXCEPTION.getMsg());
        }
    }

gulimall-member/src/main/java/site/zhourui/gulimall/member/service/MemberService.java

    /**
     * User login
     */
    MemberEntity login(MemberUserLoginVo vo);
    /**
     * Local login
     */
    @Override
    public MemberEntity login(MemberUserLoginVo vo) {

        String loginacct = vo.getLoginacct();
        String password = vo.getPassword();

        //1. Query the database SELECT * FROM ums_member WHERE username = ? OR mobile = ?
        MemberEntity memberEntity = baseMapper.selectOne(new QueryWrapper<MemberEntity>()
                .eq("username", loginacct).or().eq("mobile", loginacct));

        if (memberEntity == null) {
            //Login failed
            return null;
        } else {
            //Get the password in the database
            String password1 = memberEntity.getPassword();
            BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
            //Make password matching
            boolean matches = passwordEncoder.matches(password, password1);
            if (matches) {
                //Login succeeded
                return memberEntity;
            }
        }
        return null;
    }

5.4 feign interface addition

gulimall-auth-server/src/main/java/site/zhourui/gulimall/auth/feign/MemberFeignService.java

    @PostMapping(value = "/member/member/login")
    R login(@RequestBody UserLoginVo vo);

6. Social login (OAuth2.0)

QQ, microblog, github and other websites have a large number of users. In order to simplify the login and registration logic of their own websites, other websites have introduced the social login function;
Steps:
1) The user clicks the QQ button
2) Guide to jump to QQ authorization page
3) . the user actively clicks authorization and jumps back to the previous web page.

6.1 OAuth2.0

  • OAuth: OAuth (open authorization) is an open standard that allows users to authorize third-party websites to access the information they store on other service providers without providing user names and passwords to third-party websites or sharing all the contents of their data.
  • OAuth2.0: for user related openapis (such as obtaining user information, dynamic synchronization, photos, logs, sharing, etc.), in order to protect the security and privacy of user data, third-party websites need to explicitly ask the user for authorization before accessing user data.

6.1. 1 official process

(A) After the user opens the client, the client requires the user to give authorization.
(B) The user agrees to grant authorization to the client.
(C) The client uses the authorization obtained in the previous step to apply for a token from the authentication server.
(D) After authenticating the client, the authentication server confirms that there is no error and agrees to issue the token.
(E) The client uses a token to apply to the resource server to obtain resources.
(F) The resource server confirms that the token is correct and agrees to open the resource to the client.

6.1. 2 process

Note:

  1. Use code in exchange for AccessToken. Code can only be used once
  2. The accessToken of the same user will not change for a period of time, even if it is obtained multiple times

6.2 microblog social login

6.2. 1. Enter the microblog open platform (give up if the approval time is too long)

use QQ,Wechat login
 Steps:
	1),User click QQ Button
	2),Boot jump to QQ Authorization page
	3),The user actively clicks authorization and jumps back to the previous page.

1,OAuth2.0
OAuth: OAuth(Open authorization) is an open standard that allows users to authorize third-party websites to access information they store on other service providers,Instead of providing user names and passwords to third-party websites or sharing all the content of their data.

OAuth2.0:For user related OpenAPI(For example, access to user information, dynamic synchronization, photos, logs, sharing, etc.), in order to protect the security and privacy of user data, third-party websites need to explicitly ask for authorization from users before accessing user data.

Official version process:
Client client: CSDN
Resource Owner: User himself

2,Microblog login
	1)https://open.weibo.com / = "website access =" create new app
	2)Advanced information OAuth2.0 Authorization settings
		Authorization callback page: http://auth.gulimall.com/oauth2.0/weibo/success
		Cancel authorization callback page: http://gulimall.com/fall
	3)doc: https://open.weibo.com/wiki / authorization mechanism description
		1,modify a Label jump address: https://api.weibo.com/oauth2/authorize?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=YOUR_REGISTERED_REDIRECT_URI
		2,modify client_id and redirect_uri[Self created apps App Key And authorization callback page]
			After logging in with the microblog account, the user jumps to the callback page and brings a code=xxxx
		3,according to code exchange for Access Token[code Can only be used once!]
		Send request: https://api.weibo.com/oauth2/access_token?client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET&grant_type=authorization_code&redirect_uri=YOUR_REGISTERED_REDIRECT_URI&code=CODE
 result:{
         "access_token": "SlAV32hkKG",
    	 "remind_in": 3600,
    	 "expires_in": 3600,
    	 "uid": 134156431
	 }
	 	4,have access to access_token To use the microblog interface to obtain information: [for example, the user interface accesses the user microblog account information] https://open.weibo.com/apps/2129105835/privilege [access_token has a lifetime and can be reused]
	 	
3,to write java controller Request: [see the code for details]
	1)send out post Request use code Change zone access_token
	2)Judge whether the current user has logged in to [login or registration, binding] uid [account number of the system]
		1,have	Log in directly [according to uid Associated query user information]
		2,Not automatically created
			Call microblog	Account information [nickname, gender, avatar,]
		
		3,Return user information
		


·gulimall-auth-server receive/auth2.0/sucess Social login successful callback
·gulimall-auth-server send out post Request basis code obtain access_token and uid
·towards gulimall-member Send login request and query member Watch, uid Is there an associated account [modify] member Table, add three fields]
	Social login UID	socialUid
	Social login TOKEN	accessToken
	Social login expiration time	expiresIn

In exchange for Access_Token

Logic:

6.3 gitee social login (real name authentication is not required)

6.3. 1. Open the third-party api permission in gitee

I haven't passed the real name certification of microblog open platform for 8 days. I gave up and chose gitee. I don't need real name certification approval

6.3. 2 create application

The callback address here must be consistent with that in the background

6.3. 3 initiate authentication request

This step can be written on the back end to prevent client_ The ID is exposed, and the callback address is filled in the callback address of the previous step

				<li>
					<a href="https://gitee.com/oauth/authorize?client_id=43966d65b7e3920a830bec212333df3a81e3fceb673520996db1d9265a0c26e6&redirect_uri=http://auth.gulimall.com/oauth2.0/gitee/success&response_type=code">
						<img style="width: 50px;height: 18px;" src="/static/login/JD_img/weibo.png"/>
						<!--                        <span>weibo</span>-->
					</a>
				</li>

6.3. 4. Add callback interface

Add constants related to code cloud authentication

package site.zhourui.gulimall.auth.constant;

/**
 * @author zr
 * @date 2021/12/8 17:19
 */
public class GiteeConstant {
    /**
     *  Steps for third-party authority authentication
     *  1. Via client_id and callback method to obtain the specified third-party authorization page
     *  2. The authorization of the third-party page is successful. Obtain the returned pass code through the callback method
     *  3. Exchange the login successful ticket token from the third-party service through code and callback
     *  4. Get the user's basic information through the ticket token of the third-party service and return to the front-end page
     */

    // The corresponding client id in the code cloud my application
    public static final String clientId = "xxxx";
    // Code cloud corresponding to my application
    public static final String  secret = "xxxx";
    // Configured callback interface address
    public static final String callback ="http://auth.gulimall.com/oauth2.0/gitee/success";
    // Jump to the authorization page of code cloud
    public static final String GiteeURI = "https://gitee.com/oauth/authorize?client_id=" + clientId + "&redirect_uri="+ callback+"&response_type=code";
    // Exchange the code of the code cloud for his token credentials
    public static final String address = "https://gitee.com";
    public static final String path = "/oauth/token";
    // Obtain user information through the token credentials of the code cloud service
    public static final String userInfo = "https://gitee.com/api/v5/user";

}

Add a SocialUser. The user accepts to obtain access_token related information

This class is best placed under the common service, which is required for remote invocation

package site.zhourui.gulimall.auth.vo;

import lombok.Data;

/**
 * @author zr
 * @date 2021/12/8 17:22
 */
@Data
public class GiteeSocialUser {
    private String accessToken;

    private String tokenType;

    private Long expiresIn;

    private String refreshToken;

    private String scope;

    private String createdAt;

}

gulimall-auth-server/src/main/java/site/zhourui/gulimall/auth/controller/OAuth2Controller.java

Here, you need to call the remote service social login interface oautlogin

package site.zhourui.gulimall.auth.controller;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpResponse;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import site.zhourui.common.constant.AuthServerConstant;
import site.zhourui.common.utils.HttpUtils;
import site.zhourui.common.utils.R;
import site.zhourui.gulimall.auth.constant.GiteeConstant;
import site.zhourui.gulimall.auth.feign.MemberFeignService;
import site.zhourui.gulimall.auth.vo.GiteeSocialUser;

import vo.MemberResponseVo;

import javax.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.Map;

/**
 * @author zr
 * @date 2021/12/8 17:13
 */
@Slf4j
@Controller
public class OAuth2Controller {
    @Autowired
    MemberFeignService memberFeignService;
    @GetMapping(value = "/oauth2.0/gitee/success")
    public String gitee(@RequestParam("code") String code, HttpSession session) throws Exception {

        //These parameter formats are mandatory. You can refer to the official api of the code cloud
        Map<String,String> params = new HashMap<>();
        params.put("grant_type","authorization_code");
        params.put("code",code);
        params.put("client_id", GiteeConstant.clientId);
        params.put("redirect_uri",GiteeConstant.callback);
        params.put("client_secret",GiteeConstant.secret);

        //1. Exchange the code returned according to the user's authorization for access_token
        HttpResponse response = HttpUtils.doPost("https://gitee.com", "/oauth/token", "post", new HashMap<>(), params, new HashMap<>());
//2. Handle
        if (response.getStatusLine().getStatusCode() == 200) {
            //Access obtained_ token
            String json = EntityUtils.toString(response.getEntity());// Get json string
            //String json = JSON.toJSONString(response.getEntity());
            GiteeSocialUser socialUser = JSON.parseObject(json, GiteeSocialUser.class);

            //Know which social user
            //1) If the current user enters the website for the first time, he will automatically register (generate a member information for the current social user, and the social account will correspond to the specified member in the future)
            //Log in or register this social user
            System.out.println("Use after login code In exchange token Value:" + socialUser.getAccessToken());
            //Call remote service
            R oauthLogin = memberFeignService.oauthLogin(socialUser);
            if (oauthLogin.getCode() == 0) {
                MemberResponseVo data = oauthLogin.getData("data", new TypeReference<MemberResponseVo>() {});
                log.info("Login succeeded: user information:\n{}",data.toString());

                //1. When using session for the first time, command the browser to save the card number and the cookie JSESSIONID
                //In the future, the browser will bring the cookie of this website when it visits which website
                //TODO 1. Default token. Current domain (solve the problem of sub domain session sharing)
                //TODO 2. Use JSON serialization to serialize objects into Redis
                session.setAttribute(AuthServerConstant.LOGIN_USER, data);

                //2. Log in successfully and jump back to the home page
                return "redirect:http://gulimall.com";
            } else {
                return "redirect:http://auth.gulimall.com/login.html";
            }
        } else {
            return "redirect:http://auth.gulimall.com/login.html";
        }
    }
}

6.3. 5. Add the remote call interface oautlogin

gulimall-member/src/main/java/site/zhourui/gulimall/member/service/MemberService.java

    /**
     * Login of social users
     * @param socialUser
     * @return
     */
    MemberEntity login(GiteeSocialUser socialUser) throws Exception;

gulimall-member/src/main/java/site/zhourui/gulimall/member/service/impl/MemberServiceImpl.java

Here, you need to judge whether the user has logged in with the social account before

Therefore, the UMS in the database is required_ A new field is added in the menber table to save the user's social login information

The logic here is different from that of the teacher. I get access_ The token information does not return the user id of the platform, so I wrote the user information query in the front

The external code cloud of the returned result of user information also has no user gender information

    @Override
    public MemberEntity login(GiteeSocialUser socialUser) throws Exception {
        Map<String, String> query = new HashMap<>();
        query.put("access_token", socialUser.getAccessToken());
        HttpResponse response = HttpUtils.doGet("https://gitee.com", "/api/v5/user", "get", new HashMap<String, String>(), query);
        String json = EntityUtils.toString(response.getEntity());
        JSONObject jsonObject = JSON.parseObject(json);
        String id = jsonObject.getString("id");
        String name = jsonObject.getString("name");
        String gender = jsonObject.getString("gender");
        String profileImageUrl = jsonObject.getString("avatar_url");
        //With login and registration logic
        String uid = id;

        //1. Judge whether the current social user has logged in to the system
        MemberEntity memberEntity = baseMapper.selectOne(new QueryWrapper<MemberEntity>().eq("social_uid", uid));

        if (memberEntity != null) {
            //This user has already registered
            //Time and access to update the user's access token_ token
            MemberEntity update = new MemberEntity();
            update.setId(memberEntity.getId());
            update.setAccessToken(socialUser.getAccessToken());
            update.setExpiresIn(socialUser.getExpiresIn());
            baseMapper.updateById(update);

            memberEntity.setAccessToken(socialUser.getAccessToken());
            memberEntity.setExpiresIn(socialUser.getExpiresIn());
            return memberEntity;
        } else {
            //2. If we don't find the corresponding record of the current social user, we need to register one
            MemberEntity register = new MemberEntity();
            //3. Query the social account information (nickname, gender, etc.) of the current social user
            // Remote call without affecting the result
            try {
//                Map<String, String> query = new HashMap<>();
//                query.put("access_token", socialUser.getAccessToken());
//                HttpResponse response = HttpUtils.doGet("https://gitee.com", "/api/v5/user", "get", new HashMap<String, String>(), query);

                if (response.getStatusLine().getStatusCode() == 200) {
                    //query was successful
//                    String gender = jsonObject.getString("gender");
                    register.setUsername(name);
                    register.setNickname(name);
                    register.setCreateTime(new Date());
                    register.setGender("m".equals(gender) ? 1 : 0);
                    register.setHeader(profileImageUrl);
                }
            }catch (Exception e){}
            register.setCreateTime(new Date());
            register.setSocialUid(uid);
            register.setAccessToken(socialUser.getAccessToken());
            register.setExpiresIn(socialUser.getExpiresIn());

            //Insert user information into the database
            baseMapper.insert(register);
            return register;
        }
    }

gulimall-member/src/main/java/site/zhourui/gulimall/member/controller/MemberController.java

    @PostMapping(value = "/oauth2/login")
    public R oauthLogin(@RequestBody GiteeSocialUser socialUser) throws Exception {

        MemberEntity memberEntity = memberService.login(socialUser);

        if (memberEntity != null) {
            return R.ok().setData(memberEntity);
        } else {
            return R.error(BizCodeEnume.LOGINACCT_PASSWORD_EXCEPTION.getCode(),BizCodeEnume.LOGINACCT_PASSWORD_EXCEPTION.getMsg());
        }
    }

6.3. 6. Add remote feign interface

gulimall-auth-server/src/main/java/site/zhourui/gulimall/auth/feign/MemberFeignService.java

    @PostMapping(value = "/member/member/oauth2/login")
    R oauthLogin(@RequestBody GiteeSocialUser socialUser) throws Exception;

6.3. 7 test

If you can get the following information, it means success, and a corresponding user will be created in the database

6.3. 8 current problems

We're at auth gulimall.com saves the user information into the session, but we redirect to gulimall.com after successful login com

As a result, the login is successful, and the user information is still not available on the home page

Solution: expand the session scope

7.Session sharing

7.1 session principle

Session is also a mechanism for recording browser status, but unlike cookie s, session is saved in the server.

Because http is a stateless protocol, when the server stores session data of multiple users, how to confirm which session on the server corresponds to the http request is very critical. This is also the core content of the session principle.

7.2 session sharing in distributed environment

7.2. 1. Same service in distributed scenario

According to the principle of session, the session information is saved in the server. Although it is the same service, it can not be shared in different servers

7.2. 2 different services

Because the session object is obtained according to the JSESSIONID in the cookie as the key. Different session ID s will lead to different session objects

7.3 Session sharing problem solving - Session replication

advantage

  • Web server (Tomcat) native support, only need to modify the configuration file

shortcoming

  • session synchronization requires data transmission, occupies a lot of network bandwidth, and reduces the business processing capacity of the server cluster
  • The data saved by any web server is the sum of all web server session s. Due to memory constraints, more web servers cannot be expanded horizontally
  • In the case of large-scale distributed clusters, this scheme is not desirable because all web servers save all data.
  • Personal summary: it is easy to use, but each server needs to save the full amount of session data and occupy the network bandwidth (suitable for small distributed)

7.4 Session sharing problem solving - client storage

advantage

  • The server does not need to store sessions, and users save their session information into cookie s. Save server resources

shortcoming

  • It's all shortcomings. It's just an idea.

    The details are as follows:

    • Each http request carries the user's complete information in the cookie, wasting network bandwidth
    • session data is placed in a cookie. The cookie has a length limit of 4K and cannot store a large amount of information
    • session data is placed in cookie s, which has potential security risks such as disclosure, tampering and theft

This method will not be used

7.5 Session sharing problem solving – hash consistency

Method 1: use the user's ip address for load balancing, so that a user will always access the same server

Method 2: use user id for load balancing, so that a user will always access the same server

  • advantage:
    • You only need to change the nginx configuration without modifying the application code
    • Load balancing: as long as the value distribution of hash attribute is uniform, the load of multiple web servers is balanced
    • It can support the horizontal expansion of web server (session synchronization method is not available and is limited by memory)
  • shortcoming
    • Sessions still exist in the web server, so restarting the web server may cause some session s to be lost and affect the business. For example, some users need to log in again
    • If the web server expands horizontally and the sessions are redistributed after rehash, some users will not be able to route the correct sessions
  • However, the above shortcomings are not very big, because session s are originally valid. Therefore, these two reverse proxy methods can be used

7.6 Session sharing problem solving – unified storage

  • advantage:
    • No potential safety hazard
    • It can be expanded horizontally, and the database / cache can be segmented horizontally
    • There will be no session loss when the web server is restarted or expanded
  • Insufficient
    • A network call is added, and the application code needs to be modified; For example, replace all getSession methods with the way of querying data from redis. Redis gets data much slower than memory
    • The above shortcomings can be perfectly solved by spring session

7.7 Session sharing problem solving - different services, sub domains and session sharing

When saving the session, the scope of jssessionid is raised to the maximum Like auth gulimall.com->. gulimall.com, then gulimall COM and all subdomains below it can get the jssessionid, and then query the corresponding session information in redis to realize session sharing between different services

The problem can be solved by sharing sessions between the same services. The domain name of the same service is the same, and the jssessionid is the same

7.8 spring session integration redis

7.8. 1. Import required dependencies

		<dependency>
			<groupId>org.springframework.session</groupId>
			<artifactId>spring-session-data-redis</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>

7.8. 2. Configure session storage mode

spring:
  redis:
    host: 192.168.157.128
    port: 6379
    #Using redis to store session s
  session:
    store-type: redis
    
server:
  port: 20000
  servlet:
  #Configure session expiration time
    session:
      timeout: 30m

7.8. 3. Start the spring session

Configure the annotation on the main startup class or configuration class

@EnableRedisHttpSession  //Integrate Redis as session storage
package site.zhourui.gulimall.auth;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;

/**
 * Core principles
 * 1),@EnableRedisHttpSession Import RedisHttpSessionConfiguration
 *      1,Added a component to the container
 *          RedisOperationsSessionRepository: Redis Operate the session, add, delete, modify and query the encapsulated class of the session
 *
 */
@EnableRedisHttpSession  //Integrate Redis as session storage
@EnableFeignClients
@EnableDiscoveryClient
@SpringBootApplication
public class GulimallAuthServerApplication {

	public static void main(String[] args) {
		SpringApplication.run(GulimallAuthServerApplication.class, args);
	}

}

At this point, perform the login operation to test

Because jdk is used for serialization by default, it is inconvenient to read. It is recommended to modify it to json

7.8. 4 modify to json serialization, and enlarge the scope (custom)

gulimall-auth-server/src/main/java/site/zhourui/gulimall/auth/config/GulimallSessionConfig.java

package site.zhourui.gulimall.auth.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.DefaultCookieSerializer;

/**
 * @author zr
 * @date 2021/12/12 10:29
 */
@Configuration
public class GulimallSessionConfig {
    @Bean
    public CookieSerializer cookieSerializer() {
        DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
        //Zoom in scope
        cookieSerializer.setDomainName("gulimall.com");
        cookieSerializer.setCookieName("GULISESSION");
        cookieSerializer.setCookieMaxAge(60*60*24*7);
        return cookieSerializer;
    }

    @Bean
    public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
        return new GenericJackson2JsonRedisSerializer();
    }
}

You can get jsessionid on the home page

The session serialization mode is modified to json

7.8. 5. Other modules need to obtain session s and also need to be integrated (the integration steps are the same as above)

The module that needs to share the session integrates the spring session

After the integration of gulimall product, the user can get the session on the home page after logging in, and then get the user information for display

At this point, we enter it manually http://auth.gulimall.com/login.html You can still enter the login page to log in again. You need to judge whether the user logs in when entering the login page. If the user has logged in, you can directly redirect to the home page, and the user is allowed to log in only if the user has not logged in

The user's login page has set the view mapping, which must be annotated. The view mapping has no logic. As long as it is this request, it will jump to the specified view (html). However, our current login page has a logical judgment and needs to be added in the controller

gulimall-auth-server/src/main/java/site/zhourui/gulimall/auth/controller/LoginController.java

    /**
     * Judge whether the session has loginUser. If not, jump to the login page and jump to the home page
     */
    @GetMapping(value = "/login.html")
    public String loginPage(HttpSession session) {
        //Get the user's information from the session to judge whether the user has logged in
        Object attribute = session.getAttribute(AuthServerConstant.LOGIN_USER);
        //If the user is not logged in, jump to the login page
        if (attribute == null) {
            return "login";
        } else {
            return "redirect:http://gulimall.com";
        }
    }

Enter manually after successful login http://auth.gulimall.com/login.html , automatically redirect back to the home page. After clearing session, enter the login page

7. Single sign on SSO

[Social login and spring session + extended sub domain are just the login of single system distributed cluster]

Multi system - single sign on

1. Log in at one place and everywhere

2. Exit at one place and exit everywhere

7.1 Xu Xueli open source project

Frame effect demonstration address: https://gitee.com/xuxueli0323/xxl-sso

Most important: central authentication server
Core: even if the domain names of the three systems are different, try to synchronize the bills of the same user to the three systems;
1) . central authentication server; ssoserver.com
2) For other systems, you want to log in to ssoserver Com login, login successfully, jump back
3) As long as there is one login, the others do not need to log in
4) . system wide system - individual SSO sessionid;

7.2 single sign on Implementation

7.2. 0 flowchart

7.2. 1. Single sign on server gulimall test SSO server

7.2. 1.1 creating steps

7.2.1.2 pom files (including thymeleaf,redis)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>site.zhourui</groupId>
    <artifactId>gulimall-test-sso-server</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>gulimall-test-sso-server</name>
    <description>Single sign on central authentication server</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

7.2. 1.3 configuring application
server.port=8080
#Virtual machine address, default port 6379, no configuration
spring.redis.host=192.168.157.128
7.2. 1.4 add LoginController

gulimall-test-sso-server/src/main/java/site/zhourui/gulimall/ssoserver/controller/LoginController.java

package site.zhourui.gulimall.ssoserver.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;

/**
 * @author zr
 * @date 2021/12/4 16:48
 */
@Controller
public class LoginController {

    @Autowired
    StringRedisTemplate redisTemplate;

    @ResponseBody
    @GetMapping("/userinfo")
    public String userinfo(@RequestParam(value = "token") String token) {
        String s = redisTemplate.opsForValue().get(token);

        return s;

    }


    @GetMapping("/login.html")
    public String loginPage(@RequestParam(value = "redirect_url",required = false) String url, Model model, @CookieValue(value = "sso_token", required = false) String sso_token) {
        if (!StringUtils.isEmpty(sso_token)) {
            return "redirect:" + url + "?token=" + sso_token;
        }
        model.addAttribute("url", url);

        return "login";
    }

    @PostMapping(value = "/doLogin")
    public String doLogin(@RequestParam("username") String username,
                          @RequestParam("password") String password,
                          @RequestParam("redirect_url") String url,
                          HttpServletResponse response) {

        //Log in successfully, jump back to the login page
        if (!StringUtils.isEmpty(username) && !StringUtils.isEmpty(password)) {

            String uuid = UUID.randomUUID().toString().replace("_", "");
            redisTemplate.opsForValue().set(uuid, username);
            Cookie sso_token = new Cookie("sso_token", uuid);

            response.addCookie(sso_token);
            return "redirect:" + url + "?token=" + uuid;
        }
        return "login";
    }

}

7.2. 1.4 add a new template login html

gulimall-test-sso-server/src/main/resources/templates/login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Landing page</title>
</head>
<body>
<form action="/doLogin" method="post">
    user name:<input type="text" name="username" /><br />
    password:<input type="password" name="password" /><br />
    <input type="hidden" name="redirect_url" th:value="${url}" />
    <input type="submit" value="Sign in">
</form>
</body>
</html>

7.2. 2 single sign on client (clien1)

7.2. 2.1 creating steps

7.2.2.2 pom file

client1pom file

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>site.zhourui</groupId>
    <artifactId>gulimall-test-sso-client</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>gulimall-test-sso-client</name>
    <description>client-test sso</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

7.2. 2.3 add application configuration
server.port=8081

spring.redis.host=192.168.157.128
7.2. 2.4 add HelloController

gulimall-test-sso-client/src/main/java/site/zhourui/gulimall/ssoclient/controller/HelloController.java

package site.zhourui.gulimall.ssoclient.controller;

/**
 * @author zr
 * @date 2021/12/4 16:27
 */

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RestTemplate;

import javax.servlet.http.HttpSession;
import java.util.ArrayList;
import java.util.List;

/**
 * Test single sign on
 */
@Controller
public class HelloController {


    /**
     * Access without login
     *
     * @return
     */
    @ResponseBody
    @GetMapping(value = "/hello")
    public String hello() {
        return "hello";
    }


    @GetMapping(value = "/employees")
    public String employees(Model model, HttpSession session, @RequestParam(value = "token", required = false) String token) {

        if (!StringUtils.isEmpty(token)) {
            RestTemplate restTemplate=new RestTemplate();
            ResponseEntity<String> forEntity = restTemplate.getForEntity("http://ssoserver.com:8080/userinfo?token=" + token, String.class);
            String body = forEntity.getBody();

            session.setAttribute("loginUser", body);
        }
        Object loginUser = session.getAttribute("loginUser");

        if (loginUser == null) {

            return "redirect:" + "http://ssoserver.com:8080/login.html"+"?redirect_url=http://client1.com:8081/employees";
        } else {


            List<String> emps = new ArrayList<>();

            emps.add("Zhang San");
            emps.add("Li Si");

            model.addAttribute("emps", emps);
            return "employees";
        }
    }

}

7.2. 2.5 new template employees html

gulimall-test-sso-client/src/main/resources/templates/employees.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Employee list</title>
</head>
<body>
<h1>welcome:[[${session.loginUser}]]</h1>
<ul>
    <li th:each="emp:${emps}">full name:[[${emp}]]</li>
</ul>
</body>
</html>

7.2. 3 single sign on client (clien1)

7.2. 3.1 creating steps

Change gulimall-testsso-client to gulimall-testsso-client2

7.2.3.2 pom documents

client2pom file

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>gulimall-test-sso-client2</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>gulimall-test-sso-client2</name>
    <description>client-test sso</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

7.2. 3.3 add application configuration
server.port=8082

spring.redis.host=192.168.157.128
7.2. 3.4 add HelloController

gulimall-test-sso-client2/src/main/java/com/example/gulimall/ssoclient2/controller/HelloController.java

package com.example.gulimall.ssoclient2.controller;

/**
 * @author zr
 * @date 2021/12/4 16:52
 */

import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RestTemplate;

import javax.servlet.http.HttpSession;
import java.util.ArrayList;
import java.util.List;

/**
 * Test single sign on
 */
@Controller
public class HelloController {


    /**
     * Access without login
     * @return
     */
    @ResponseBody
    @GetMapping(value = "/hello")
    public String hello() {
        return "hello";
    }


    @GetMapping(value = "/boss")
    public String employees(Model model, HttpSession session, @RequestParam(value = "token", required = false) String token) {

        if (!StringUtils.isEmpty(token)) {
            RestTemplate restTemplate=new RestTemplate();
            ResponseEntity<String> forEntity = restTemplate.getForEntity("http://ssoserver.com:8080/userinfo?token=" + token, String.class);
            String body = forEntity.getBody();

            session.setAttribute("loginUser", body);
        }
        Object loginUser = session.getAttribute("loginUser");

        if (loginUser == null) {

            return "redirect:" + "http://ssoserver.com:8080/login.html"+"?redirect_url=http://client2.com:8082/boss";
        } else {


            List<String> emps = new ArrayList<>();

            emps.add("Zhang San");
            emps.add("Li Si");

            model.addAttribute("emps", emps);
            return "employees";
        }
    }
}

7.2. 3.5 new template employees html

gulimall-test-sso-client2/src/main/resources/templates/employees.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Employee list</title>
</head>
<body>
<h1>welcome:[[${session.loginUser}]]</h1>
<ul>
    <li th:each="emp:${emps}">full name:[[${emp}]]</li>
</ul>
</body>
</html>

7.2. 4. Configure host

#Single sign on
127.0.0.1 ssoserver.com

127.0.0.1 client1.com

127.0.0.1 client2.com

7.2. 5 test

Start three services

Access the / employees path of client1 and directly jump to ssoserver without logging in Login page of con

http://client1.com:8081/employees

Access the / boss path of client2 and directly jump to ssoserver without logging in Login page of con

http://client2.com:8082/boss

stay http://ssoserver.com:8080/login.html?redirect_url=http://client1.com:8081/employees Login under

Login succeeded

Refresh http://ssoserver.com:8080/login.html?redirect_url=http://client2.com:8082/boss

Or access [employee list http://client2.com:8082/boss

7.2.6 summary

7.2.6.1 implementation 1: log in to the central server and return the token mechanism

1. Create central server
2. Create client
3. Visit http://client1.com:8081/employees =>Jump http://ssoserver.com:8080/login.html
4. With parameters
http://ssoserver.com:8080/login.html?redirect_url=http://client1.com:8081/employess
5. The value of the url is placed in the hidden field of the login page. After doLogin logs in, it will jump
1) Store the user information first redis
2) When sending the token back to the client, redirect:http://client1.com:8081/employess?token=uuid
6. Judge whether the token is returned. If the login is successful, the token is obtained
1) Judge whether there is a token [login]
2) Request user information from the central server according to the token

6.2.6.2 implementation 2: as long as one client logs in at the central server, other servers are also logged in

Implementation: using cookies, the browser caches the cookies of the central server, so every time you jump to the central server, the browser will record sso_token [used for SSO between multiple systems], and then when the client requests to jump to the central server, it will view the cookie, realize login free, and return the cookie value to the client as a token [the client can avoid login after obtaining the token]

6.2.6.3 implementation 3: the interface is provided by the certification center, which remotely calls and transmits token query data [the data is stored in redis, the same as Spring Session]

Then save the data to your own session. [in fact, spring session can also put a 0.0 into the session after redis checks the data]
Difference: the authentication service is added to solve the problem that cookies cannot cross domains. They all use cookies with the same domain name

Topics: Java Session Spring Boot Spring Cloud