咱們以前的用戶名/密碼的方式已經能夠正常返回oauth協議的 訪問令牌,也就是access_token.給咱們前端app,前端app拿着這個token就能夠訪問咱們的服務了,接下來咱們分析另外兩種認證方式。前端
2.App
App端,咱們服務器是沒有session的,這種狀況下會出現什麼問題呢? web
咱們先獲取短信驗證碼:redis
http://127.0.0.1:8088/code/sms?mobile=13012345678
獲取到短信驗證碼後登陸:spring
http://127.0.0.1:8088/authentication/mobile 參數是: mobile:13226595347 smsCode:875653
注意咱們此時不能使用Rest Client去發送短信登陸請求,由於Rest Client基於瀏覽器,而且把cookie攜帶到後端,可是咱們可使用其組織請求參數,而後在dos命令行請求。
組織參數:
數據庫
拷貝參數:
後端
在dos非瀏覽器窗口請求:(其在app端發送代碼是一致的,沒有cookie) 瀏覽器
一樣的數據,咱們使用web去發送就會返回200:
服務器
咱們使用短信驗證碼去登陸時候,咱們攜帶一個參數:deviceId;而後咱們會將此參數存儲在數據庫或者redis裏面。而後校驗時候也是帶着這個deviceId,此時咱們去從數據庫查詢。 cookie
spring-security-app模塊時候:
咱們在驗證碼存儲,驗證碼校驗和驗證碼移除都是從數據庫存儲中進行操做。而不是直接操做session。 session
spring-security-web模塊時候:
咱們在驗證碼存儲,驗證碼校驗和驗證碼移除都是對Session進行操做。
1.定義一個操做驗證碼的接口
在Spring-Security-Core工程的包:com.yxm.security.core.validate.code下面建立一個接口。
public interface ValidateCodeRepository { /** * 保存驗證碼 * @param request * @param code * @param validateCodeType */ void save(ServletWebRequest request, ValidateCode code, ValidateCodeType validateCodeType); /** * 獲取驗證碼 * @param request * @param validateCodeType * @return */ ValidateCode get(ServletWebRequest request, ValidateCodeType validateCodeType); /** * 移除驗證碼 * @param request * @param validateCodeType */ void remove(ServletWebRequest request, ValidateCodeType validateCodeType); }
2.spring-security-web定義使用session存儲方式實現ValidateCodeRepository接口的類:
@Component public class SessionValidateCodeRepository implements ValidateCodeRepository { /** * 驗證碼放入session時的前綴 */ String SESSION_KEY_PREFIX = "SESSION_KEY_FOR_CODE_"; /** * 操做session的工具類 */ private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy(); @Override public void save(ServletWebRequest request, ValidateCode code, ValidateCodeType validateCodeType) { sessionStrategy.setAttribute(request,getSessionKey(request, validateCodeType),code); } @Override public ValidateCode get(ServletWebRequest request, ValidateCodeType validateCodeType) { return (ValidateCode)sessionStrategy.getAttribute(request,getSessionKey(request,validateCodeType)); } @Override public void remove(ServletWebRequest request, ValidateCodeType validateCodeType) { sessionStrategy.removeAttribute(request,getSessionKey(request,validateCodeType)); } /** * 構建驗證碼放入session時的key * @param request * @return */ private String getSessionKey(ServletWebRequest request, ValidateCodeType validateCodeType) { return SESSION_KEY_PREFIX + validateCodeType.toString().toUpperCase(); } }
3.spring-security-app定義使用redis存儲方式實現ValidateCodeRepository接口的類:
@Component public class RedisValidateCodeRepository implements ValidateCodeRepository { @Autowired private RedisTemplate<Object, Object> redisTemplate; @Override public void save(ServletWebRequest request, ValidateCode code, ValidateCodeType type) { redisTemplate.opsForValue().set(buildKey(request, type), code, 30, TimeUnit.MINUTES); } @Override public ValidateCode get(ServletWebRequest request, ValidateCodeType type) { Object value = redisTemplate.opsForValue().get(buildKey(request, type)); if (value == null) { return null; } return (ValidateCode) value; } @Override public void remove(ServletWebRequest request, ValidateCodeType type) { redisTemplate.delete(buildKey(request, type)); } /** * @param request * @param type * @return */ private String buildKey(ServletWebRequest request, ValidateCodeType type) { String deviceId = request.getHeader("deviceId"); if (StringUtils.isBlank(deviceId)) { throw new ValidateCodeException("請在請求頭中攜帶deviceId參數"); } return "code:" + type.toString().toLowerCase() + ":" + deviceId; } }
當咱們的spring-security-demo項目依賴spring-security-app的時候就會走RedisValidateCodeRepository,當咱們的spring-security-demo項目依賴spring-security-web項目時候就會走SessionValidateCodeRepository實現。
public abstract class AbstractValidateCodeProcessor<C extends ValidateCode> implements ValidateCodeProcessor { /** * 操做session的工具類 */ private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy(); @Autowired private ValidateCodeRepository validateCodeRepository; /** * 收集系統中全部的 {@link ValidateCodeGenerator} 接口的實現。 */ @Autowired private Map<String, ValidateCodeGenerator> validateCodeGenerators; @Override public void create(ServletWebRequest request) throws Exception { //主幹邏輯3步驟:1.生成 2.保存 3.發送(發送是抽象方法,讓子類本身去實現) C validateCode = generate(request); save(request, validateCode); send(request, validateCode); } /** * 生成校驗碼:這裏涉及到Spring裏面的開發技巧;依賴查找 * * @param request * @return */ @SuppressWarnings("unchecked") private C generate(ServletWebRequest request) { /*String type = getProcessorType(request);*/ String type = getValidateCodeType(request).toString().toLowerCase(); String generatorName = type + ValidateCodeGenerator.class.getSimpleName(); ValidateCodeGenerator validateCodeGenerator = validateCodeGenerators.get(generatorName); if(validateCodeGenerator == null){ throw new ValidateCodeException("驗證碼生成器" + generatorName + "不存在"); } return (C) validateCodeGenerator.generate(request); } /** * 保存校驗碼 * @param request * @param validateCode */ private void save(ServletWebRequest request, C validateCode) { ValidateCode code = new ValidateCode(validateCode.getCode(),validateCode.getExpireTime()); validateCodeRepository.save(request,code,getValidateCodeType(request)); } /** * 構建驗證碼放入session時的key * @param request * @return */ private String getSessionKey(ServletWebRequest request) { return SESSION_KEY_PREFIX + getValidateCodeType(request).toString().toUpperCase(); } /** * 發送校驗碼,由子類實現 * @param request * @param validateCode * @throws Exception */ protected abstract void send(ServletWebRequest request, C validateCode) throws Exception; /** * 根據請求的url獲取校驗碼類型:根據請求後半段不一樣對應的校驗器也不一樣 * @param request * @return */ private ValidateCodeType getValidateCodeType(ServletWebRequest request){ String type = StringUtils.substringBefore(getClass().getSimpleName(), "CodeProcessor"); return ValidateCodeType.valueOf(type.toUpperCase()); } @SuppressWarnings("unchecked") @Override public void validate(ServletWebRequest request) { ValidateCodeType processorType = getValidateCodeType(request); String sessionKey = getSessionKey(request); //C codeInSession = (C) sessionStrategy.getAttribute(request, sessionKey); ValidateCode validateCode = validateCodeRepository.get(request, getValidateCodeType(request)); String codeInRequest; try { codeInRequest = ServletRequestUtils.getStringParameter(request.getRequest(), processorType.getParamNameOnValidate()); } catch (ServletRequestBindingException e) { throw new ValidateCodeException("獲取驗證碼的值失敗"); } if (StringUtils.isBlank(codeInRequest)) { throw new ValidateCodeException(processorType + "驗證碼的值不能爲空"); } if (validateCode == null) { throw new ValidateCodeException(processorType + "驗證碼不存在"); } if (validateCode.isExpried()) { sessionStrategy.removeAttribute(request, sessionKey); throw new ValidateCodeException(processorType + "驗證碼已過時"); } if (!StringUtils.equals(validateCode.getCode(), codeInRequest)) { throw new ValidateCodeException(processorType + "驗證碼不匹配"); } sessionStrategy.removeAttribute(request, sessionKey); } }
咱們在請求頭裏面添加字段:deviceId
查看redis中存儲:
登陸校驗: