❣博主主页: 33的博客❣
▶️文章专栏分类:项目日记◀️
🚚我的代码仓库: 33的代码仓库🚚
🫵🫵🫵关注我带你了解更多项目内容
1.前言
在上一篇文章中,我们已经完成了博客系统的数据库设计和数据层实现,那么接下我们继续完成控制层和业务层的逻辑。
2.用户登录实现
我们先定义一个返回结果的实体类:
public class Result<T> {
private ResultStatus code;
private String errMsg;
private T data;
public static <T>Result<T> sucess(T data){
Result result=new Result();
result.setCode(ResultStatus.SUCCESS);
result.setErrMsg("");
result.setData(data);
return result;
}
public static <T>Result<T> fail(String errMsg){
Result result=new Result();
result.setCode(ResultStatus.FAIL);
result.setErrMsg(errMsg);
result.setData(null);
return result;
}
}
2.1UserController
只要实现与前端的接口,验证密码是否正确,据博客id获取作者信息
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
UserService userService;
@RequestMapping("/login")
public Result login(String username,String password){
log.info("接受到参数:username{} password{}",username,password);
if(!StringUtils.hasLength(username)||!StringUtils.hasLength(password)){
Result.fail("用户名或密码不能为空");
}
UserInfo userInfo=userService.selectByName(username);
if (userInfo==null){
return Result.fail("用户不存在");
}
if (!password.eaquals(userInfo.getPassword()){
return Result.fail("密码错误");
}
return Result.sucess();
}
/*
* 根据博客id获取作者信息
* */
@RequestMapping("/getAuthorInfo")
public UserInfo getAuthorInfo(Integer blogId){
UserInfo userInfo=userService.getAuthorInfo(blogId);
return userInfo;
}
}
UserService类
@Autowired
private UserInfoMapper userInfoMapper;
@Autowired
private BlogMapper blogMapper;
public UserInfo selectByName(String username) {
return userInfoMapper.selectByName(username);
}
public UserInfo selectById(Integer userId) {
return userInfoMapper.selectById(userId);
}
在之前的项目中,我们会使用cookie和session来存储用户的信息,即当客户端第一次发送一个请求的时候,此时服务器在session中会新增一个记录,并把session返回给客户端,在后续的请求中,客户端都会携带sessionId,当服务器收到sessionId时候会更具sessionId去查找用户对应的信息,如果找到就进行后续操作,没有找到会创建一个sessionId返回给客户端。但是当我们把一个程序部署到服务器上时,可能部署到多个服务器中,这个时候由于负载均衡,可能用户刚刚已经登录过账号,但是在下一个请求的时候,又需要重新登录,这样用户的体验就非常不会,那为什么会这样呢?因为sessionId是存储在服务器的内存中,当请求分配到宁外一个服务器时就此时不含有sessionId的信息就需要重新创建一个,这样是非常不友好的。那么有没有什么其他的方式既可以存储用户信息也可以避免重复登录的问题呢?当然是有的,那就是令牌技术。
令牌技术听起来很高级,实际上就是一个字符串。服务器可以生成和验证字符串由客户端进行保存。此时当客户端第一次向服务器发送一个请求时,服务器就会生成一个令牌返回给客户端,在后续的请求中,客户端就携带这个令牌,服务器此时只需要验证这个令牌是否有效就可以了。这样就解决了负载均衡引起的问题,但令牌的创建需要自己完成。令牌的实现方式有很多种,我们采用JWT令牌。
2.2JWT令牌
JWT令牌包含3个部分:头部,负载,签名。
头部:包含令牌的类型,和使用的算法
负载:存储的具体的有效信息,内容是自定义的
签名:⽤于防止jwt内容被篡改,确保安全
左半部分信息,合在一起使用Base64编码,就成了右半部分的JWT令牌。
生成令牌:
//1.生成密钥
private static final String KEY="fbvH5TEANeRAPGAPSrZ5+8N5GQpj6rxjiG/mjmIgF+k=";
private static final Key key=Keys.hmacShaKeyFor(Decoders.BASE64.decode(KEY));
//2.生成过期时间:
public static final long Expiration=12*60*60*1000;
//生成token
public void genJwt(){
Map<String,Object> claim=new HashMap<>();
claim.put("id",2);
claim.put("name","lisi");
String token= Jwts.builder().setClaims(claim)
.setExpiration(new Date(System.currentTimeMillis()+Expiration))
.signWith(key)
.compact();
System.out.println(token);
}
校验令牌:
public void parseJWT(){
String token="eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoibGlzaSIsImlkIjoyLCJleHAiOjE3MTg2NDUxOTd9.3N7564VY2YktvLBcJW5m024pUiBgCHXuOoVmjWOBMHc";
JwtParser build = Jwts.parserBuilder().setSigningKey(key).build();
Claims claims = build.parseClaimsJws(token).getBody();
System.out.println(claims);
}
2.3MD5加密
其实在登录模块,还有一个问题就是关于密码的显示,在MySQL中我们常常会对用户的一些隐私信息进行加密,例如密码,家庭住址等,防止当黑客侵入数据库的时候获取用户信息。
在博客系统中,我们使用MD5加密算法进行加密。MD5是属于摘要算法,输入任意长度的内容都会生成长度相同的加密内容,并且摘要算法是不可逆,无法解密。
虽然MD5算法是不能解密的但是相同的数据,生成的密文都是相同的。所以当存储数据的数据库泄露以后,攻击者就很容易找到相同的密码的用户。因此在对用户密码进行加密的时候,需要考虑即使相同的密码也具有不同的密文。
我们可以通过一个明文+一个随机数值,通过加密算法来进行加密,这个随机值就称为盐,此时就算有相同的明文但是有不同密文。
接下来我们进行具体操作:
2.4完善登录
创建JWT类
public class JwyUtils {
private static final String KEY="fbvH5TEANeRAPGAPSrZ5+8N5GQpj6rxjiG/mjmIgF+k=";
private static final Key key= Keys.hmacShaKeyFor(Decoders.BASE64.decode(KEY));
public static final long Expiration=12*60*60*1000;
/*
* 生成token
* */
public static String genJwtToken(Map<String,Object> claim){
String token= Jwts.builder()
.setClaims(claim)
.setExpiration(new Date(System.currentTimeMillis()+Expiration))
.signWith(key)
.compact();
return token;
}
/*
* 校验token
* */
public static Claims parseToken(String token){
JwtParser build = Jwts.parserBuilder().setSigningKey(key).build();
Claims claims=null;
try {
claims = build.parseClaimsJws(token).getBody();
}catch (Exception e){
return null;
}
return claims;
}
/*
* 根据token返回id
* */
public static Integer getIdByToken(String token){
Claims claims=parseToken(token);
if(claims!=null){
Integer userId=(Integer) claims.get(Constants.TOKEN_ID);
if (userId>0){
return userId;
}
}
return null;
}
}
在密码验证通过以后,把token返回给前端:
Map<String,Object> claim=new HashMap<>();
claim.put(Constants.TOKEN_ID,userInfo.getId());
claim.put(Constants.TOKEN_USERNAME,userInfo.getUserName());
String token= JwyUtils.genJwtToken(claim);
return Result.sucess(token);
当前端收到返回的数据可以把它存储来 local storage中我们后续再讲
同时可以更具用户id返回用户信息:
@RequestMapping("/getUserInfo")
public UserInfo getLoginUserInfo(HttpServletRequest request){
String token = request.getHeader(Constants.REQUEST_HEADER_TOKEN);
Integer userId=JwyUtils.getIdByToken(token);
if(userId==null){
return null;
}
UserInfo userInfo=userService.selectById(userId);
return userInfo;
}
同时我们提供用户注册模块:
@RequestMapping("/register")
public Boolean register(String username,String password,String githubUrl){
Boolean b=userService.register(username,password,githubUrl);
return true;
}
Service类:
public Boolean register(String username, String password, String githubUrl) {
UserInfo userInfo=new UserInfo();
userInfo.setUserName(username);
userInfo.setGithubUrl(githubUrl);
userInfo.setPassword(SecurityUtils.encrypt(password));
return userInfoMapper.adduser(userInfo);
}
3.总结
这部分我主要对用户登录模块的后端代码进行了设计和优化,再下一面文章中将对博客页面进行实现。
下期预告:梦幻笔耕-博客页面