Spring Security技術棧開發企業級認證與授權(十一)開發簡訊驗證碼登入
簡訊登入
也是一種常見的登入方式,但是簡訊登入
的方式並沒有整合到Spring Security
中,所以往往還需要我們自己開發簡訊登入
邏輯,將其整合到Spring Security
中,使用Spring Security
來進行校驗。本文將介紹開發簡訊登入
的方法,並將其加入到Spring Security
的驗證邏輯中。
一、簡訊登入邏輯設計以及圖片驗證碼程式碼重構
在前面一篇部落格《Spring Security技術棧開發企業級認證與授權(九)開發圖形驗證碼介面》中介紹瞭如何開發圖形驗證碼介面,並將驗證邏輯加入到Spring Security
中,這裡將介紹如何開發簡訊驗證,兩者之間有許多非常類似的程式碼,所以在設計簡訊登入程式碼的時候,將它們進一步整合、抽象與重構。
圖形驗證碼和簡訊驗證碼重構後的結構圖如下所示:
ValidateCodeController
是這個驗證碼介面體系的入口,它主要抽象出可以同時接收兩種驗證碼的請求方式,使用請求型別type
來進行區分。ValidateCodeProcessor
是一個介面,專門用來生成驗證碼,並將驗證碼存入到session
中,最後將驗證碼傳送出去,傳送的方式有兩種,圖片驗證碼是寫回到response
中,簡訊驗證碼呼叫第三方簡訊服務平臺的API
進行傳送,比如阿里巴巴的簡訊服務。AbstractValidateCodeProcessor
是一個抽象類,它實現了ValidateCodeProcessor
介面,並提供了抽象方法send
方法,因為圖片的傳送方法和簡訊的傳送方法具體實現不同,所以得使用具體的方法進行傳送。這裡面的create
ValidateCodeGenerator
也是一個介面,它有兩個實現類,分別是ImageCodeGenerator
和SmsCodeGenerator
,它們具體是完成了程式碼的生成邏輯。ImageCodeProcessor和SmsCodeProcessor
是專門用來重寫send
方法的一個處理器,展示了兩種驗證碼的不同傳送方式。
1)將簡訊驗證碼和圖形驗證碼的相同屬性進行抽取
簡訊驗證碼和圖形驗證後包含屬性有code
和expireTime
,簡訊驗證碼只有這兩個屬性,而圖形驗證碼還多一個BufferedImage
例項物件屬性,所以將共同屬性進行抽取,抽取為ValidateCode
package com.lemon.security.core.validate.code;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.time.LocalDateTime;
/**
* @author lemon
* @date 2018/4/17 下午8:13
*/
@Data
@AllArgsConstructor
public class ValidateCode {
private String code;
private LocalDateTime expireTime;
public boolean isExpired() {
return LocalDateTime.now().isAfter(expireTime);
}
}
抽取後的圖片驗證碼實體類為:
package com.lemon.security.core.validate.code.image;
import com.lemon.security.core.validate.code.ValidateCode;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.awt.image.BufferedImage;
import java.time.LocalDateTime;
/**
* 圖片驗證碼實體類
*
* @author lemon
* @date 2018/4/6 下午4:34
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class ImageCode extends ValidateCode {
private BufferedImage image;
public ImageCode(BufferedImage image, String code, LocalDateTime expireTime) {
super(code, expireTime);
this.image = image;
}
public ImageCode(BufferedImage image, String code, int expireIn) {
super(code, LocalDateTime.now().plusSeconds(expireIn));
this.image = image;
}
}
圖片驗證碼實體類繼承了ValidateCode
類,那麼在寫一個簡訊驗證碼實體類:
package com.lemon.security.core.validate.code.sms;
import com.lemon.security.core.validate.code.ValidateCode;
import java.time.LocalDateTime;
/**
* 簡訊驗證碼實體類
*
* @author lemon
* @date 2018/4/17 下午8:18
*/
public class SmsCode extends ValidateCode {
public SmsCode(String code, LocalDateTime expireTime) {
super(code, expireTime);
}
public SmsCode(String code, int expireIn) {
super(code, LocalDateTime.now().plusSeconds(expireIn));
}
}
簡訊驗證碼只需要繼承ValidateCode
即可,沒有其他多餘的屬性增加。
對於配置的程式碼,也是可以進一步進行重構,簡訊驗證碼和圖片驗證碼在配置上有幾個重複的屬性,比如:驗證碼長度length
,驗證碼過期時間expireIn
,以及需要新增簡訊驗證的url地址。ImageCodeProperties
和SmsCodeProperties
共同抽取出CodeProperties
,程式碼如下:
- CodeProperties
package com.lemon.security.core.properties;
import lombok.Data;
/**
* @author lemon
* @date 2018/4/17 下午9:11
*/
@Data
public class CodeProperties {
/**
* 驗證碼長度
*/
private int length = 6;
/**
* 驗證碼過期時間
*/
private int expireIn = 60;
/**
* 需要驗證碼的url字串,用英文逗號隔開
*/
private String url;
}
- ImageCodeProperties
package com.lemon.security.core.properties;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 圖形驗證碼的預設配置
*
* @author lemon
* @date 2018/4/6 下午9:42
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class ImageCodeProperties extends CodeProperties {
public ImageCodeProperties() {
setLength(4);
}
/**
* 驗證碼寬度
*/
private int width = 67;
/**
* 驗證碼高度
*/
private int height = 23;
}
- SmsCodeProperties
package com.lemon.security.core.properties;
/**
* @author lemon
* @date 2018/4/17 下午9:13
*/
public class SmsCodeProperties extends CodeProperties {
}
為了實現配置資訊可以由使用者自定義配置,還需要將其加入到讀取配置檔案的配置類中,建立一個ValidateCodeProperties
類,將圖片驗證碼和簡訊驗證碼例項物件作為屬性配置進去,程式碼如下:
package com.lemon.security.core.properties;
import lombok.Data;
/**
* 封裝多個配置的類
*
* @author lemon
* @date 2018/4/6 下午9:45
*/
@Data
public class ValidateCodeProperties {
private ImageCodeProperties image = new ImageCodeProperties();
private SmsCodeProperties sms = new SmsCodeProperties();
}
再將ValidateCodeProperties
封裝到整個安全配置類SecurityProperties
中,具體的程式碼如下:
package com.lemon.security.core.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author lemon
* @date 2018/4/5 下午3:08
*/
@Data
@ConfigurationProperties(prefix = "com.lemon.security")
public class SecurityProperties {
private BrowserProperties browser = new BrowserProperties();
private ValidateCodeProperties code = new ValidateCodeProperties();
}
這個時候就可以讀取到使用者自定義的配置檔案application.properties
或者application.yml
中的配置。關於驗證碼的配置方式的application.properties
檔案內容形式如下,application.yml
類似:
com.lemon.security.code.image.length=4
com.lemon.security.code.sms.length=6
2)編寫ValidateCodeProcessor介面
ValidateCodeProcessor
介面主要是完成了驗證碼的生成、儲存與傳送的一整套流程,介面的主要設計如下所示:
package com.lemon.security.core.validate.code;
import org.springframework.web.context.request.ServletWebRequest;
import javax.servlet.http.HttpServletRequest;
/**
* 驗證碼生成介面
*
* @author lemon
* @date 2018/4/17 下午9:46
*/
public interface ValidateCodeProcessor {
String SESSION_KEY_PREFIX = "SESSION_KEY_FOR_CODE_";
String CODE_PROCESSOR = "CodeProcessor";
/**
* 生成驗證碼
*
* @param request 封裝了 {@link HttpServletRequest} 例項物件的請求
* @throws Exception 異常
*/
void create(ServletWebRequest request) throws Exception;
}
由於圖片驗證碼和簡訊驗證碼的生成和儲存、傳送等流程是固定的,只是在生成兩種驗證碼的時候分別呼叫各自的生成方法,儲存到session
中是完全一致的,最後的傳送各有不同,圖片驗證碼是寫到response
中,而簡訊驗證碼是呼叫第三方簡訊傳送平臺的SDK
來實現傳送功能。所以這裡寫一個抽象類來實現ValidateCodeProcessor
介面。
package com.lemon.security.core.validate.code.impl;
import com.lemon.security.core.validate.code.ValidateCodeGenerator;
import com.lemon.security.core.validate.code.ValidateCodeProcessor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.social.connect.web.HttpSessionSessionStrategy;
import org.springframework.social.connect.web.SessionStrategy;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.ServletWebRequest;
import java.util.Map;
/**
* @author lemon
* @date 2018/4/17 下午9:56
*/
@Component
public abstract class AbstractValidateCodeProcessor<C> implements ValidateCodeProcessor {
private static final String SEPARATOR = "/code/";
/**
* 操作session的工具集
*/
private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();
/**
* 這是Spring的一個特性,就是在專案啟動的時候會自動收集系統中 {@link ValidateCodeGenerator} 介面的實現類物件
*/
@Autowired
private Map<String, ValidateCodeGenerator> validateCodeGeneratorMap;
@Override
public void create(ServletWebRequest request) throws Exception {
C validateCode = generate(request);
save(request, validateCode);
send(request, validateCode);
}
/**
* 生成驗證碼
*
* @param request ServletWebRequest例項物件
* @return 驗證碼例項物件
*/
@SuppressWarnings("unchecked")
private C generate(ServletWebRequest request) {
String type = getProcessorType(request);
ValidateCodeGenerator validateCodeGenerator = validateCodeGeneratorMap.get(type.concat(ValidateCodeGenerator.CODE_GENERATOR));
return (C) validateCodeGenerator.generate(request);
}
/**
* 儲存驗證碼到session中
*
* @param request ServletWebRequest例項物件
* @param validateCode 驗證碼
*/
private void save(ServletWebRequest request, C validateCode) {
sessionStrategy.setAttribute(request, SESSION_KEY_PREFIX.concat(getProcessorType(request).toUpperCase()), validateCode);
}
/**
* 傳送驗證碼
*
* @param request ServletWebRequest例項物件
* @param validateCode 驗證碼
* @throws Exception 異常
*/
protected abstract void send(ServletWebRequest request, C validateCode) throws Exception;
/**
* 獲取請求URL中具體請求的驗證碼型別
*
* @param request ServletWebRequest例項物件
* @return 驗證碼型別
*/
private String getProcessorType(ServletWebRequest request) {
// 獲取URI分割後的第二個片段
return StringUtils.substringAfter(request.getRequest().getRequestURI(), SEPARATOR);
}
}
對上面的程式碼進行解釋:
首先將驗證碼生成介面
ValidateCodeGenerator
的實現類物件注入到Map
集合中,這個是Spring
的一個特性。抽象類中實現了
ValidateCodeProcessor
介面的create
方法,從程式碼中可以看出,它主要是完成了驗證碼的建立、儲存和傳送的功能。generate
方法根據傳入的不同泛型而生成了特定的驗證碼,而泛型的傳入是通過AbstractValidateCodeProcessor
的子類來實現的。save
方法是將生成的驗證碼例項物件存入到session
中,兩種驗證碼的儲存方式一致,所以程式碼也是通用的。send
方法一個抽象方法,分別由ImageCodeProcessor
和SmsCodeProcessor
來具體實現,也是根據泛型來判斷具體呼叫哪一個具體的實現類的send
方法。
3)編寫驗證碼的生成介面
package com.lemon.security.core.validate.code;
import org.springframework.web.context.request.ServletWebRequest;
/**
* @author lemon
* @date 2018/4/7 上午11:06
*/
public interface ValidateCodeGenerator {
String CODE_GENERATOR = "CodeGenerator";
/**
* 生成圖片驗證碼
*
* @param request 請求
* @return ImageCode例項物件
*/
ValidateCode generate(ServletWebRequest request);
}
它有兩個具體的實現,分別是ImageCodeGenerator
和SmsCodeGenerator
,具體程式碼如下:
package com.lemon.security.core.validate.code.image;
import com.lemon.security.core.properties.SecurityProperties;
import com.lemon.security.core.validate.code.ValidateCodeGenerator;
import lombok.Data;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.context.request.ServletWebRequest;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;
/**
* 圖片驗證碼生成器
*
* @author lemon
* @date 2018/4/7 上午11:09
*/
@Data
public class ImageCodeGenerator implements ValidateCodeGenerator {
private static final String IMAGE_WIDTH_NAME = "width";
private static final String IMAGE_HEIGHT_NAME = "height";
private static final Integer MAX_COLOR_VALUE = 255;
private SecurityProperties securityProperties;
@Override
public ImageCode generate(ServletWebRequest request) {
int width = ServletRequestUtils.getIntParameter(request.getRequest(), IMAGE_WIDTH_NAME, securityProperties.getCode().getImage().getWidth());
int height = ServletRequestUtils.getIntParameter(request.getRequest(), IMAGE_HEIGHT_NAME, securityProperties.getCode().getImage().getHeight());
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics g = image.getGraphics();
Random random = new Random();
// 生成畫布
g.setColor(getRandColor(200, 250));
g.fillRect(0, 0, width, height);
g.setFont(new Font("Times New Roman", Font.ITALIC, 20));
g.setColor(getRandColor(160, 200));
for (int i = 0; i < 155; i++) {
int x = random.nextInt(width);
int y = random.nextInt(height);
int xl = random.nextInt(12);
int yl = random.nextInt(12);
g.drawLine(x, y, x + xl, y + yl);
}
// 生成數字驗證碼
StringBuilder sRand = new StringBuilder();
for (int i = 0; i < securityProperties.getCode().getImage().getLength(); i++) {
String rand = String.valueOf(random.nextInt(10));
sRand.append(rand);
g.setColor(new Color(20 + random.nextInt(110), 20 + random.nextInt(110), 20 + random.nextInt(110)));
g.drawString(rand, 13 * i + 6, 16);
}
g.dispose();
return new ImageCode(image, sRand.toString(), securityProperties.getCode().getImage().getExpireIn());
}
/**
* 生成隨機背景條紋
*
* @param fc 前景色
* @param bc 背景色
* @return RGB顏色
*/
private Color getRandColor(int fc, int bc) {
Random random = new Random();
if (fc > MAX_COLOR_VALUE) {
fc = MAX_COLOR_VALUE;
}
if (bc > MAX_COLOR_VALUE) {
bc = MAX_COLOR_VALUE;
}
int r = fc + random.nextInt(bc - fc);
int g = fc + random.nextInt(bc - fc);
int b = fc + random.nextInt(bc - fc);
return new Color(r, g, b);
}
}
package com.lemon.security.core.validate.code.sms;
import com.lemon.security.core.properties.SecurityProperties;
import com.lemon.security.core.validate.code.ValidateCodeGenerator;
import lombok.Data;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.ServletWebRequest;
/**
* 簡訊驗證碼生成器
*
* @author lemon
* @date 2018/4/7 上午11:09
*/
@Data
@Component("smsCodeGenerator")
public class SmsCodeGenerator implements ValidateCodeGenerator {
private final SecurityProperties securityProperties;
@Autowired
public SmsCodeGenerator(SecurityProperties securityProperties) {
this.securityProperties = securityProperties;
}
@Override
public SmsCode generate(ServletWebRequest request) {
String code = RandomStringUtils.randomNumeric(securityProperties.getCode().getSms().getLength());
return new SmsCode(code, securityProperties.getCode().getSms().getExpireIn());
}
}
兩個實現類完成了具體的驗證碼生成邏輯,根據傳入的泛型然後進行強轉之後便可呼叫各自的生成邏輯方法。
4)編寫驗證碼的傳送邏輯類
不同的驗證碼的傳送邏輯是不一樣的,圖片驗證碼是寫回response
中,而簡訊驗證碼是將驗證碼傳送到指定手機號的手機上。
圖片驗證碼的傳送邏輯類的程式碼如下:
package com.lemon.security.core.validate.code.image;
import com.lemon.security.core.validate.code.impl.AbstractValidateCodeProcessor;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.ServletWebRequest;
import javax.imageio.ImageIO;
/**
* @author lemon
* @date 2018/4/17 下午11:37
*/
@Component("imageCodeProcessor")
public class ImageCodeProcessor extends AbstractValidateCodeProcessor<ImageCode> {
private static final String FORMAT_NAME = "JPEG";
/**
* 傳送圖形驗證碼,將其寫到相應中
*
* @param request ServletWebRequest例項物件
* @param imageCode 驗證碼
* @throws Exception 異常
*/
@Override
protected void send(ServletWebRequest request, ImageCode imageCode) throws Exception {
ImageIO.write(imageCode.getImage(), FORMAT_NAME, request.getResponse().getOutputStream());
}
}
簡訊驗證碼的傳送邏輯類的程式碼如下:
package com.lemon.security.core.validate.code.sms;
import com.lemon.security.core.validate.code.impl.AbstractValidateCodeProcessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.context.request.ServletWebRequest;
/**
* @author lemon
* @date 2018/4/17 下午11:41
*/
@Component("smsCodeProcessor")
public class SmsCodeProcessor extends AbstractValidateCodeProcessor<SmsCode> {
private static final String SMS_CODE_PARAM_NAME = "mobile";
private final SmsCodeSender smsCodeSender;
@Autowired
public SmsCodeProcessor(SmsCodeSender smsCodeSender) {
this.smsCodeSender = smsCodeSender;
}
@Override
protected void send(ServletWebRequest request, SmsCode smsCode) throws Exception {
String mobile = ServletRequestUtils.getRequiredStringParameter(request.getRequest(), SMS_CODE_PARAM_NAME);
smsCodeSender.send(mobile, smsCode.getCode());
}
}
注意到上面的簡訊傳送呼叫了SmsCodeSender
的實現類,因此和圖片的傳送有所區別。而在設計中,SmsCodeSender
有一個預設的實現,也就是自帶的簡訊傳送方式,但是在實際的開發過程中,往往需要開發者覆蓋自帶的傳送邏輯,而是採用自定義的傳送邏輯,所以需要預設的簡訊傳送方式是可以被覆蓋的。SmsCodeSender
介面程式碼如下:
package com.lemon.security.core.validate.code.sms;
/**
* 簡訊驗證傳送介面
*
* @author lemon
* @date 2018/4/17 下午8:25
*/
public interface SmsCodeSender {
/**
* 簡訊驗證碼傳送介面
*
* @param mobile 手機號
* @param code 驗證碼
*/
void send(String mobile, String code);
}
它的預設實現類程式碼啊如下:
package com.lemon.security.core.validate.code.sms;
/**
* 預設的簡訊傳送邏輯
*
* @author lemon
* @date 2018/4/17 下午8:26
*/
public class DefaultSmsCodeSender implements SmsCodeSender {
@Override
public void send(String mobile, String code) {
// 這裡僅僅寫個列印,具體邏輯一般都是呼叫第三方介面傳送簡訊
System.out.println("向手機號為:" + mobile + "的使用者傳送驗證碼:" + code);
}
}
注意到上面的程式碼並沒有使用@Component
註解來標註為一個Spring
的Bean
,這麼做不是說它不由Spring
管理,而是需要配置的可以被覆蓋的形式,所以在ValidateCodeBeanConfig
類中加上配置其為Spring Bean
的程式碼,為了體現程式碼的完整性,這裡貼出ValidateCodeBeanConfig
類中的所有程式碼。
package com.lemon.security.core.validate.code;
import com.lemon.security.core.properties.SecurityProperties;
import com.lemon.security.core.validate.code.image.ImageCodeGenerator;
import com.lemon.security.core.validate.code.sms.DefaultSmsCodeSender;
import com.lemon.security.core.validate.code.sms.SmsCodeSender;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author lemon
* @date 2018/4/7 上午11:22
*/
@Configuration
public class ValidateCodeBeanConfig {
private final SecurityProperties securityProperties;
@Autowired
public ValidateCodeBeanConfig(SecurityProperties securityProperties) {
this.securityProperties = securityProperties;
}
@Bean
@ConditionalOnMissingBean(name = "imageCodeGenerator")
public ValidateCodeGenerator imageCodeGenerator() {
ImageCodeGenerator imageCodeGenerator = new ImageCodeGenerator();
imageCodeGenerator.setSecurityProperties(securityProperties);
return imageCodeGenerator;
}
@Bean
@ConditionalOnMissingBean(SmsCodeSender.class)
public SmsCodeSender smsCodeSender() {
return new DefaultSmsCodeSender();
}
}
在最後一個Bean
的配置中,使用了@ConditionalOnMissingBean
註解,這裡是告訴Spring
,如果上下文環境中沒有SmsCodeSender
介面的實現類物件,那麼就執行下面的方法進行預設的Bean
建立。所以對於使用者自定義方式,只需要寫一個類實現SmsCodeSender
介面,並將其標註為Spring
的Bean
即可,就可以覆蓋自帶的簡訊傳送邏輯。如果一開始使用@Component
註解來進行標註了,那就無法獲得這樣自定義的效果。
至此,我們已經完成了對文章開始處的邏輯分析的所有程式碼,接下來將程式碼整合到Spring Security
中,讓其能在Spring Security
中得到驗證,從而實現簡訊的驗證功能。
二、將簡訊驗證碼登入方式整合到Spring Security中
相關推薦
Spring Security技術棧開發企業級認證與授權(十一)開發簡訊驗證碼登入
簡訊登入也是一種常見的登入方式,但是簡訊登入的方式並沒有整合到Spring Security中,所以往往還需要我們自己開發簡訊登入邏輯,將其整合到Spring Security中,使用Spring Security來進行校驗。本文將介紹開發簡訊登入的方法,
Spring Security技術棧開發企業級認證與授權(五)使用Filter、Interceptor和AOP攔截REST服務
一般情況,在訪問RESTful風格的API之前,可以對訪問行為進行攔截,並做一些邏輯處理,本文主要介紹三種攔截方式,分別是:過濾器Filter、攔截器Interceptor以及面向切面的攔截方式AOP。 一、使用過濾器Filter進行攔截 使用過
Spring Security技術棧開發企業級認證與授權(九)開發圖形驗證碼介面
在設計登入模組的時候,圖形驗證碼基本上都是標配,本篇部落格重點介紹開發可重用的圖形驗證碼介面,該介面支援使用者自定義配置,比如驗證碼的長度、驗證碼圖形的寬度和高度等資訊。 本文的目標是開發一個圖形驗證碼介面,該驗證碼支援使用者自定義長度,以及生成圖片後
Spring Security技術棧開發企業級認證與授權(八)Spring Security的基本執行原理與個性化登入實現
正如你可能知道的兩個應用程式的兩個主要區域是“認證”和“授權”(或者訪問控制)。這兩個主要區域是Spring Security的兩個目標。“認證”,是建立一個他宣告的主題的過程(一個“主體”一般是指使用者,裝置或一些可以在你的應用程式中執行動作的其他系統)
Spring Security技術棧開發企業級認證與授權(七)使用Swagger自動生成API文件
由於Spring Boot能夠快速開發、便捷部署等特性,相信有很大一部分Spring Boot的使用者會用來構建RESTful API。而我們構建RESTful API的目的通常都是由於多終端的原因,這些終端會共用很多底層業務邏輯,因此我們會抽象出這樣一層
Spring Security技術棧開發企業級認證與授權(一)環境搭建
Spring Security是一個能夠為基於Spring的企業應用系統提供宣告式的安全訪問控制解決方案的安全框架。它提供了一組可以在Spring應用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反轉Inversion of Contr
Spring Security技術棧開發企業級認證與授權
iyu 復雜 sha 日誌 開發app 一個 核心概念 並發 自動 Spring Security技術棧開發企業級認證與授權網盤地址:https://pan.baidu.com/s/1mj8u6JQ 密碼: 92rp備用地址(騰訊微雲):https://share.weiy
Spring Security技術棧開發企業級認證與授權 Spring Security開發安全的REST服務
第1章 課程導學 介紹課程內容、課程特點,使用的主要技術棧,以及學習課程所需的前置知識 1-1 導學 第2章 開始開發 安裝開發工具,介紹專案程式碼結構並搭建,基本的依賴和引數設定,開發hello world 2-1 開發環境安裝 2-2 程式碼結構介紹 2-3
軟體開發中的理想與現實(十一)——夠用就好
2月21日,專案正式開始第二天。依照昨天設計的框架和介面,我們開始實現這些功能,不過似乎大家的進展都比較慢,特別是XophiiX,似乎他陷入了困境之中。具體是什麼問題呢?請看下面的介面定義: class CReader { // ... pub
使用O2OA二次開發搭建企業辦公平臺(十一)組織管理篇:關於系統角色管理
本部落格為O2OA系列教程、O2OA使用手冊,教程目錄和各章節天梯將在連載完後更新。 使用O2OA二次開發搭建企業辦公平臺(一)平臺部署篇:平臺下載和部署 使用O2OA二次開發搭建企業辦公平臺(二)平臺部署篇:埠衝突和伺服器埠配置 使用O2OA二次開發搭建企業辦公平臺(三)平臺部署篇:使用外部資料庫
計算機組成與設計(十一)—— 儲存層次結構(上)
儲存層次結構概況 這是我們非常熟悉的馮·諾依曼計算機結構, 那這其中哪些部件和儲存功能有關呢? 儲存器和外部記錄介質肯定具有儲存功能,另外還有一個自帶儲存功能的運算器,為了描述方便,我們把這些部件統稱為儲存器。那麼我們看一下計算機中對儲存器有哪些要求 ? 1、首先儲存器當
演算法設計與分析(十一)
300. Longest Increasing Subsequence Given an unsorted array of integers, find the length of longest increasing subsequence. Example
Cocos2d—X遊戲開發之CCTableView詳解(十一)(附原始碼)
本來很早就想寫關於CCTableView的文章,但是在基本功能實現之後呢,專案需求增加導致對這個控制元件的研究必須更加深入一點. 好的,現在開始介紹一下這個控制元件,在Cocos2d—X引擎中,這是一個仿製iOS裡面的NSTableView的一個控制元件。 S1,使用這個
百度大腦人臉識別深度驗證與思考(十一)之動態實時
前言 我已經厭倦了靜態圖片的識別,那些技術對我已經沒有了挑戰性。今天我們就來看看動態實時的深度識別表現如何。 攝像頭 央視 我們直接採集央視rtmp推流地址的視訊,直接進行人臉識別和即時
整合SpringMVC框架+Mybatis框架開發人力資源管理系統(十一)
實現使用者管理中的新增、刪除、修改功能 新增功能的實現 @RequestMapping(value="/insertUser") public ModelAndView addUser(@ModelAttribute User user,ModelAndView mv){
android 開發零起步學習筆記(十一):介面切換+幾種常用介面切換效果
兩種方法實現介面的切換: 方法1、layout切換(通過setContentView切換layout) 方法2、Activity切換 方法3、Android之fragment點選切換和滑動切換 方法1、layout切換(通過setContentView切換la
【連載】物聯網全棧教程-從雲端到裝置(十一)---呼叫阿里雲API,獲取物的屬性。
物聯網全棧教程-從雲端到裝置(十一)一千千萬萬的物聯網裝置通過ALink協議接入到了雲端,它們不斷地按照ALink協議的規範向雲端彙報資料,同時也一直在等待伺服器下發一些資料並處理這些資料。關於物聯網裝置端如何搞定這些資料,零妖老哥會在下一篇文章中以一個型號叫做STM32F1
Linux程序啟動過程分析do_execve(可執行程式的載入和執行)---Linux程序的管理與排程(十一)
execve系統呼叫 execve系統呼叫 我們前面提到了, fork, vfork等複製出來的程序是父程序的一個副本, 那麼如何我們想載入新的程式, 可以通過execve來載入和啟動新的程式。 x86架構下, 其實還實現了一個
資料結構與演算法(十一)Trie字典樹
本文主要包括以下內容: Trie字典樹的基本概念 Trie字典樹的基本操作 插入 查詢 字首查詢 刪除 基於連結串列的Trie字典樹 基於Trie的Set效能對比 LeetCode相關線段樹的問題 LeetCode第208號問題 LeetCode第211
物聯網全棧教程-從雲端到裝置(十一)
一千千萬萬的物聯網裝置通過ALink協議接入到了雲端,它們不斷地按照ALink協議的規範向雲端彙報資料,同時也一直在等待伺服器下發一些資料並處理這些資料。關於物聯網裝置端如何搞定這些資料,零妖老哥會在下一篇文章中以一個型號叫做STM32F103的微控制器為例子進行硬體電路和程