前言:相信很多小伙伴在刚做程序员或者说刚学习做程序员的时候首门课程就是登陆,登陆看起来简单,其实里面也包含了很多东西。逻辑清晰,十分适合新手学习。由于种种原因我在工作很久后才接触,以前总是觉得token很难,这次终于学会了,安排!
入参:username,password(用户名和密码)
出参:token,username(token和用户名)
思路:拿到用户名,密码,给密码加密,去查数据库,根据业务判断异常信息,根据userid生成token,并把token放到redis中设置好过期时间,把username和token放到返回体中
service层:
这段代码
public AdminLoginVO loginPass(AdminLoginReq adminLoginReq) {
String name = adminLoginReq.getName();
String password = SecureUtil.md5(adminLoginReq.getPassword());// 获取密码并加密
Admin admin = adminMapper.selectOne(new LambdaQueryWrapper<Admin>().eq(Admin::getName, name));
if (null == admin) {
throw new BusinessException("系统中无此用户");
}
if (admin.getStatus().equals(AdminStatusEnum.DISABLE) || admin.getSysStatus().equals(AdminStatusEnum.DISABLE)) {
throw new BusinessException("这个用户处于禁用状态,请联系管理员");
}
if (!password.equals(admin.getPassword())) {
throw new BusinessException("密码不相符");
}
return adminLoginData(admin);
}
注意:BusinessException使我们自己项目中封装好的异常,大家用的时候可以抛RuntimeException
封装返回信息(即token和username):
talk is cheap,show your code!!
private AdminLoginVO adminLoginData(Admin admin) {
String adminToken = JwtToken.createToken(admin.getId().toString());
redisTemplate.opsForValue().set(
String.format(GODZILLA:ADMIN:TOKEN:%s, admin.getId()),//%s是占位符,这样id就拼到%s的位置上了,形成一个字符串,不会自行百度String.format
adminToken, 60 * 2,// 设置存放在redis中的时间
TimeUnit.MINUTES); // 这个是时间的单位
return new AdminLoginVO().setName(admin.getName()).setAdminToken(adminToken);
}
创建token(根据userid):
1.创建一个空map
2. map中将userid put进去
3. 声明过期时间(integer,秒为单位)
4. 调用创建token(此方法的重写)根据map和过期时间
这段代码
public static String createToken(String userId) {
Map<String, String> mapClaim = new HashMap<>();
mapClaim.put("userId", userId);
Integer expireSeconds = 2* 60 * 60;// 这里设置过期时间为两个小时
return createToken(mapClaim, expireSeconds);
}
创建token(根据map和过期时间):
奉上代码
public static String createToken(Map<String, String> map, Integer expireSeconds) {
String token = null;
Map<String, Object> mapHeader = new HashMap<>();
mapHeader.put("alg", "HS256");
mapHeader.put("typ", "JWT");
JWTCreator.Builder jwtBuilder = JWT.create();
jwtBuilder.withHeader(mapHeader);
if (map != null && !map.isEmpty()) {
Set<Map.Entry<String, String>> setEntry = map.entrySet();
for (Map.Entry<String, String> entry : setEntry) {
jwtBuilder.withClaim(entry.getKey(), entry.getValue());
}
}
if (expireSeconds != null && expireSeconds > 0) {
Calendar nowTime = Calendar.getInstance();
int calendarField = Calendar.SECOND;
nowTime.add(calendarField, expireSeconds);
Date expiresDate = nowTime.getTime();
jwtBuilder.withExpiresAt(expiresDate);
}
jwtBuilder.withIssuedAt(new Date());
try {
token = jwtBuilder.sign(Algorithm.HMAC256(“GODZILLALOGINtoken”));
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (JWTCreationException e) {
e.printStackTrace();
}
return token;
}
com.auth0</groupId>
java-jwt</artifactId>
3.8.3</version>
</dependency>
token,俗称令牌,其实就是一个字符串
JWTtoken由三部分组成,中间用“.”相隔,这三部分分别是头部,载荷、签名
头部信息:用于描述该JWT最基本的信息,如类型及签名所用的算法等
载荷:其实就是自定义的数据,如userid,过期时间(是JWT的核心,通过这些自定义的信息才可以确定唯一token)
签名: 头部和载荷分别base64加密后,拼接起来,就形成了xxx.xxx的前两段token
前两段加入一个秘钥用HS256或其他算法加密,就形成了最后一段token(所以最后一段token是在签名这形成的)
入参:token
出参:“登出成功”等……
思路:清掉redis里的token
service层:
代码
public void logout() {
String token = request.getHeader("token");
String userId = JwtToken.getUserId(token);
String key = String.format(GODZILLA:ADMIN:TOKEN:%s, userId);
redisTemplate.delete(key);
}
根据token解密出userid:
此段代码
public static String getValue(String token, String key) {
Map<String, Claim> claims = getClaims(token);
if (claims == null) {
return null;
}
Claim valueClaim = claims.get(key);
if (null == valueClaim || valueClaim.asString().equals("")) {
return null;
}
return valueClaim.asString();
}
解密token Claim(根据token):
代码奉上
public static Map<String, Claim> getClaims(String token) {
DecodedJWT jwt = verifyToken(token);
return jwt == null ? null : jwt.getClaims();
}
解密token根据token:
声明JWT对象为null,
JWT对象实例化
判断JWT对象,为null返回null,不为null就返回JWT对象
代码来了
public static DecodedJWT verifyToken(String token) {
DecodedJWT jwt = null;
try {
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET)).build();
jwt = verifier.verify(token);
} catch (Exception e) {
// e.printStackTrace(); token 校验失败, 抛出Token验证非法异常
}
return jwt == null ? null : jwt;
}
如此,登陆登出是不是清晰明了了呢,登陆光有这些还是不够的,还需要设置拦截器,只有通过登陆接口才能进去才是目的,登陆+拦截器才是完整的登陆!下篇文章讲springboot如何优雅的写出登陆拦截器,敬请期待
下一篇:springboot如何优雅写登陆拦截器