之前做了一个登录功能 就想着记录一下 也算记录一下我这个菜鸟的成长,哈哈,分享给有需要得朋友们 希望对你们有所帮助
1.首先功能拿到手中第一步肯定是创建控制器再想后面陆续的一些操作啦
@ApiOperation(value = "登录测试")
@PostMapping("/login")
public R loginTest(@RequestParam String username, @RequestParam String password) throws UnsupportedEncodingException {
//1、检查用户名秘密是否正确 TODO
//2.判断用户是否已经登录
//2.1 登录的情况下 要么放行 要么做限制只允许一端登录
//2.2没有登录情况下 则使用工具生成token验证登录(我这里使用的是的jwt)
//2.2.1 将token存redis并设置任意时间过期 我这里设置60分钟
}
既然现在主体的逻辑出来了 那就开始正式干活了咯
2.引入jwt依赖
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.18.3</version>
</dependency>
3.编写jwt的工具类
/**
* @Author mark
* @Description TODO
* @Date 2023/02/03/10:26
* @Version 1.0
*/
public class JWTUtils {
//token有效时长
private static final long EXPIRE=60*60*1000L;
//token的密钥 随便定义
private static final String SECRET="jwtTest";
public static String createToken(User user) throws UnsupportedEncodingException {
//token过期时间
Date date=new Date(System.currentTimeMillis()+EXPIRE);
//jwt的header部分
Map<String ,Object> map=new HashMap<>();
map.put("alg","HS256");
map.put("typ","JWT");
//使用jwt的api生成token 私有声明随便你加几个都可以
String token= JWT.create()
.withHeader(map)
.withClaim("username", user.getUsername())//私有声明
.withClaim("userId", String.valueOf(user.getId()))//私有声明
.withClaim("email", user.getEmail())//私有声明
.withExpiresAt(date)//过期时间
.withIssuedAt(new Date())//签发时间
.sign(Algorithm.HMAC256(SECRET));//签名
return token;
}
/**
* 校验token的有效性
* 1 token的header和payload是否没改过
* 2 没有过期
*/
public static boolean verifyToken(String token){
try {
//解密
JWTVerifier verifier= JWT.require(Algorithm.HMAC256(SECRET)).build();
verifier.verify(token);
return true;
}catch (Exception e){
return false;
}
}
//无需解密也可以获取token的信息
public static String getUsername(String token){
try {
DecodedJWT jwt = JWT.decode(token);
return jwt.getClaim("username").asString();
} catch (JWTDecodeException e) {
return null;
}
}
}
4.开始填充主体逻辑了
@Resource
private UserService userService;
@ApiOperation(value = "登录")
@PostMapping("/loginWeiYuManager")
public R loginTest(@RequestParam String username, @RequestParam String password) throws UnsupportedEncodingException {
//1、检查用户名秘密是否正确
User user=userService.getUserByPass(username, password);
if(user == null){
return R.Failed("用户名或密码错误");
}
//2.判断用户是否已经登录
String loginKey =redisTempLate.get(CacheKeys.getUserLogin(user.getPhone()));
//2.1 登录的情况下 要么放行 要么做限制只允许一端登录---这里就做一下限制吧
if (loginKey==null){ //2.2没有登录情况下 则使用工具生成token验证登录(我这里使用的是的jwt)
String token= JWTUtil.createToken(user);
log.info("用户 {} 登录系统",username);
//将token存入redis缓存中:并设置过期时间1小时
redisCacheManager.set(CacheKeys.getUserLogin(user.getPhone()),token,3600);
return Response.OK("登陆成功",token);//返回token给前端
}else {
return R.Failed("你已在其他设备登陆");
}
5.做全局Token拦截
@Component
public class TokenInterceptor implements HandlerInterceptor {
@Autowired
private RedisTempLate redisTempLate ;
@Override//在controller前拦截
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String tokenRequest = request.getHeader("Authorization");//请求参数Authorization 令牌=token
HashMap<String,Object> map = new HashMap();
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json");
if (StringUtils.isBlank(tokenRequest)) {
map.put("code",StatusCode.NO_EXIST.code);
map.put("msg","未获取到token, 请重新登录!");
map.put("data",null);
response.getWriter().write(JSON.toJSONString(map));
response.getWriter().close();
return false;//中断请求
}
User user = new User();
String username= JWT.decode(tokenRequest).getClaim("username").asString();
String userId =JWT.decode(tokenRequest).getClaim("userId").asString();
String email= JWT.decode(tokenRequest).getClaim("email").asString();
if (username!=null){user.setUsername(username);}
if (userId!=null){user.setId(Integer.parseInt(userId));}
if (email!=null){user.setEmail(email);}
if (newToken==null ||!newToken.equals(tokenRequest)){
map.put("code", StatusCode.EXPIRE.code);
map.put("msg","token已过期,重新登录!");
map.put("data",null);
response.getWriter().write(JSON.toJSONString(map));
response.getWriter().close();
return false;
}
if(user==null){
map.put("code",StatusCode.AUTH_VERIFY_FAILED.code);
map.put("msg","权限验证失败, 请重新登录!");
map.put("data",null);
response.getWriter().write(JSON.toJSONString(map));
response.getWriter().close();
return false;
}
// 将用户放在threadLocal中 并续期
UserHolder.saveUser(user);
DecodedJWT decode = JWT.decode(tokenRequest);
Date expiresAt = decode.getExpiresAt();
long times = expiresAt.getTime();
long minute=(times- System.currentTimeMillis())/(60*1000);
// 还有10分钟过期时就更新token
if (minute>0 && minute<10){
String token = JWTUtil.createToken(user);
redisTempLate.set(CacheKeys.getUserLogin(phone),token,1800);
response.addHeader("Authorization",token);
}
if (minute<0){
map.put("code",StatusCode.EXPIRE.code);
map.put("msg","token已过期,重新登录!");
map.put("data",null);
response.getWriter().write(JSON.toJSONString(map));
response.getWriter().close();
return false;
}
// 放行
return true;
}
@Override//响应结束 threadLocal移除对象
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
UserHolder.removeUser(); //移除对象
}
}
6.登录拦截
/**
* @Author mark
* @Description TODO
* @Date 2023/02/03/11:39
* @Version 1.0
*/
@Slf4j
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Resource
private UserService userService;
/**
* 目标方法执行前
* 该方法在控制器处理请求方法前执行,其返回值表示是否中断后续操作
* 返回 true 表示继续向下执行,返回 false 表示中断后续操作
* @return
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("Authorization");
// 如果不是映射到方法直接通过
if (!(handler instanceof HandlerMethod)) {
return true;
}
// 执行认证
if (token == null) {
throw new RuntimeException("无token,请重新登录或联系管理员");
}
// 获取 token 中的 username
String userId;
try {
userId= JWT.decode(token).getClaim("userId").asString();
} catch (JWTDecodeException j) {
throw new RuntimeException("token不正确");
}
//查询数据库,看看是否存在此用户
User user=userService.getUserByUserId(userId);
if (user == null) {
throw new RuntimeException("此用户不存在,请重新登录或联系管理员");
}
// 验证 token
if (JWTUtils.verifyToken(token)) {
return true;
} else {
throw new RuntimeException("token过期或不正确,请重新登录或联系管理员");
}
}
@Override
public void postHandle (HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView
modelAndView) throws Exception {
}
@Override
public void afterCompletion (HttpServletRequest request, HttpServletResponse response, Object handler, Exception
ex) throws Exception {
}
}
7.增加配置类 可以配置一些白名单之类的
@Configuration
public class AuthMvcConfig implements WebMvcConfigurer {
@Resource
private TokenInterceptor tokenInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
//注册自己的拦截器,并设置拦截的请求路径
registry.addInterceptor(tokenInterceptor)
//拦截此请求路径的请求
.addPathPatterns("/*/*")
//白名单
.excludePathPatterns("/login/login");
}
}
到此就大功告成了 可以检验一下成果 账号 密码 自己再数据库自己添加就行啦 过程就不放上来啦~~~
如果做了限制只允许一端登录也成功了
退出登录只要删除redis缓存就可以了 就不做演示啦,喜欢的朋友可以自己动起小手试一试哈