JavaWeb博客系统后端-登录功能
登录是不是最基本的功能呀
你发文章之前要登录
你修改文章内容、添加分类需要登录
登录流程
我们先不考虑验证码的问题,后面我们再叠加功能上去,先完成简单的操作。一步一步去进化即可。
从前端给后台提交用户数据,然后去查询用户,校验密码,颁发令牌。
代码实现
/**
* @param user
* @return
*/
@PostMapping("/login")
public ResponseResult login(User user) {
return userService.login(user);
}
这是service的代码
@Override
public ResponseResult login(User user) {
//查询用户是否存在,可以通过邮箱,可以通过用户名
String userName = user.getUserName();
User dbUser = userDao.findOneByUserName(userName);
if (dbUser == null) {
dbUser = userDao.findOneByEmail(userName);
}
if (dbUser != null) {
//如果不为空,那么对比密码
boolean matches = passwordEncoder.matches(user.getPassword(), dbUser.getPassword());
if (matches) {
//如果密码正确,那么就颁发令牌
//TODO:
}
}
//也可以说用户名错误/密码错误,就是不告诉用户真正原因,提高安全性
//return ResponseResult.FAILED("用户不存在");
return ResponseResult.FAILED("账号或密码错误");
}
如何保持用户的登陆状态
接触过web基础的同学应该知道,可以把查询到的user,整个对象干掉密码以后,设置到session里,这样子,前端就可以用了。
但是这个方式满足不了一处登陆,处处访问。
比如说,你登录了百度的账号,你访问贴吧的时候就是登录的,你访问百度网盘是已经登陆的...这就是单点登陆了。一处登录以后,访问同域名下的网站就不用再登录了。
它是怎么实现的呢?
令牌token
什么是令牌呢?
古代的令牌是通行证,现在的令牌也一样。你持有某一个令牌,当你访问服务器的时候,带上令牌。服务器校验你的令牌后才给你特定的权限。
什么是jwt呢?
其实令牌可以任意的,只要完成认证的功能,对吧!这是最简单的功能需求。
但是这样子满足不了之前通过sesstion设置整个用户信息的需求,所以就有了jwt
我们先了解JWT,也就是JSON web token,它可以携带一定的信息,比如说我们的用户名,用户头像...
官网 https://jwt.io/
JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.
什么时候使用jwt呢?
When should you use JSON Web Tokens?
- 认证 Authorization
- 传递信息
jwt结构
- Header
- Payload
- Signature
JWT包括头部信息,载荷,签名
主要解释一睛什么叫负载,载荷。这个套到火箭上就是运载的东西。这里呢我们可以承载相关信息,比如说用户的头像,用户id,用户名之类的。
生成jwt
添加依赖
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.6.0</version>
</dependency>
我们整一个工具类,方便创建以及解析jwt
public class JwtUtil {
//盐值
private static String key = "ad128433d8e3356e7024009bf6add2ab";
//单位是毫秒
private static long ttl = Constants.TimeValueInMillions.HOUR_2;//2个小时
public String getKey() {
return key;
}
public void setKey(String key) {
this.key = key;
}
public long getTtl() {
return ttl;
}
public void setTtl(long ttl) {
this.ttl = ttl;
}
/**
* @param claims 载荷内容
* @param ttl 有效时长
* @return
*/
public static String createToken(Map<String, Object> claims, long ttl) {
JwtUtil.ttl = ttl;
return createToken(claims);
}
public static String createRefreshToken(String userId, long ttl) {
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
JwtBuilder builder = Jwts.builder().setId(userId)
.setIssuedAt(now)
.signWith(SignatureAlgorithm.HS256, key);
if (ttl > 0) {
builder.setExpiration(new Date(nowMillis + ttl));
}
return builder.compact();
}
/**
* @param claims 载荷
* @return token
*/
public static String createToken(Map<String, Object> claims) {
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
JwtBuilder builder = Jwts.builder()
.setIssuedAt(now)
.signWith(SignatureAlgorithm.HS256, key);
if (claims != null) {
builder.setClaims(claims);
}
if (ttl > 0) {
builder.setExpiration(new Date(nowMillis + ttl));
}
return builder.compact();
}
public static Claims parseJWT(String jwtStr) {
return Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(jwtStr)
.getBody();
}
}
这里注意一下哈,因为视频里就弄错了单位。时间单位是毫秒哦!
最后的实现过程
- 前端获取到验证码(图灵验证码,证明是人类在操作)
- 填写账号密码
- 提交
- 返回登录结果
获取图灵验证码
/**
* 获取图灵验证码
* 有效时长为10分钟
*
* @return
*/
@GetMapping("/captcha")
public void getCaptcha(HttpServletResponse response) {
try {
userUtilsService.createCaptcha(response);
} catch (Exception e) {
log.error(e.toString());
}
}
这里是生成图灵验证码,并且返回,使用Img即可显示
@Override
public void createCaptcha(HttpServletResponse response) throws Exception {
//放置重新创建,占用redis里太多资源
//先检查上一次的id,如果有话重复利用
String lastId = CookieUtils.getCookie(getRequest(), Constants.User.LAST_CAPTCHA_ID);
String key;
if (TextUtils.isEmpty(lastId)) {
key = idWorker.nextId() + "";
} else {
key = lastId;
}
//可以用了
// 设置请求头为输出图片类型
response.setContentType("image/gif");
response.setHeader("Pragma", "No-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
int captchaType = random.nextInt(3);
Captcha targetCaptcha;
int width = 120;
int height = 40;
if (captchaType == 0) {
// 三个参数分别为宽、高、位数
targetCaptcha = new SpecCaptcha(width, height, 5);
} else if (captchaType == 1) {
// gif类型
targetCaptcha = new GifCaptcha(width, height);
} else {
// 算术类型
targetCaptcha = new ArithmeticCaptcha(width, height);
targetCaptcha.setLen(2); // 几位数运算,默认是两位
}
int index = random.nextInt(captcha_font_types.length);
log.info("captcha font type index == > " + index);
targetCaptcha.setFont(captcha_font_types[index]);
targetCaptcha.setCharType(Captcha.TYPE_DEFAULT);
String content = targetCaptcha.text().toLowerCase();
log.info("captcha content == > " + content);
//保存到redis里头
//删除时机
//1、自然过期,也就是10分钟后自己删除
//2、验证码用完以后删除
//3、用完的情况:看get的地方
//把这个id写到cookie里,后提交的时候,用于查询验证的正确性
CookieUtils.setUpCookie(response, Constants.User.LAST_CAPTCHA_ID, key);
redisUtils.set(Constants.User.KEY_CAPTCHA_CONTENT + key, content, 60 * 10);
targetCaptcha.out(response.getOutputStream());
}
图灵验证码相关的依赖
<!--图灵验证码-->
<dependency>
<groupId>com.github.whvcse</groupId>
<artifactId>easy-captcha</artifactId>
<version>1.6.2</version>
</dependency>
用户填写用户信息提交
登录接口
/**
* 登录sign-up
* <p>
* 需要提交的数据
* 1、用户账号-可以昵称,可以邮箱--->做了唯一处理
* 2、密码
* 3、图灵验证码
* 4、图灵验证的key
*
* @param captcha 图灵验证码
* @param sobUser 用户bean类,封装着账号和密码
* @return
*/
@PostMapping("/login/{captcha}/")
public ResponseResult login(@PathVariable("captcha") String captcha,
@RequestBody SobUser sobUser,
@RequestParam(value = "from", required = false) String from) {
return userAccountService.doLogin(captcha, sobUser, from);
}
相关的业务逻辑
@Override
public ResponseResult doLogin(String captcha,
SobUser sobUser, String from) {
//from可能不有值
//如果不有值,就给它一个默认值
if (TextUtils.isEmpty(from)
|| (!Constants.FROM_MOTILE.equals(from) && !Constants.FROM_PC.equals(from))) {
from = Constants.FROM_MOTILE;
}
String captchaKey = CookieUtils.getCookie(getRequest(), Constants.User.LAST_CAPTCHA_ID);
HttpServletRequest request = getRequest();
HttpServletResponse response = getResponse();
String captchaValue = (String) redisUtils.get(Constants.User.KEY_CAPTCHA_CONTENT + captchaKey);
if (!captcha.equals(captchaValue)) {
return ResponseResult.FAILED("人类验证码不正确");
}
//验证成功,删除redis里的验证码
redisUtils.del(Constants.User.KEY_CAPTCHA_CONTENT + captchaKey);
//有可能是邮箱,也有可能是用户名
String userName = sobUser.getUserName();
if (TextUtils.isEmpty(userName)) {
return ResponseResult.FAILED("账号不可以为空.");
}
String password = sobUser.getPassword();
if (TextUtils.isEmpty(password)) {
return ResponseResult.FAILED("密码不可以为空.");
}
SobUser userFromDb = userDao.findOneByUserName(userName);
if (userFromDb == null) {
userFromDb = userDao.findOneByEmail(userName);
}
if (userFromDb == null) {
return ResponseResult.FAILED("用户名或密码不正确");
}
//用户存在
//对比密码
boolean matches = bCryptPasswordEncoder.matches(password, userFromDb.getPassword());
if (!matches) {
return ResponseResult.FAILED("用户名或密码不正确");
}
//密码是正确
//判断用户状态,如果是非正常的状态,则返回结果
if (!"1".equals(userFromDb.getState())) {
return ResponseResult.ACCOUNT_DENIED();
}
//修改更新时间和登录IP
userFromDb.setLoginIp(request.getRemoteAddr());
userFromDb.setUpdateTime(new Date());
createToken(response, userFromDb, from);
CookieUtils.deleteCookie(getResponse(), Constants.User.LAST_CAPTCHA_ID);
return ResponseResult.SUCCESS("登录成功");
}
至于流程,同学们请看前面的图吧
课程详情请看课程内容
相关课程
JavaWeb实现个人博客系统-管理中心(前端) power by vue.js