初步完成了Redis情况下的登录、登出和Token验证,*此处有重大注意事项:拦截器请使用Bean注入,否则redis无法注入!!!

master
白封羽 2022-06-29 14:15:45 +08:00
parent 2c9a880c23
commit 132abb270a
6 changed files with 99 additions and 41 deletions

View File

@ -3,11 +3,13 @@ package cn.edu.hfut.rmdjzz.projectmanagement.config;
import cn.edu.hfut.rmdjzz.projectmanagement.interceptor.CorsInterceptor; import cn.edu.hfut.rmdjzz.projectmanagement.interceptor.CorsInterceptor;
import cn.edu.hfut.rmdjzz.projectmanagement.interceptor.TokenInterceptor; import cn.edu.hfut.rmdjzz.projectmanagement.interceptor.TokenInterceptor;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.security.Permission;
/** /**
* @author * @author
@ -15,17 +17,17 @@ import javax.annotation.Resource;
*/ */
@Configuration @Configuration
public class WebConfig implements WebMvcConfigurer { public class WebConfig implements WebMvcConfigurer {
@Autowired @Bean
private CorsInterceptor corsInterceptor; public CorsInterceptor getCorsInterceptor(){return new CorsInterceptor();}
@Bean
public TokenInterceptor getTokenInterceptor(){return new TokenInterceptor();}
@Override @Override
public void addInterceptors(InterceptorRegistry registry) { public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(corsInterceptor).addPathPatterns("/**"); registry.addInterceptor(getCorsInterceptor()).addPathPatterns("/**");
registry.addInterceptor(new TokenInterceptor()) registry.addInterceptor(getTokenInterceptor())
.addPathPatterns("/**") .addPathPatterns("/**")
.excludePathPatterns("/staff/login") .excludePathPatterns("/staff/login")
.excludePathPatterns("/hello") .excludePathPatterns("/hello");
.excludePathPatterns("/staff/hello2");
} }
/* /*
@Override @Override

View File

@ -1,6 +1,7 @@
package cn.edu.hfut.rmdjzz.projectmanagement.controller; package cn.edu.hfut.rmdjzz.projectmanagement.controller;
import cn.edu.hfut.rmdjzz.projectmanagement.entity.Staff; import cn.edu.hfut.rmdjzz.projectmanagement.entity.Staff;
import cn.edu.hfut.rmdjzz.projectmanagement.exception.TokenException;
import cn.edu.hfut.rmdjzz.projectmanagement.service.impl.StaffServiceImpl; import cn.edu.hfut.rmdjzz.projectmanagement.service.impl.StaffServiceImpl;
import cn.edu.hfut.rmdjzz.projectmanagement.utils.response.ResponseMap; import cn.edu.hfut.rmdjzz.projectmanagement.utils.response.ResponseMap;
import lombok.SneakyThrows; import lombok.SneakyThrows;
@ -10,6 +11,8 @@ import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
/** /**
* @author * @author
* created at 2022/6/28 21:59 * created at 2022/6/28 21:59
@ -26,8 +29,14 @@ public class StaffController {
return staffServiceImpl.login(staff.getStaffUsername(), staff.getStaffPassword()); return staffServiceImpl.login(staff.getStaffUsername(), staff.getStaffPassword());
} }
@RequestMapping("/hello2") @SneakyThrows
public ResponseMap hello2(@RequestBody Staff str) { @PostMapping("/logout")
return ResponseMap.ofSuccess("adada", str); public ResponseMap logout(HttpServletRequest request) {
String token= request.getHeader("Token");
if(staffServiceImpl.logout(token))
return ResponseMap.ofSuccess("操作成功");
else{
throw new TokenException("操作失败");
}
} }
} }

View File

@ -7,6 +7,9 @@ import cn.edu.hfut.rmdjzz.projectmanagement.utils.response.ResponseMap;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod; import org.springframework.web.method.HandlerMethod;
@ -16,6 +19,8 @@ import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/** /**
* @author * @author
@ -24,30 +29,32 @@ import java.io.IOException;
//FIXME: objectMapper去掉 //FIXME: objectMapper去掉
@Component @Component
public class TokenInterceptor implements HandlerInterceptor { public class TokenInterceptor implements HandlerInterceptor {
@Resource
private RedisTemplate<Integer,String> redisTemplate;
@Override @Override
public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws TokenException, IOException { public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object object) throws TokenException, IOException {
System.out.println(httpServletRequest.getRequestURL()+" "+httpServletRequest.getMethod()); System.out.println(httpServletRequest.getRequestURL()+" "+httpServletRequest.getMethod());
throw new TokenException("in token");
/*if (!(object instanceof HandlerMethod)) {
return false;
}
String token = httpServletRequest.getHeader("Token"); String token = httpServletRequest.getHeader("Token");
if (null == token || "".equals(token.trim())) { if (null == token || "".equals(token.trim())) {
ResponseMap res= ResponseMap.of(HttpStatus.UNAUTHORIZED.value(),"缺少Token"); throw new TokenException("缺少Token");
httpServletResponse.getWriter().print(objectMapper.writeValueAsString(res));
return false;
} }
if(!TokenUtils.checkToken(token)){ if(!TokenUtils.checkToken(token)){
ResponseMap res= ResponseMap.of(HttpStatus.UNAUTHORIZED.value(),"无效的Token"); throw new TokenException("无效的Token");
httpServletResponse.getWriter().print(objectMapper.writeValueAsString(res));
return false;
} }
if(TokenUtils.checkTimeOut(token)){ if(TokenUtils.checkTimeOut(token)){
ResponseMap res= ResponseMap.of(HttpStatus.UNAUTHORIZED.value(),"Token已过期"); throw new TokenException("Token已过期");
httpServletResponse.getWriter().print(objectMapper.writeValueAsString(res));
return false;
} }
httpServletResponse.setHeader("Token",TokenUtils.autoRequire(token)); System.out.println(Objects.requireNonNull(TokenUtils.getStaffId(token)));
return true;*/ System.out.println(token);
if(!token.equals(redisTemplate.opsForValue().get(Objects.requireNonNull(TokenUtils.getStaffId(token))))){
throw new TokenException("请重新登录");
}
String newToken=TokenUtils.autoRequire(token);
if(!newToken.equals(token)){
redisTemplate.opsForValue().set(Objects.requireNonNull(TokenUtils.getStaffId(token)),
token,Objects.requireNonNull(TokenUtils.getDuration(token)), TimeUnit.SECONDS);
}
httpServletResponse.setHeader("Token",newToken);
return true;
} }
} }

View File

@ -2,6 +2,7 @@ package cn.edu.hfut.rmdjzz.projectmanagement.service;
import cn.edu.hfut.rmdjzz.projectmanagement.entity.Staff; import cn.edu.hfut.rmdjzz.projectmanagement.entity.Staff;
import cn.edu.hfut.rmdjzz.projectmanagement.exception.BadRequestException; import cn.edu.hfut.rmdjzz.projectmanagement.exception.BadRequestException;
import cn.edu.hfut.rmdjzz.projectmanagement.exception.TokenException;
import cn.edu.hfut.rmdjzz.projectmanagement.utils.response.ResponseMap; import cn.edu.hfut.rmdjzz.projectmanagement.utils.response.ResponseMap;
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
@ -10,5 +11,6 @@ import com.baomidou.mybatisplus.extension.service.IService;
* @since 2022/6/28 17:28 * @since 2022/6/28 17:28
*/ */
public interface IStaffService extends IService<Staff> { public interface IStaffService extends IService<Staff> {
ResponseMap login(String username, String password) throws BadRequestException; ResponseMap login(String username, String password) throws BadRequestException, TokenException;
Boolean logout(String token) throws TokenException;
} }

View File

@ -2,6 +2,7 @@ package cn.edu.hfut.rmdjzz.projectmanagement.service.impl;
import cn.edu.hfut.rmdjzz.projectmanagement.entity.Staff; import cn.edu.hfut.rmdjzz.projectmanagement.entity.Staff;
import cn.edu.hfut.rmdjzz.projectmanagement.exception.BadRequestException; import cn.edu.hfut.rmdjzz.projectmanagement.exception.BadRequestException;
import cn.edu.hfut.rmdjzz.projectmanagement.exception.TokenException;
import cn.edu.hfut.rmdjzz.projectmanagement.mapper.StaffMapper; import cn.edu.hfut.rmdjzz.projectmanagement.mapper.StaffMapper;
import cn.edu.hfut.rmdjzz.projectmanagement.service.IStaffService; import cn.edu.hfut.rmdjzz.projectmanagement.service.IStaffService;
import cn.edu.hfut.rmdjzz.projectmanagement.utils.TokenUtils; import cn.edu.hfut.rmdjzz.projectmanagement.utils.TokenUtils;
@ -9,35 +10,62 @@ import cn.edu.hfut.rmdjzz.projectmanagement.utils.response.ResponseMap;
import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils; import org.springframework.util.DigestUtils;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/** /**
* @author * @author
* @since 2022/6/28 17:29 * @since 2022/6/28 17:29
*/ */
@Service @Service
public class StaffServiceImpl extends ServiceImpl<StaffMapper, Staff> implements IStaffService { public class StaffServiceImpl extends ServiceImpl<StaffMapper, Staff> implements IStaffService {
private static final Long tokenDuration=5*60*60L;
@Resource
private RedisTemplate<Integer,String> redisTemplate;
@Override @Override
public ResponseMap login(String username, String password) throws BadRequestException { public ResponseMap login(String staffUsername, String password) throws BadRequestException, TokenException {
if (username == null || username.trim().length() == 0) if (staffUsername == null || staffUsername.trim().length() == 0)
throw new BadRequestException("用户名为空"); throw new BadRequestException("用户名为空");
else if (!username.equals(username.replaceAll("[^a-zA-Z0-9]", ""))) else if (!staffUsername.equals(staffUsername.replaceAll("[^a-zA-Z0-9]", "")))
throw new BadRequestException("用户名格式错误"); throw new BadRequestException("用户名格式错误");
else if (password == null || password.trim().length() != 32) else if (password == null || password.trim().length() != 32)
throw new BadRequestException("密码格式错误"); throw new BadRequestException("密码格式错误");
Staff staff = getOne(Wrappers.<Staff>lambdaQuery().eq(Staff::getStaffUsername, username)); Staff staff = getOne(Wrappers.<Staff>lambdaQuery().eq(Staff::getStaffUsername, staffUsername));
if (staff == null) if (staff == null)
throw new BadRequestException("用户不存在"); throw new BadRequestException("用户不存在");
password = DigestUtils.md5DigestAsHex((password + staff.getStaffSalt()).getBytes()); password = DigestUtils.md5DigestAsHex((password + staff.getStaffSalt()).getBytes());
if (!staff.getStaffPassword().equals(password)) if (!staff.getStaffPassword().equals(password))
throw new BadRequestException("密码错误"); throw new BadRequestException("密码错误");
String token=TokenUtils.getToken(staff.getStaffUsername(), staff.getStaffId(),tokenDuration);
redisTemplate.opsForValue().set(Objects.requireNonNull(TokenUtils.getStaffId(token)),
token,Objects.requireNonNull(TokenUtils.getDuration(token)), TimeUnit.SECONDS);
return ResponseMap.ofSuccess("ok") return ResponseMap.ofSuccess("ok")
.put("Token", TokenUtils.getToken(staff.getStaffUsername(), staff.getStaffId())) .put("Token",token)
.put("staffUsername", username) .put("staffUsername", staffUsername)
.put("staffFullname", staff.getStaffFullname()) .put("staffFullname", staff.getStaffFullname())
.put("staffId", staff.getStaffId()); .put("staffId", staff.getStaffId());
} }
@Override
public Boolean logout(String token) throws TokenException {
Integer staffId=TokenUtils.getStaffId(token);
if(staffId==null)
return false;
String remoteToken= redisTemplate.opsForValue().get(staffId);
if(!token.equals(remoteToken))
return false;
redisTemplate.delete(staffId);
return true;
}
} }

View File

@ -6,25 +6,31 @@ import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException; import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT; import com.auth0.jwt.interfaces.DecodedJWT;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Date; import java.util.Date;
import java.util.concurrent.TimeUnit;
/** /**
* @author * @author
* created at 2022/6/28 18:20 * created at 2022/6/28 18:20
*/ */
//TODO: 演示的时候把expireTime改短点儿 //TODO: 演示的时候把expireTime改短点儿
@Component
public final class TokenUtils { public final class TokenUtils {
public final static String pvKey = "SignedByRMDJZZ"; public final static String pvKey = "SignedByRMDJZZ";
public static String getToken(String staffUsername, Integer staffId,Long duration) {
public static String getToken(String username, Integer staffId) {
return JWT.create() return JWT.create()
.withClaim("username", username) .withClaim("staffUsername", staffUsername)
.withClaim("staffId", staffId) .withClaim("staffId", staffId)
.withClaim("duration",duration)
.withIssuedAt(new Date()) .withIssuedAt(new Date())
.withExpiresAt(new Date(System.currentTimeMillis() + 5 * 60 * 60 * 1000)) .withExpiresAt(new Date(System.currentTimeMillis() + duration*1000L))
.sign(Algorithm.HMAC256(pvKey)); .sign(Algorithm.HMAC256(pvKey));
} }
public static boolean checkToken(String token) throws TokenException { public static boolean checkToken(String token) throws TokenException {
@ -33,7 +39,7 @@ public final class TokenUtils {
verifier.verify(token); verifier.verify(token);
return true; return true;
} catch (JWTVerificationException e) { } catch (JWTVerificationException e) {
throw new TokenException("非法的Token"); throw new TokenException("无效的Token");
} }
} }
@ -46,7 +52,7 @@ public final class TokenUtils {
public static String getUsername(String token) throws TokenException { public static String getUsername(String token) throws TokenException {
if (!checkToken(token)) if (!checkToken(token))
return null; return null;
return JWT.decode(token).getClaim("username").asString(); return JWT.decode(token).getClaim("staffUsername").asString();
} }
public static Integer getStaffId(String token) throws TokenException { public static Integer getStaffId(String token) throws TokenException {
@ -54,9 +60,13 @@ public final class TokenUtils {
return null; return null;
return JWT.decode(token).getClaim("staffId").asInt(); return JWT.decode(token).getClaim("staffId").asInt();
} }
public static Long getDuration(String token) throws TokenException {
if (!checkToken(token))
return null;
return JWT.decode(token).getClaim("duration").asLong();
}
public static String refreshToken(String token) throws TokenException { public static String refreshToken(String token) throws TokenException {
return getToken(getUsername(token), getStaffId(token)); return getToken(getUsername(token), getStaffId(token),getDuration(token));
} }
public static String autoRequire(String token) throws TokenException { public static String autoRequire(String token) throws TokenException {