作者热门文章
- 使用 Spring Initializr 创建 Spring Boot 应用程序
- 在Spring Boot中配置Cassandra
- 在 Spring Boot 上配置 Tomcat 连接池
- 将Camel消息路由到嵌入WildFly的Artemis上
最近分析了一大学bbs登录时,加密的流程,在此自己用spring boot做了个类似的,在此记录下,感觉还是有点东西的。逻辑很简单:
登录页面:
输入用户名/密码后,这里是admin/admin
用Fiddler抓包是这样的:
这里的Password用了AES加密,这里前端从后端再加载页面的时候,就获取了aes的密钥和向量,当然这里可以用JS逆向,得到这个值(这里和某大学BBS一样)
对应的流程图是这样的:
程序结构如下:
代码如下:
LoginController.java
package cn.it1995.MyController;
import cn.it1995.service.LoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Controller
public class LoginController {
@Autowired
LoginService loginService;
@GetMapping("/")
public String index(HttpServletResponse response,
Model model){
String cookie = loginService.generateCookie();
String salt = loginService.getSaltByCookie(cookie);
String offset = loginService.getOffsetByCookie(cookie);
response.addCookie(new Cookie("token", cookie));
model.addAttribute("saltStr", salt);
model.addAttribute("offsetStr", offset);
return "login.html";
}
@PostMapping
public String login(@RequestParam("userName") String userName,
@RequestParam("password") String password,
HttpServletRequest request,
HttpServletResponse response){
Cookie[] cookies = request.getCookies();
String cookie = "";
for(Integer i = 0; i < cookies.length; i++){
if(cookies[i].getName().equals("token")){
cookie = cookies[i].getValue();
}
}
if(cookie.equals("")){
return "redircet:/";
}
// String salt = loginService.getSaltByCookie(cookie);
try{
boolean passwordCorrect = loginService.isPasswordCorrect(cookie, password);
System.out.println("The passwordCorrect is " + passwordCorrect);
if(!passwordCorrect){
return "redirect:/";
}
}
catch (Exception e){
e.printStackTrace();
return "redirect:/";
}
// System.out.println(userName);
// System.out.println(password);
// System.out.println(salt);
return "success.html";
}
}
MySession.java
package cn.it1995.Object;
public class MySession {
public String cookie;
public String salt;
public String offset;
public MySession(String cookie, String salt, String offset) {
this.cookie = cookie;
this.salt = salt;
this.offset = offset;
}
}
SessionRepository.java
package cn.it1995.repository;
import cn.it1995.Object.MySession;
import org.springframework.stereotype.Repository;
import java.util.ArrayList;
@Repository
public class SessionRepository {
private ArrayList<MySession> sessionList = new ArrayList<MySession>();
public void addSession(MySession session){
sessionList.add(session);
}
public String getSaltByCookie(String cookie){
for(MySession mySession : sessionList){
if(mySession.cookie.equals(cookie)){
return mySession.salt;
}
}
return null;
}
public String getOffsetByCookie(String cookie){
for(MySession mySession : sessionList){
if(mySession.cookie.equals(cookie)){
return mySession.offset;
}
}
return null;
}
public boolean isSessionExist(MySession session){
for(MySession mySession : sessionList){
if(mySession.cookie.equals(session.cookie)){
return true;
}
}
return false;
}
public void clearAllSession(){
sessionList.clear();
}
}
LoginServer.java
package cn.it1995.service;
import cn.it1995.Object.MySession;
import cn.it1995.repository.SessionRepository;
import cn.it1995.tool.AESUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Random;
@Service
public class LoginService {
@Autowired
SessionRepository sessionRepository;
public String generateCookie(){
String cookie = generateString(32);
String salt = generateString(16);
String offset = generateString(16);
MySession mySession = new MySession(cookie, salt, offset);
sessionRepository.addSession(mySession);
return cookie;
}
public String getSaltByCookie(String cookie){
return sessionRepository.getSaltByCookie(cookie);
}
public String getOffsetByCookie(String cookie){
return sessionRepository.getOffsetByCookie(cookie);
}
public boolean isUserExist(String userName){
if(userName.equals("admin")){
return true;
}
return false;
}
public boolean isPasswordCorrect(String cookie, String password) throws Exception {
String saltByCookie = sessionRepository.getSaltByCookie(cookie);
String vi = sessionRepository.getOffsetByCookie(cookie);
String decrypt = AESUtil.decrypt(password, saltByCookie, vi);
if(decrypt.equals("admin")){
return true;
}
return false;
}
public void removeAllSession(){
sessionRepository.clearAllSession();
}
protected String generateString(Integer length){
String str = "zxcvbnmlkjhgfdsaqwertyuiopQWERTYUIOPASDFGHJKLZXCVBNM1234567890";
Random random = new Random();
//cookie
StringBuffer cookieSb = new StringBuffer();
for(int i = 0; i < length; ++i){
//产生0-61的数字
int number = random.nextInt(62);
//将产生的数字通过length次承载到sb中
cookieSb.append(str.charAt(number));
}
return new String(cookieSb);
}
}
AESUtil.java
package cn.it1995.tool;
import org.apache.tomcat.util.codec.binary.Base64;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
public class AESUtil {
public static String decrypt(String content, String key, String vi) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException, UnsupportedEncodingException, UnsupportedEncodingException, InvalidAlgorithmParameterException {
Key k = toKey(key.getBytes());
byte[] encoded = k.getEncoded();
SecretKeySpec aes = new SecretKeySpec(encoded, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
IvParameterSpec iv = new IvParameterSpec(vi.getBytes());
cipher.init(Cipher.DECRYPT_MODE, aes, iv);
byte[] bytes = cipher.doFinal(Base64.decodeBase64(content));
return new String(bytes, "UTF-8");
}
public static String encrypt(String data, String key, String vi) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException, BadPaddingException, IllegalBlockSizeException, InvalidAlgorithmParameterException {
//Key k = toKey(Base64.decodeBase64(key));
Key k = toKey(key.getBytes());
byte[] encoded = k.getEncoded();
SecretKeySpec aes = new SecretKeySpec(encoded, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
IvParameterSpec iv = new IvParameterSpec(vi.getBytes());
cipher.init(Cipher.ENCRYPT_MODE, aes, iv);
byte[] bytes = cipher.doFinal(data.getBytes("UTF-8"));
return Base64.encodeBase64String(bytes);
}
private static Key toKey(byte[] key){
SecretKeySpec aes = new SecretKeySpec(key, "AES");
return aes;
}
public static void main(String[] args) throws NoSuchPaddingException, BadPaddingException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IllegalBlockSizeException, UnsupportedEncodingException, InvalidKeyException {
String content = "7mv2MJPHj1o/rdar1I4i0Q==";
String key = "sN1DEJAVZNf3OdM3";
String vi = "GDHgt7hbKpsIR4b4";
System.out.println("原文 : root");
String e = AESUtil.encrypt("root", key, vi);
System.out.println("密文 : " + e);
String f = AESUtil.decrypt(e, key, vi);
System.out.println("解密 : " + f);
}
}
WebAppMain.java
package cn.it1995;
import org.apache.catalina.Context;
import org.apache.catalina.connector.Connector;
import org.apache.tomcat.util.descriptor.web.SecurityCollection;
import org.apache.tomcat.util.descriptor.web.SecurityConstraint;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
public class WebAppMain {
public static void main(String[] args) {
SpringApplication.run(WebAppMain.class, args);
}
@Bean
public TomcatServletWebServerFactory servletContainer() {
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory() {
@Override
protected void postProcessContext(Context context) {
SecurityConstraint constraint = new SecurityConstraint();
constraint.setUserConstraint("CONFIDENTIAL");
SecurityCollection collection = new SecurityCollection();
collection.addPattern("/*");
constraint.addCollection(collection);
context.addConstraint(constraint);
}
};
tomcat.addAdditionalTomcatConnectors(httpConnector());
return tomcat;
}
@Bean
public Connector httpConnector() {
Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
connector.setScheme("http");
//Connector监听的http的端口号
connector.setPort(8081);
connector.setSecure(false);
//监听到http的端口号后转向到的https的端口号
connector.setRedirectPort(8443);
return connector;
}
}
login.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="https://www.thymeleaf.org"
xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<!-- Standard Meta -->
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
<!-- Site Properties -->
<title>Login Example - Semantic</title>
<link rel="stylesheet" type="text/css" href="https://semantic-ui.com/dist/components/reset.css">
<link rel="stylesheet" type="text/css" href="https://semantic-ui.com/dist/components/site.css">
<link rel="stylesheet" type="text/css" href="https://semantic-ui.com/dist/components/container.css">
<link rel="stylesheet" type="text/css" href="https://semantic-ui.com/dist/components/grid.css">
<link rel="stylesheet" type="text/css" href="https://semantic-ui.com/dist/components/header.css">
<link rel="stylesheet" type="text/css" href="https://semantic-ui.com/dist/components/image.css">
<link rel="stylesheet" type="text/css" href="https://semantic-ui.com/dist/components/menu.css">
<link rel="stylesheet" type="text/css" href="https://semantic-ui.com/dist/components/divider.css">
<link rel="stylesheet" type="text/css" href="https://semantic-ui.com/dist/components/segment.css">
<link rel="stylesheet" type="text/css" href="https://semantic-ui.com/dist/components/form.css">
<link rel="stylesheet" type="text/css" href="https://semantic-ui.com/dist/components/input.css">
<link rel="stylesheet" type="text/css" href="https://semantic-ui.com/dist/components/button.css">
<link rel="stylesheet" type="text/css" href="https://semantic-ui.com/dist/components/list.css">
<link rel="stylesheet" type="text/css" href="https://semantic-ui.com/dist/components/message.css">
<link rel="stylesheet" type="text/css" href="https://semantic-ui.com/dist/components/icon.css">
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://semantic-ui.com/dist/components/form.js"></script>
<script src="https://semantic-ui.com/dist/components/transition.js"></script>
<!-- 引入 CDN Crypto.js 开始 AES加密 注意引入顺序 -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/core.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/enc-base64.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/md5.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/evpkdf.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/cipher-core.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/aes.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/pad-pkcs7.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/mode-ecb.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/enc-utf8.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/enc-hex.min.js"></script>
<!-- 引入 CDN Crypto.js 结束 -->
<style type="text/css">
body {
background-color: #DADADA;
}
body > .grid {
height: 100%;
}
.image {
margin-top: -100px;
}
.column {
max-width: 450px;
}
</style>
<script language="JavaScript">
function login(){
var password = document.getElementById("password").value;
var offset = document.getElementById("offset").value;
var salt = document.getElementById("salt").value;
let key = CryptoJS.enc.Utf8.parse(salt);
let srcs = CryptoJS.enc.Utf8.parse(password);
let vi = CryptoJS.enc.Utf8.parse(offset);
var encrypted = CryptoJS.AES.encrypt(srcs, key, {
iv: vi,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
console.log("原始数据 : ", password);
console.log("vi : ", offset);
console.log("salt : ", salt);
document.getElementById("password").value = encrypted.toString();
console.log("ase加密 : ", encrypted.toString());
console.log("解密");
let decrypted = CryptoJS.AES.decrypt(encrypted.toString(), key, {
iv: vi,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
console.log("ase 解码 : " , CryptoJS.enc.Utf8.stringify(decrypted).toString());
}
</script>
</head>
<body>
<div class="ui middle aligned center aligned grid">
<div class="column">
<h2 class="ui teal image header">
<div class="content">
Log-in to your account
</div>
</h2>
<form action="login" method="post" class="ui large form" onsubmit="login()">
<div class="ui stacked segment">
<div class="field">
<div class="ui left icon input">
<i class="user icon"></i>
<input type="text" id="userName" name="userName" placeholder="userName">
</div>
</div>
<div class="field">
<div class="ui left icon input">
<i class="lock icon"></i>
<input type="password" id="password" name="password" placeholder="password">
</div>
</div>
<button class="ui fluid large teal button submit">Login</button>
</div>
<input name="salt" id="salt" type="hidden" th:value="${saltStr}">
<input name="offset" id="offset" type="hidden" th:value="${offsetStr}">
<div class="ui error message"></div>
</form>
</div>
</div>
</body>
</html>
success.html
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="https://www.thymeleaf.org"
xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
<head>
<!-- Standard Meta -->
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
<!-- Site Properties -->
<title>Success</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<link rel="stylesheet" type="text/css" href="https://semantic-ui.com/dist/components/reset.css">
<link rel="stylesheet" type="text/css" href="https://semantic-ui.com/dist/components/site.css">
<link rel="stylesheet" type="text/css" href="https://semantic-ui.com/dist/components/container.css">
<link rel="stylesheet" type="text/css" href="https://semantic-ui.com/dist/components/grid.css">
<link rel="stylesheet" type="text/css" href="https://semantic-ui.com/dist/components/header.css">
<link rel="stylesheet" type="text/css" href="https://semantic-ui.com/dist/components/image.css">
<link rel="stylesheet" type="text/css" href="https://semantic-ui.com/dist/components/menu.css">
<link rel="stylesheet" type="text/css" href="https://semantic-ui.com/dist/components/divider.css">
<link rel="stylesheet" type="text/css" href="https://semantic-ui.com/dist/components/segment.css">
<link rel="stylesheet" type="text/css" href="https://semantic-ui.com/dist/components/form.css">
<link rel="stylesheet" type="text/css" href="https://semantic-ui.com/dist/components/input.css">
<link rel="stylesheet" type="text/css" href="https://semantic-ui.com/dist/components/button.css">
<link rel="stylesheet" type="text/css" href="https://semantic-ui.com/dist/components/list.css">
<link rel="stylesheet" type="text/css" href="https://semantic-ui.com/dist/components/message.css">
<link rel="stylesheet" type="text/css" href="https://semantic-ui.com/dist/components/icon.css">
<script src="https://semantic-ui.com/dist/components/form.js"></script>
<script src="https://semantic-ui.com/dist/components/transition.js"></script>
<style type="text/css">
body {
background-color: #DADADA;
}
body > .grid {
height: 100%;
}
.image {
margin-top: -100px;
}
.column {
max-width: 450px;
}
</style>
</head>
<body>
<div class="ui middle aligned center aligned grid">
<div class="column">
<h1>Success</h1>
</div>
</div>
</body>
</html>
application.properties
#Thymeleaf配置
spring.thymeleaf.cache=false
spring.thymeleaf.encoding=utf-8
spring.thymeleaf.mode=HTML5
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
server.port=8443
server.ssl.key-alias=selfsigned_localhost_sslserver
server.ssl.key-password=it1995
server.ssl.key-store=classpath:ssl-server.jks
server.ssl.key-store-provider=SUN
server.ssl.key-store-type=JKS
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>LoginAuthority</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<version>2.3.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-ext-jdk16</artifactId>
<version>1.45</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.1.10.RELEASE</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
源码打包下载地址:
我是一名优秀的程序员,十分优秀!