后端控制登陸的Controller位置

對應前端文件


二維碼
來看看二維碼是怎么實現的,點擊圖片觸發了getCode這個函數

這個getCode函數被放在初始化函數里,可以看到具體的getCodeImg函數接收了一個img和uuid,所以我們要提供給前端的也就是uuid和img


調用的是這個函數,而這個函數調用了request的函數來自/utils/request,具體先不管這個函數是怎么實現的,大概意思就是指定一個方法和url就可以發送請求了

來到后端的代碼

大體步驟是獲取驗證碼實例--》去除小數情況--》緩存進redis保存,並且設置緩存時間--》返回驗證碼信息給前端
@ApiOperation("獲取用戶信息")
@GetMapping(value = "/info")
public ResponseEntity<Object> getUserInfo() {
return ResponseEntity.ok(SecurityUtils.getCurrentUser());
}
@ApiOperation("獲取驗證碼")
@AnonymousGetMapping(value = "/code")//支持匿名訪問的get請求
//ResponseEntity是可以添加HttpStatus狀態碼的HttpEntity的擴展類。被用於RestTemplate和Controller層方法
public ResponseEntity<Object> getCode() {
// 獲取運算的結果
Captcha captcha = loginProperties.getCaptcha();//得到一個驗證碼實例
String uuid = properties.getCodeKey() + IdUtil.simpleUUID();//uuid=properties對應的是jwt+獨立的uui
String captchaValue = captcha.text();
//當驗證碼類型為 arithmetic時且長度 >= 2 時,captcha.text()的結果有幾率為浮點型,如果二維碼是小數就舍棄掉
//ordinal():用於獲取某個枚舉對象的位置索引值
if (captcha.getCharType() - 1 == LoginCodeEnum.arithmetic.ordinal() && captchaValue.contains(".")) {
captchaValue = captchaValue.split("\\.")[0];
}
// 緩存進redis並且設置過期時間,loginProperties.getLoginCode().getExpiration()是兩分鍾
redisUtils.set(uuid, captchaValue, loginProperties.getLoginCode().getExpiration(), TimeUnit.MINUTES);
// 驗證碼信息
Map<String, Object> imgResult = new HashMap<String, Object>(2) {{
put("img", captcha.toBase64());//jwt用到的編碼方法
put("uuid", uuid);
}};
return ResponseEntity.ok(imgResult);//ok表示返回狀態碼是200
}
幾個函數分析
getCaptcha()



其他的登陸功能
前端會傳遞給后端的參數在data里面,上面的url和method是請求
其中的uuid是驗證碼的唯一識別碼(之前后端給前端發送驗證碼的時候同樣也往redis里存了一份,所以后續在后端會從redis緩存中拿出來校對)

后端的接收的對象里包括了這幾個參數

具體的登陸授權代碼
@ApiOperation("登錄授權")
@AnonymousPostMapping(value = "/login")//支持匿名訪問的post請求
//@Validated注解用於校驗從前端傳遞的參數
//@RequestBody將json格式的數據轉為java對象,也就是接收前端數據
public ResponseEntity<Object> login(@Validated @RequestBody AuthUserDto authUser, HttpServletRequest request) throws Exception {
// 密碼解密,使用的私鑰具體在配置文件可以看到
String password = RsaUtils.decryptByPrivateKey(RsaProperties.privateKey, authUser.getPassword());
// 從redis緩存中拿到驗證碼
String code = (String) redisUtils.get(authUser.getUuid());
// 清除驗證碼緩存
redisUtils.del(authUser.getUuid());
//判斷驗證碼
if (StringUtils.isBlank(code)) {
throw new BadRequestException("驗證碼不存在或已過期");
}
if (StringUtils.isBlank(authUser.getCode()) || !authUser.getCode().equalsIgnoreCase(code)) {//驗證碼不正確
throw new BadRequestException("驗證碼錯誤");
}
//security相關的內容
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(authUser.getUsername(), password);
Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
String token = tokenProvider.createToken(authentication);
//jwtToken規范,不用去服務器端也保存一份token進行校對,只要是沒有過期,就可以使用
//獲得一個登陸的主題,這里封裝了所有的信息
final JwtUserDto jwtUserDto = (JwtUserDto) authentication.getPrincipal();
// 保存在線信息,為了看目前有多少人在線
onlineUserService.save(jwtUserDto, token, request);
// 返回 token 與 用戶信息
Map<String, Object> authInfo = new HashMap<String, Object>(2) {{
put("token", properties.getTokenStartWith() + token);
put("user", jwtUserDto);
}};
if (loginProperties.isSingleLogin()) {
//踢掉之前已經登錄的token
onlineUserService.checkLoginOnUser(authUser.getUsername(), token);
}
return ResponseEntity.ok(authInfo);
}
RsaProperties.privateKey


回顧整個登陸流程:
進入登陸頁面,自動獲取驗證碼,每次生成的驗證碼都會不一樣
后端生成驗證碼,通過(uuid,值)的鍵值對放入redis並且設置過期時間為,然后返回uuid,圖片,url給前端
前端通過訪問auth/code獲取后端生成的二維碼
用戶填寫用戶名,密碼,驗證碼,點擊登陸
前端用預設的公匙進行密碼數據加密,發送到后端
后端接收數據后,首先用預設的私匙進行解密,然后從redis中獲取驗證碼,判斷是否正確或者過期,然后清除驗證碼
如果用戶的密碼和驗證碼正確就生成token,並保存在線信息,然后返回用戶信息和token給前端
