前置知识:
前端登录加密看用户登录
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); // 在当前时间上减去两个小时// 获取加两个小时后的TimestampTimestamp twoHoursLater = new Timestamp(calendar.getTimeInMillis());// 获取加两个小时前的TimestampTimestamp 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 nullsysIpLock1.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()) {// 踢掉之前已经登录的tokenonlineUserService.kickOutForUsername(authUser.getUsername());}// 保存在线信息onlineUserService.save(jwtUserDto, token, request);// 返回登录信息return ResponseEntity.ok(authInfo);}
controller里面关于密码的(接着是ip和用户是否锁定暂时不看)
总结
这只是用户登录处理简单处理,重头戏是Security认证流程用户登录:断点看流程认证