登陆
- 根据文档说明了登陆的时候的接口、请求方式以及请求的参数(String account、String password)
controller
- 新创建一个LoginController,专门负责登陆功能
@RestController
@RequestMapping("login")
public class LoginController {
@Autowired
private LoginService loginService;
@PostMapping
public Result login(@RequestBody LoginParam loginParam){
return loginService.login(loginParam);
}
}
@Data
public class LoginParam {
private String account;
private String password;
}
- 根据返回数据的形式,返回的是一个token,要想到JWT
JWT
- 什么是JWT?
- JWT就理解为一个用户登陆令牌,在用户登陆的时候,可以生成一个加密的TOKEN,同时把这个TOKEN发给用户端。
- 请求需要登陆的资源或者接口的时候,要将TOKEN携带上,后端去验证token是否合法。
- JWT的组成
- Header,{"type":"JWT","alg":"HS256"} 固定
- playload,存放信息,比如,用户id,过期时间等等,可以被解密,不能存放敏感信息
- 签名,Header和playload加上秘钥加密而成,只要秘钥不丢失,就可以认为是安全的。
JWT验证主要就是验证签名部分是否合法
导入依赖包
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
编写JWT的工具类
JWTUtils
public class JWTUtils {
private static final String jwtToken = "123456Mszlu!@#$$";
public static String createToken(Long userId){
Map<String,Object> claims = new HashMap<>();
claims.put("userId",userId);
JwtBuilder jwtBuilder = Jwts.builder()
.signWith(SignatureAlgorithm.HS256, jwtToken) // 签发算法,秘钥为jwtToken
.setClaims(claims) // body数据,要唯一,自行设置
.setIssuedAt(new Date()) // 设置签发时间
.setExpiration(new Date(System.currentTimeMillis() + 24 * 60 * 60 * 1000));// 一天的有效时间
String token = jwtBuilder.compact();
return token;
}
public static Map<String, Object> checkToken(String token){
try {
Jwt parse = Jwts.parser().setSigningKey(jwtToken).parse(token);
return (Map<String, Object>) parse.getBody();
}catch (Exception e){
e.printStackTrace();
}
return null;
}
}
LoginService
- 为什么不适用SysuserService?
- 因为每个表有每个表对应的一套流程,所以对于这个登录操作来说,应该新建立一个登陆的service专门来解决登陆的功能。
public interface LoginService {
/**
* 登录
* @param loginParam
* @return
*/
Result login(LoginParam loginParam);
}
LoginServiceImpl
- 分析在实现类的具体动作
- 根据传入的登陆参数,可以获取到用户账户、密码。
- 密码不能以明文的形式报文在数据库中,要使用算法进行加密,加密算法为MD5加盐。
- 加密完成之后,要去用户表中找,根据账户和密码
- 如果返回为空,那么就返回账户名密码不存在的信息。
- 返回不为空,那么说明有该账户,使用JWT去生成一个token,同时将token保存到redis中,根据返回数据看,最后返回的也是token。
- 这里因为是登陆设计到了用户表,肯定需要sysUserService
@Service
public class LoginServiceImpl implements LoginService {
private static final String slat = "mszlu!@#";
@Autowired
private SysUserService sysUserService;
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Override
public Result login(LoginParam loginParam) {
String account = loginParam.getAccount();
String password = loginParam.getPassword();
if (StringUtils.isBlank(account) || StringUtils.isBlank(password)){
return Result.fail(ErrorCode.PARAMS_ERROR.getCode(),ErrorCode.PARAMS_ERROR.getMsg());
}
String pwd = DigestUtils.md5Hex(password + slat);
SysUser sysUser = sysUserService.findUser(account,pwd);
if (sysUser == null){
return Result.fail(ErrorCode.ACCOUNT_PWD_NOT_EXIST.getCode(),ErrorCode.ACCOUNT_PWD_NOT_EXIST.getMsg());
}
//登录成功,使用JWT生成token,返回token和redis中
String token = JWTUtils.createToken(sysUser.getId());
redisTemplate.opsForValue().set("TOKEN_"+token, JSON.toJSONString(sysUser),1, TimeUnit.DAYS);
return Result.success(token);
}
public static void main(String[] args) {
System.out.println(DigestUtils.md5Hex("admin"+slat));
}
}
SysUserService
//根据用户名和密码查找用户
SysUser findUser(String account, String password);
SysUserServiceImpl
@Override
public SysUser findUser(String account, String password) {
LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SysUser::getAccount,account);
wrapper.eq(SysUser::getPassword, password);
wrapper.select(SysUser::getAccount, SysUser::getId,SysUser::getAvatar,SysUser::getNickname);
// 加快查询策略
wrapper.last("limit 1");
return sysUserMapper.selectOne(wrapper);
}
redis
- 为什么要将token保存到redis?
(1)token具有时效性,用redis处理有优势
(2)登陆信息一般不需要长效存储
(3)如果出现跨设备了,设备A登陆收到一个TOken,设备B登陆收到一个Token,这样两个设备可以同时在同一个应用上进行登陆,不合理。使用redis限制。即:单点登陆
- redis的一些配置
application.properties
spring.redis.host=localhost
spring.redis.port=6379
- 同时要下载安装redis
redis
统一异常错误码
- 这是干什么用的?前面不是有定义统一返回结果了嘛?
- 前面的统一返回结果状态码是用来前后端的。这里的异常码是用来处理登陆的时候的异常,比如可能出现的,传入的参数有问题、token过期、用户名或者密码不存在等问题
public enum ErrorCode {
PARAMS_ERROR(10001,"参数有误"),
ACCOUNT_PWD_NOT_EXIST(10002,"用户名或密码不存在"),
NO_PERMISSION(70001,"无访问权限"),
SESSION_TIME_OUT(90001,"会话超时"),
NO_LOGIN(90002,"未登录"),;
private int code;
private String msg;
ErrorCode(int code, String msg){
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
- 前端获取到了token之后,会存储在storage中H5(本地存储)