前置知识:
前端登录加密看用户登录
PasswordEncoder加密看PasswordEncoder详解
项目中因为要判断用户登录密码是否正确,通过输入错误次数锁住用户
1.后端配置rsa私钥
#密码加密传输,前端公钥加密,后端私钥解密
rsa:
private_key: xxxx
2. 读取配置文件
/*
* Copyright 2019-2020 Zheng Jie
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.njry.config;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* @author Zheng Jie
* @website https://eladmin.vip
* @description
* @date 2020-05-18
**/
@Data
@Component
public class RsaProperties {
public static String privateKey;
@Value("${rsa.private_key}")
public void setPrivateKey(String privateKey) {
RsaProperties.privateKey = privateKey;
}
}
3. Rsa 工具类
package com.njry.utils;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
/**
* @author https://www.cnblogs.com/nihaorz/p/10690643.html
* @description Rsa 工具类,公钥私钥生成,加解密
* @date 2020-05-18
**/
public class RsaUtils {
private static final String SRC = "123456";
public static void main(String[] args) throws Exception {
System.out.println("\n");
RsaKeyPair keyPair = generateKeyPair();
System.out.println("公钥:" + keyPair.getPublicKey());
System.out.println("私钥:" + keyPair.getPrivateKey());
System.out.println("\n");
test1(keyPair);
System.out.println("\n");
test2(keyPair);
System.out.println("\n");
}
/**
* 公钥加密私钥解密
*/
private static void test1(RsaKeyPair keyPair) throws Exception {
System.out.println("***************** 公钥加密私钥解密开始 *****************");
String text1 = encryptByPublicKey(keyPair.getPublicKey(), RsaUtils.SRC);
String text2 = decryptByPrivateKey(keyPair.getPrivateKey(), text1);
System.out.println("加密前:" + RsaUtils.SRC);
System.out.println("加密后:" + text1);
System.out.println("解密后:" + text2);
if (RsaUtils.SRC.equals(text2)) {
System.out.println("解密字符串和原始字符串一致,解密成功");
} else {
System.out.println("解密字符串和原始字符串不一致,解密失败");
}
System.out.println("***************** 公钥加密私钥解密结束 *****************");
}
/**
* 私钥加密公钥解密
* @throws Exception /
*/
private static void test2(RsaKeyPair keyPair) throws Exception {
System.out.println("***************** 私钥加密公钥解密开始 *****************");
String text1 = encryptByPrivateKey(keyPair.getPrivateKey(), RsaUtils.SRC);
String text2 = decryptByPublicKey(keyPair.getPublicKey(), text1);
System.out.println("加密前:" + RsaUtils.SRC);
System.out.println("加密后:" + text1);
System.out.println("解密后:" + text2);
if (RsaUtils.SRC.equals(text2)) {
System.out.println("解密字符串和原始字符串一致,解密成功");
} else {
System.out.println("解密字符串和原始字符串不一致,解密失败");
}
System.out.println("***************** 私钥加密公钥解密结束 *****************");
}
/**
* 公钥解密
*
* @param publicKeyText 公钥
* @param text 待解密的信息
* @return /
* @throws Exception /
*/
public static String decryptByPublicKey(String publicKeyText, String text) throws Exception {
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyText));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, publicKey);
byte[] result = doLongerCipherFinal(Cipher.DECRYPT_MODE, cipher, Base64.decodeBase64(text));
return new String(result);
}
/**
* 私钥加密
*
* @param privateKeyText 私钥
* @param text 待加密的信息
* @return /
* @throws Exception /
*/
public static String encryptByPrivateKey(String privateKeyText, String text) throws Exception {
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyText));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
byte[] result = doLongerCipherFinal(Cipher.ENCRYPT_MODE, cipher, text.getBytes());
return Base64.encodeBase64String(result);
}
/**
* 私钥解密
*
* @param privateKeyText 私钥
* @param text 待解密的文本
* @return /
* @throws Exception /
*/
public static String decryptByPrivateKey(String privateKeyText, String text) throws Exception {
PKCS8EncodedKeySpec pkcs8EncodedKeySpec5 = new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKeyText));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec5);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] result = doLongerCipherFinal(Cipher.DECRYPT_MODE, cipher, Base64.decodeBase64(text));
return new String(result);
}
/**
* 公钥加密
*
* @param publicKeyText 公钥
* @param text 待加密的文本
* @return /
*/
public static String encryptByPublicKey(String publicKeyText, String text) throws Exception {
X509EncodedKeySpec x509EncodedKeySpec2 = new X509EncodedKeySpec(Base64.decodeBase64(publicKeyText));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec2);
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] result = doLongerCipherFinal(Cipher.ENCRYPT_MODE, cipher, text.getBytes());
return Base64.encodeBase64String(result);
}
private static byte[] doLongerCipherFinal(int opMode,Cipher cipher, byte[] source) throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
if (opMode == Cipher.DECRYPT_MODE) {
out.write(cipher.doFinal(source));
} else {
int offset = 0;
int totalSize = source.length;
while (totalSize - offset > 0) {
int size = Math.min(cipher.getOutputSize(0) - 11, totalSize - offset);
out.write(cipher.doFinal(source, offset, size));
offset += size;
}
}
out.close();
return out.toByteArray();
}
/**
* 构建RSA密钥对
*
* @return /
* @throws NoSuchAlgorithmException /
*/
public static RsaKeyPair generateKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
String publicKeyString = Base64.encodeBase64String(rsaPublicKey.getEncoded());
String privateKeyString = Base64.encodeBase64String(rsaPrivateKey.getEncoded());
return new RsaKeyPair(publicKeyString, privateKeyString);
}
/**
* RSA密钥对对象
*/
public static class RsaKeyPair {
private final String publicKey;
private final String privateKey;
public RsaKeyPair(String publicKey, String privateKey) {
this.publicKey = publicKey;
this.privateKey = privateKey;
}
public String getPublicKey() {
return publicKey;
}
public String getPrivateKey() {
return privateKey;
}
}
}
4. 用户登录controller
@Log("用户登录")
@ApiOperation("登录授权")
@AnonymousPostMapping(value = "/login")
public ResponseEntity<Object> login(@Validated @RequestBody AuthUserDto authUser, HttpServletRequest request) throws Exception {
// 密码解密
String password = RsaUtils.decryptByPrivateKey(RsaProperties.privateKey, authUser.getPassword());
String username = authUser.getUsername();
String ip = StringUtils.getIp(request);
String browser = StringUtils.getBrowser(request);
// String address = StringUtils.getCityInfo(ip);
// 1.验证当前ip是否锁定
List<SysIpLock> sysIpLock = sysIpLockService.findByIp(ip);
// 获取当前日期的Date对象
Date currentDate = new Date();
if(sysIpLock != null && sysIpLock.size() > 0){
// ip多个信息里找最新的一个锁过期时间判断
SysIpLock sysIpLockFrist = sysIpLock.get(0);
if(sysIpLockFrist.getIpLockTime().compareTo(currentDate) > 0){
throw new BadRequestException("ip已锁定,稍后再试");
}else{
// 有ip锁记录,但是释放了,依旧要查用户
List<SysIpLock> sysIpLockList = sysIpLockService.findLockByUserName(username);
if(sysIpLockList != null && sysIpLockList.size() > 0){
// 账号多个信息里找最新的一个锁过期时间判断
SysIpLock sysIpLock1 = sysIpLockList.get(0);
if(sysIpLock1.getLockDateEnd().compareTo(currentDate) > 0){
throw new BadRequestException("账号已锁定,稍后再试");
}
}
}
}else{
// 2.验证用户是否锁定
// 2.1先去sys_ip_lock里看用户是否锁定
List<SysIpLock> sysIpLockList = sysIpLockService.findLockByUserName(username);
if(sysIpLockList != null && sysIpLockList.size() > 0){
// 账号多个信息里找最新的一个锁过期时间判断
SysIpLock sysIpLockFrist = sysIpLockList.get(0);
if(sysIpLockFrist.getLockDateEnd().compareTo(currentDate) > 0){
throw new BadRequestException("账号已锁定,稍后再试");
}
}
}
//比较密码
// 根据用户名获取数据库密码---判断用户是否输入正确密码
String encode = userService.findByUserName(username);
boolean matches = passwordEncoder.matches(password,encode);
SysLog sysLog = new SysLog("LOGIN",System.currentTimeMillis());
// 密码正确与否都交给下面框架jwt搞定,这里负责记录登录日志
sysLogService.saveLog(username, browser, ip, sysLog, matches);
if(matches){
// 保存登录信息(redis)
// loginUserLockService.save(authUser.getUsername(),0,request);
}else{
// 保存登录信息(redis)
// loginUserLockService.save(authUser.getUsername(),1, request);
// 判断log表一段时间内是否超过用户输入密码错误次数,
Timestamp timestampNow = new Timestamp(System.currentTimeMillis());
Timestamp timestampNowNoCalendar = new Timestamp(System.currentTimeMillis());
// 使用Calendar进行时间计算
Calendar calendar = Calendar.getInstance();
calendar.setTime(timestampNowNoCalendar);
calendar.add(Calendar.HOUR, 2); // 在当前时间上加两个小时
Calendar calendarAgain = Calendar.getInstance();
calendarAgain.setTime(timestampNowNoCalendar);
calendarAgain.add(Calendar.HOUR, -2); // 在当前时间上减去两个小时
// 获取加两个小时后的Timestamp
Timestamp twoHoursLater = new Timestamp(calendar.getTimeInMillis());
// 获取加两个小时前的Timestamp
Timestamp twoHoursBefore = new Timestamp(calendarAgain.getTimeInMillis());
Integer usernameFailNumber = sysLogService.findFailCountByUsername(username,timestampNow,twoHoursBefore);
Integer ipFailNumber = sysLogService.findFailCountByIp(ip,timestampNow,twoHoursBefore);
// 超过失败次数就想记录ip和username的sys_ip_lock表加锁记录
if(usernameFailNumber >= 3){
SysIpLock sysIpLock1 = new SysIpLock();
sysIpLock1.setUsername(username);
sysIpLock1.setLockPwd(true);
sysIpLock1.setLockType(false);
sysIpLock1.setLockDateEnd(twoHoursLater);
// ip这两个字段设置not null
sysIpLock1.setIp(ip);
sysIpLock1.setIpStatus(false);
sysIpLockService.create(sysIpLock1);
// sysIpLockService.save(sysIpLock1);
}
if(ipFailNumber >= 6){
SysIpLock sysIpLock2 = new SysIpLock();
sysIpLock2.setIp(ip);
sysIpLock2.setLockType(true);
sysIpLock2.setIpStatus(true);
sysIpLock2.setIpLockTime(twoHoursLater);
// 插入无用的username失败状态
sysIpLock2.setUsername(username);
sysIpLock2.setLockPwd(false);
sysIpLockService.create(sysIpLock2);
// sysIpLockService.save(sysIpLock2);
}
}
// 查询验证码
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("验证码错误");
}
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(authUser.getUsername(), password);
Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
// 生成令牌与第三方系统获取令牌方式
// UserDetails userDetails = userDetailsService.loadUserByUsername(userInfo.getUsername());
// Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
// SecurityContextHolder.getContext().setAuthentication(authentication);
String token = tokenProvider.createToken(authentication);
final JwtUserDto jwtUserDto = (JwtUserDto) authentication.getPrincipal();
// 返回 token 与 用户信息
Map<String, Object> authInfo = new HashMap<String, Object>(2) {{
put("token", properties.getTokenStartWith() + token);
put("user", jwtUserDto);
}};
if (loginProperties.isSingleLogin()) {
// 踢掉之前已经登录的token
onlineUserService.kickOutForUsername(authUser.getUsername());
}
// 保存在线信息
onlineUserService.save(jwtUserDto, token, request);
// 返回登录信息
return ResponseEntity.ok(authInfo);
}
controller里面关于密码的(接着是ip和用户是否锁定暂时不看)
总结
这只是用户登录处理简单处理,重头戏是Security认证流程用户登录:断点看流程认证