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:
- Use code in exchange for AccessToken. Code can only be used once
- 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