发表于: 2020-05-18 14:53:48
1 1482
今天完成的事情:
修改后完整的注册代码
@RequestMapping(value = "/register",method = RequestMethod.POST)
public String register(account account){
logger.info("传入对象为:"+account);
String u = account.getUsername();
String p = account.getPassword();
logger.info("输入的账户为:"+ u+"密码为"+p);
//判断输入的用户名是否为空 或者 空字符
if(u!=null && u!=""){
//判断数据库中是否有此用户名
account accountA = accountService.findAccount(u);
//如果查询出的账户为空(代表用户不存在),才能注册
if(ObjectUtils.isEmpty(accountA)){
try{
//对传入的密码用MD5进行加密加盐
String MD5Password = MD5Util.generate(p);
//设置密码为新密码
account.setPassword(MD5Password);
//插入数据
int id = accountService.addAccount(account);
System.out.print("注册成功,请登录");
//返回到登录页面
return "loginPage";}catch (Exception e){
System.out.print("注册失败,请重新填写注册信息");
return "registerPage";
}
}else {
System.out.print("用户名已经存在,请更换用户名");
return "registerPage";
}
}else{
System.out.print("用户名不能为空");
return "registerPage";
}
}
注册流程:
1.判断输入的用户名密码是否为空
2.判断数据库中有无此用户
3.对用户密码进行MD5加密加盐,存入数据库中
4.返回到登录页面
完整登录代码
@RequestMapping(value = "/login",method = RequestMethod.POST)
public String login(account account, HttpServletResponse response) throws Exception{
logger.info("输入的表格数据为为:" +account);
String u = account.getUsername();
String p = account.getPassword();
logger.info("取出account账号为"+u+"---------"+"取出account密码为"+p);
//判断输入的用户名是否为空 或者 空字符
if (u != null && u != "") {
logger.info("用户名通过");
account accountB = accountService.findAccount(u);
logger.info("查询出的内容为:"+accountB);
//判断数据库中是否有此用户名
if (!ObjectUtils.isEmpty(accountB)) {
logger.info("用户不为空");
//用MD5验证输入密码有没有被篡改过 没有就返回true
if(MD5Util.verify(p,accountB.getPassword())==true) {
logger.info("密码正确");
// 把用户id转化为long类型
int BId = accountB.getId();
long B = (int)BId;
//对此用户id进行des加密
String desId = des.encryptFromLong(B);
logger.info("对用户id加密的结果为========" + desId);
//对当前时间进行des加密
String desTime = des.encryptFromLong(System.currentTimeMillis());
logger.info("对时间加密的结果为========" + desTime);
//对用户id和登陆时间一起进行des加密
String token = des.encrypt(desId + "|" +accountB.getUsername()+ "|" + desTime);
logger.info("对用户id和登陆时间加密的结果(token)为========" + token);
// 创建cookie赋值
Cookie tokenCookie = new Cookie("token", token);
// 设置cookie存在时间
tokenCookie.setMaxAge(30 * 60);
// 向客户端返回cookie
response.addCookie(tokenCookie);
//把token放到header头里
// response.setHeader("token",token)
System.out.print("登陆成功,正在进入主页面");
return "redirect:/u/homePage";
}else {
System.out.print("密码错误,请重新输入");
return "loginPage";
}
} else {
System.out.print("用户不存在,请重新输入用户名");
return "loginPage";
}
} else {
System.out.print("用户名不能为空!");
return "loginPage";
}
}
登录流程:
1.判断输入用户名密码是否为空
2.判断数据库中有无此用户
3.用MD5工具类验证密码是否被篡改,是否还是相同的密码
4.用des加密用户的id和登陆时间 生成token
5.把token放到cookie里 或者放到reponse的header里
6.进入主页面
拦截器代码
public class LoginInterceptor implements HandlerInterceptor {
Logger logger = Logger.getLogger(LoginInterceptor.class);
@Autowired
accountService accountService;
DESUtilA des = new DESUtilA();
//在请求之前被调用
@Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception {
// 请求cookie里的信息,放入cookies
Cookie[] cookies = httpServletRequest.getCookies();
//如果cookies的长度等于0,表示没有cookies,返回登录页面,让用户登录一下下,发给用户一个cookie,下次光临的时候用户就有cookie了
if (cookies.length == 0) {
logger.info("没有cookie");
//如果没有token 就从header里取token
String token = httpServletRequest.getHeader("token");
logger.info("从header里取出的token为"+token);
//用des对token进行解密
String firstDecrypt = des.decrypt(token);
logger.info("对token进行第一次解密的结果"+firstDecrypt);
//把token解密后的结果根据|进行拆分
String[] mingwen = firstDecrypt.split("//|");
logger.info("把token解密后的结果根据|进行拆分");
//token根据 | 拆分出的第一个(用户id)
String TokenId = mingwen[0];
logger.info("token中解密出第一个是id"+TokenId);
long id = des.decryptToLong(TokenId);
logger.info("把id解密并转换成long类型"+id);
//long类型转化为int类型
int AId = (int)id;
//根据id查询数据库中有无此用户
account accountA = accountService.findById(AId);
//如果用户为空,返回登录界面
if(accountA == null){
logger.info("没有此token内所含Id的用户,必须去登录");
httpServletResponse.sendRedirect("/beforeLogin");
return false;
}
//token根据 | 拆分出的第二个(用户名称) 这个不需要验证
String TokenName = mingwen[1];
logger.info("token中解密出第二个是名字"+TokenName);
//token根据 | 拆分出的第三个(用户登陆时间)
String TokenTime = mingwen[2];
logger.info("token中解密出第三个是登陆时间"+TokenTime);
long time = des.decryptToLong(TokenTime);
logger.info("把时间解密并转换成long类型"+time);
//把当前时间(long)减去登陆时间(long) 如果>=30分钟 意味着时间已经超过了 返回登录页面
if (System.currentTimeMillis() - time >= 30 * 60 * 1000) {
System.out.println("token过期=====");
//返回登录页面
httpServletResponse.sendRedirect("/beforeLogin");
return false;
}
logger.info("token(id,有效期)验证通过,登录成功");
return true;
//验证某一项
} else {
// 用户有cookies
logger.info("有cookie");
//遍历一下用户的cookies,放入cookie,看看他的cookie是什么呀
for (Cookie cookie : cookies) {
//如果他的cookie与我发给他的cookie相等,就可以直接返到页面里
if (cookie.getName().equals("token")) {
String Mytoken = cookie.getValue();
logger.info("从cookie取出的token 为:" + Mytoken);
//用des对token进行解密
String MingwenA = des.decrypt(Mytoken);
logger.info("对token进行第一次解密的结果"+MingwenA);
//把token解密后的结果根据|进行拆分
String[] mingwenB = MingwenA.split("\\|");
logger.info("对token解密后的结果根据|进行拆分后的结果"+mingwenB);
//token根据 | 拆分出的第一个(用户id)
String TokenId = mingwenB[0];
logger.info("token中解密出第一个是id"+TokenId);
long id = des.decryptToLong(TokenId);
logger.info("把id解密并转换成long类型"+id);
//long类型转化为int类型
int BId = (int)id;
//根据id查询数据库中有无此用户
account accountB = accountService.findById(BId);
//如果用户为空,返回登录界面
if(accountB == null){
logger.info("没有此token内所含Id的用户,必须去登录");
httpServletResponse.sendRedirect("/beforeLogin");
return false;
}
//token根据 | 拆分出的第二个(用户名称)
String TokenName = mingwenB[1];
logger.info("token中解密出第二个是名字"+TokenName);
//token根据 | 拆分出的第三个(用户登陆时间)
String TokenTime = mingwenB[2];
logger.info("token中解密出第三个是登陆时间"+TokenTime);
long time = des.decryptToLong(TokenTime);
logger.info("把时间解密并转换成long类型"+time);
//把当前时间(long)减去登陆时间(long) 如果>=30分钟 意味着时间已经超过了 返回登录页面
if (System.currentTimeMillis() - time >= 30 * 60 * 1000) {
System.out.println("token过期=====");
//返回登录页面
httpServletResponse.sendRedirect("/beforeLogin");
return false;
}
logger.info("token(id,有效期)验证通过,登录成功");
return true;
}
}
}
// 返回登录页面
httpServletResponse.sendRedirect("/beforeLogin");
return false;
}
//在业务处理器处理请求完成之后,生成视图之前执行
@Override
public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception{
}
//在DispatcherServlet完全处理完请求之后被调用,可用于清理资源
@Override
public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception{
}
1. 拦截器内先获取客户端传来的cookie
2. 如果没有cookie 就从请求头获取token(有cookie就获取其value里的token,然后执行下面的流程)
3.用des解密工具拆分token (里面是id和登陆时间)
4.根据id查询数据库内有无此用户,无返回false ,进主页面失败
5. 根据登陆时间查询token是否过期,没有返回false ,进主页面失败
6.上面俩查询都没有返回false 则返回ture 进主页面成功
任务总耗时7天 完成进度慢

三 任务中遇到哪些疑难问题,最终是如何解决的,有哪些值得分享的收获
1. 做任务前要搞清楚登录注册的流程,页面之间互相跳转的关系。
2. 任务中所需的 des是对称加密算法 所以需要一个加密破解方法
md5是信息摘要算法,是不可逆的,所以需要一个验证密码是否被篡改的方法。
3.拦截器的作用就是拦截用户的请求,从请求头里获取cookie或者session或者token,对其验证后,再决定是否通过。
自己写的des算法文章:
https://blog.csdn.net/qq_42733162/article/details/106192213
任务GIt代码:https://github.com/git-tianbo/task
深度思考:
- 1.JWT简单介绍
JWT用处:
信息交换:JWT是在各个服务之间安全传输信息的好方法。因为JWT可以签名,例如,使用公钥/私钥对儿 - 可以确定请求方是合法的。此外,由于使用标头和有效负载计算签名,还可以验证内容是否未被篡改。
JWT组成:
jwt是由3段信息构成的,将这三段信息用.链接到一起就成了jwt字符串。
(2)荷载(playload)内容: 荷载jwt的主体,claims是关于实体(常用的是用户信息)和其他数据的声明
claims有三种类型: registered claims(标准注册声明), public claims(公共声明), and private claims(私有声明)。
registered claims(标准注册声明):
jti 【JWT ID】 该jwt的唯一ID编号
Public claims 和 Private claims
举例: {"sub": "1234567890","name": "admin","admin": true}
(3)签名(signature):
把header 和 claims用Base64URL生成的字符 用.连接起来 再加上我们定义的私钥
一起进行hs256加密 就生成了签名
sign = hs256( base64UrlEncode(Header) + "." + base64UrlEncode(Claims) + key)
最终组成的一个JWT签名: AAA(base64UrlEncode(Header)) . BBB(base64UrlEncode(Claims) . CCC(sign)
参考: https://www.cnblogs.com/wangshouchang/p/9551748.html
生成JWT的代码
这里把荷载内容固定了, 如果不想固定的话 定义一个方法传递荷载的参数即可。
public class JWTUtilB {
// 自己定义的私钥
private static final String SECRET_KEY = "this is a secret key";
public static void main(String[] args) {
// 生成token
String jwtToken = Jwts.builder()
// 头部
.setHeaderParam("typ", "JWT")
// jwt 标注中的申明
.setIssuedAt(new Date()) // 签发时间
.setExpiration(new Date(new Date().getTime() + 10000L))// 过期时间
.setSubject("19930311")// jwt面向的客户
.setIssuer("huan")// jwt的签发者
// 公共申明和私有申明
.claim("user_id", "admin")
.claim("profession", "前端工程师")
.claim("age", 25)
.claim("sex", "男")
// 签证
.signWith(SignatureAlgorithm.HS256, SECRET_KEY.getBytes())
.compact();
System.out.println("生成的 jwt token 如下:" + jwtToken);
// 此处是验证生成的jwt
Jws<Claims> claimsJws = Jwts.parser()
// 验证签发者字段iss 必须是 huan
.require("iss", "huan")
// 设置私钥
.setSigningKey(SECRET_KEY.getBytes())
// 解析jwt字符串
.parseClaimsJws(jwtToken);
// 获取头部信息
JwsHeader header = claimsJws.getHeader();
// 获取载荷信息
Claims payload = claimsJws.getBody();
System.out.println("解析过来的jwt的header如下:" + header.toString());
System.out.println("解析过来的jwt的payload如下:" + payload.toString());}
}
生成的 jwt token 如下:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1ODk3ODE4NzUsImV4cCI6MTU4OTc4MTg4NSwic3ViIjoiMTk5MzAzMTEiLCJpc3MiOiJodWFuIiwidXNlcl9pZCI6ImFkbWluIiwicGhvbmUiOiIxODI1MTQyMTAwMCIsImFnZSI6MjUsInNleCI6IueUtyJ9.eOQDXSz7532f0YZnun2qiJMzAKzVxnSGphID5d5cDlY
解析过来的jwt的header如下:
{typ=JWT, alg=HS256}
解析过来的jwt的payload如下:
{iat=1589781875, exp=1589781885, sub=19930311, iss=huan, user_id=admin, profession=前端工程师, age=25, sex=男}
然后根据解析的内容 做判断即可。
2.拦截器、过滤器、监听器各有什么作用?
监听器(Listener):当一个事件发生的时候,你希望获得这个事件发生的详细信息,而并不想干预这个事件本身的进程,这就要用到监听器。
三者的作用范围:
参考1: https://blog.csdn.net/c_royi/article/details/80563131
参考2:http://www.r9it.com/20171127/filter-interceptor-lisener.html
3.什么叫明文,什么叫密文,为什么不允许在数据库里明文保存密码?
明文就是未经过加密的数据,密文是经过加密的数据。
(2)一旦数据库发生泄露,攻击者拥有用户的明文密码后,就可以使用用户名和密码去其他网站尝试登陆(因为往往用户会将多个网站的密码根据习惯设成一样的),一旦登陆成功,就会造成更严重的后果。(对外)
cookie.setMaxAge(time)
maxAge 为正数,表示此cookie从创建到过期所能存在的时间,以秒为单位,此cookie会存储到客户端电脑,以cookie文件形式保存,不论关闭浏览器或关闭电脑,直到时间到才会过期。
maxAge可以为负数,表示此cookie只是存储在浏览器内存里,只要关闭浏览器,此cookie就会消失。maxAge默认值为-1。
maxAge还可以为0,表示从客户端电脑或浏览器内存中删除此cookie。
所以为了更好的兼容各个浏览器,cookie最大别超过4094字节。
5.在App中没有Cookie,怎么解决用户身份识别的问题?Token应该放在什么位置?怎么在后端设计的时候,又支持Cookie,又支持Token?
MD5(5201314+sdsajdjasj898) = sdaqqqqqqqqq44544qqqq(加密加盐的结果)
7. 什么叫MD5,MD5通常做什么用处,为什么MD5不可逆,用做密码加密的时候仍然可能会被解密?
所以为了解决MD5安全性不高的问题,我们采取加盐的方式和HMACMD5(加密匙)方式来使用。
9.Https是什么?怎么配置Https证书,哪些请求需要Https,登录是否需要Https?
HTTP协议传输的数据都是未加密的,因此使用HTTP协议传输隐私信息非常不安全。为了保证这些隐私数据能加密传输,于是网景公司设计了SSL(Secure Sockets Layer)协议用于对HTTP协议传输的数据进行加密,从而就诞生了HTTPS。HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议更为安全。
云服务器的申请方式。 参考: https://www.cnblogs.com/ggband/p/10169619.html
登录设计到用户的账户密码,为了保密安全,最好用HTTPS。
10.什么是对称加密,什么是非对称加密?分别适用于什么场景?
常见的非对称加密算法:RSA,ECC
11.Token的构造规则是什么,怎么确认用户身份的,怎么保证Token被偷窃的问题?
可以用DES对Token关键信息一起进行加密,拿到Token后再进行解密。从而保证Token的安全性。
12. Cookie是在Http的什么位置存放,Cookie里的键可以重复吗?
Cookie里的键不能重复,不然第二个相同的key会把前一个key的value覆盖掉。
13.什么是session?什么是cookie?session和cookie有什么区别?什么场景适用于session?什么场景适用于cookie?
cookie数据是存放在客户的浏览器上,session数据是放在服务器上。
cokie: 由于HTTP是一种无状态的协议,服务器单从网络连接上无从知道用户身份。而且HTTP协议数据一旦交互完毕,客户端与服务器端的连接就会关闭,意味着服务器无法从连接上跟踪对话。怎么办呢?那就给客户端们颁发一个密匙吧,每人一个,无论谁访问都必须携带自己密匙。这样服务器就能从密匙上确认客户身份了。这就是Cookie的工作原理。
session: Session是另一种记录客户状态的机制,不同的是Cookie保存在客户端浏览器中,而Session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。这就是Session。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了。
由于cookie不是很安全 ,别人可以分析存放在本地的cookie并进行cookie欺骗,考虑到安全应当使用session。
可以考虑将登陆信息等重要信息存放为session,其他信息如果需要保留,可以放在cookie中。
明天计划的事情:
开始任务6
评论