1、首先最基础的User实体类,使用了lombok,所以省略了getter、setter方法
@Data
public class UserInfo implements Serializable {
private Integer id;
//用户名
private String username;
//密码不需要被序列化存入redis
private transient String password;
//登录过期时间
private Long expireTime;
//登录成功后的token
private String token;
//权限集
private Set<String> permissions;
public UserInfo() {
}
public UserInfo(String username, String password) {
this.username = username;
this.password = password;
}
public UserInfo(int id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
}
2、redis基础操作,登录成功后,信息保存到redis中
@Component
public class RedisCache {
@Autowired
public RedisTemplate redisTemplate;
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
*/
public <T> void setCacheObject(final String key, final T value)
{
redisTemplate.opsForValue().set(key, value);
}
/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public <T> T getCacheObject(final String key)
{
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
}
/**
* 缓存带有过期时间的对象
* @param key 缓存的键值
* @param value 缓存的值
* @param time 过期时间
* @param timeUnit 时间单位
*/
public <T> void setCacheObjectExpire(final String key, final T value, final Long time, final TimeUnit timeUnit){
redisTemplate.opsForValue().set(key,value,time,timeUnit);
}
}
3、登录成功后token相关处理
@Component
public class JwtTokenService {
@Autowired
RedisCache redisCache;
//前端请求token对应的header中的key
private final static String TOKEN_KEY = "C-Token";
//jwt加密密钥
private final static String SIGNING_KEY = "xice202304181537";
//密码过期时间
private final static Long EXPIRE_TIME = 30L;
//密码过期时间单位
private final static TimeUnit EXPIRE_TIME_UNIT = TimeUnit.MINUTES;
//token刷新时间间隔
private final static Long REFRESH_EXPIRE_TIME = 20 * 60 * 1000L;
/**
* 登录成功,生成token
* @param userInfo
* @return
*/
public String createToken(UserInfo userInfo){
//生成uuid,用着redis的key、token
String uuid = UUID.randomUUID().toString().replaceAll("-", "");
//生成token
Map claims = new HashMap(1);
claims.put(TOKEN_KEY,uuid);
String token = Jwts.builder()
.setClaims(claims)
.signWith(SignatureAlgorithm.HS512, SIGNING_KEY).compact();
//保存token
userInfo.setToken(token);
//这是过期时间
userInfo.setExpireTime(System.currentTimeMillis() + REFRESH_EXPIRE_TIME * 2);
//获取权限资源,此处应该查询数据库,方便测试,直接写死
Set<String> permissions = new HashSet<>();
permissions.add("/user/getUserInfo");
userInfo.setPermissions(permissions);
//存入redis
redisCache.setCacheObjectExpire(uuid,userInfo,EXPIRE_TIME,EXPIRE_TIME_UNIT);
return token;
}
/**
* 根据token获取用户信息
* @param request
* @return
*/
public UserInfo getUserByToken(HttpServletRequest request){
String token = request.getHeader(TOKEN_KEY);
Claims claims = Jwts.parser()
.setSigningKey(SIGNING_KEY)
.parseClaimsJws(token)
.getBody();
//解密token
String uuid = (String) claims.get(TOKEN_KEY);
//通过uuid获取redis中存的用户信息
return redisCache.getCacheObject(uuid);
}
/**
* 刷新token
* @param userInfo
*/
public void refreshToken(UserInfo userInfo){
long now = System.currentTimeMillis();
//当过期时间与当时时间小于指定的刷新时间间隔是,延长redis中信息的时间
if(userInfo.getExpireTime() - now <= REFRESH_EXPIRE_TIME){
System.out.println("刷新token...");
redisCache.setCacheObjectExpire(userInfo.getToken(),userInfo,EXPIRE_TIME,EXPIRE_TIME_UNIT);
}
}
}
4、声明注解,只有加了自定义注解的方法,才进行权限验证
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CAuth {
}
5、使用HandlerInterceptor拦截器进行权限验证,详细方法
@Configuration
public class AuthConfig implements HandlerInterceptor {
@Autowired
JwtTokenService tokenService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//静态资源不拦截
if(handler instanceof ResourceHttpRequestHandler){
return true;
}
//获取注解
Annotation[] annotations = ((HandlerMethod) handler).getMethod().getDeclaredAnnotations();
for (Annotation annotation : annotations) {
//添加了CAuth注解的方法需要登录后才能访问
if(annotation.annotationType().isAssignableFrom(CAuth.class)){
//查看是否登录
UserInfo loginUser = tokenService.getUserByToken(request);
if(ObjectUtils.isEmpty(loginUser)){
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.print("请先登录!");
writer.flush();
writer.close();
return false;
}else{
//判断是否有权限访问当前资源
String requestURI = request.getRequestURI();
return loginUser.getPermissions().stream().allMatch(perm -> {
if (requestURI.equalsIgnoreCase(perm)) {
return true;
}
response.setContentType("text/html;charset=utf-8");
PrintWriter writer = null;
try {
writer = response.getWriter();
writer.print("您没有权限进行此操作!");
writer.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
writer.close();
}
return false;
});
}
}
}
return true;
}
}
6、添加拦截器
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
AuthConfig authConfig;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authConfig);
}
}
7、登录和验证方法
@RestController
@RequestMapping("user")
public class UserInfoController {
@Autowired
JwtTokenService jwtTokenService;
@RequestMapping("login")
public String login(@RequestBody UserInfo userInfo){
if("xice".equals(userInfo.getUsername())&&"123456".equals(userInfo.getPassword())){
String token = jwtTokenService.createToken(userInfo);
return token;
}else{
return "用户名或密码不正确!";
}
}
@CAuth
@RequestMapping("getUserInfo")
public UserInfo getUserInfo(HttpServletRequest request){
UserInfo userInfo = jwtTokenService.getUserByToken(request);
jwtTokenService.refreshToken(userInfo);
return userInfo;
}
@CAuth
@RequestMapping("test")
public void test(HttpServletRequest request){
System.out.println("test。。。");
}
}
登录请求 /user/login 没有添加 @CAuth注解,所以不会进行拦截,登录成功后返回一个token值,后续请求需要在header中增加C-Token:token参数
/user/getUserInfo:登陆后有此资源权限,可以获取到当前用户信息
/user/test:无此资源权限,会返回 “您没有权限进行此操作!”