diff --git a/driver/pom.xml b/driver/pom.xml index 4a5e386..9e03c51 100644 --- a/driver/pom.xml +++ b/driver/pom.xml @@ -91,6 +91,17 @@ shiro-spring 1.9.0 + + cn.hutool + hutool-all + 5.8.0 + + + com.auth0 + java-jwt + 3.8.3 + + diff --git a/driver/src/main/java/xyz/longicorn/driver/config/AuthFilter.java b/driver/src/main/java/xyz/longicorn/driver/config/AuthFilter.java deleted file mode 100644 index d857d1f..0000000 --- a/driver/src/main/java/xyz/longicorn/driver/config/AuthFilter.java +++ /dev/null @@ -1,77 +0,0 @@ -package xyz.longicorn.driver.config; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.apache.shiro.authc.AuthenticationToken; -import org.apache.shiro.subject.Subject; -import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter; - -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; - -public class AuthFilter extends BasicHttpAuthenticationFilter { - - //判断是否允许通过 - @Override - protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { - return super.isAccessAllowed(request, response, mappedValue); - } - - //是否允许登录 - @Override - protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) { - return super.isLoginAttempt(request, response); - } - - // 创建shiro token - @Override - protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) { - return super.createToken(request, response); - } - - //isAccessAllowed为false时调用,验证失败 - @Override - protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { - this.sendChallenge(request, response); - responseError(response, "he he !"); - return false; - } - - //shiro验证成功调用 - @Override - protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception { - return super.onLoginSuccess(token, subject, request, response); - } - - @Override - protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception { - if (!isLoginAttempt(request, response)) { - responseError(response, "verify token failure"); - return false; - } - return super.preHandle(request, response); - } - - private void setCors(ServletResponse response) { - HttpServletResponse httpServletResponse = (HttpServletResponse) response; -// httpServletResponse.addHeader("Access-Control-Allow-Credentials", "true"); - httpServletResponse.addHeader("Access-Control-Allow-Origin", "*"); - httpServletResponse.addHeader("Access-Control-Allow-Methods", "*"); - httpServletResponse.addHeader("Access-Control-Allow-Headers", "*"); - } - - private void responseError(ServletResponse response, Object msg) { - setCors(response); - HttpServletResponse httpResponse = (HttpServletResponse) response; - httpResponse.setStatus(401); - httpResponse.setCharacterEncoding("UTF-8"); - httpResponse.setContentType("application/json;charset=UTF-8"); - try { - String rj = msg instanceof String ? (String) msg : new ObjectMapper().writeValueAsString(msg); - httpResponse.getWriter().append(rj); - } catch (IOException e) { - e.printStackTrace(); - } - } -} diff --git a/driver/src/main/java/xyz/longicorn/driver/config/RedisConfig.java b/driver/src/main/java/xyz/longicorn/driver/config/RedisConfig.java new file mode 100644 index 0000000..cf2c151 --- /dev/null +++ b/driver/src/main/java/xyz/longicorn/driver/config/RedisConfig.java @@ -0,0 +1,106 @@ +package xyz.longicorn.driver.config; + + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.*; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +//redis配置类 +@Configuration +public class RedisConfig { + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory factory) { + + RedisTemplate template = new RedisTemplate<>(); + // 配置连接工厂 + template.setConnectionFactory(factory); + + //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式) + Jackson2JsonRedisSerializer jackson = new Jackson2JsonRedisSerializer(Object.class); + + ObjectMapper om = new ObjectMapper(); + // 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public + om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); + // 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常 + //om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); + om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL); + jackson.setObjectMapper(om); + + // 值采用json序列化 + template.setValueSerializer(jackson); + //使用StringRedisSerializer来序列化和反序列化redis的key值 + template.setKeySerializer(new StringRedisSerializer()); + + // 设置hash key 和value序列化模式 + template.setHashKeySerializer(new StringRedisSerializer()); + template.setHashValueSerializer(jackson); + template.afterPropertiesSet(); + + return template; + } + + /** + * 对hash类型的数据操作 + * + * @param redisTemplate + * @return + */ + @Bean + public HashOperations hashOperations(RedisTemplate redisTemplate) { + return redisTemplate.opsForHash(); + } + + /** + * 对redis字符串类型数据操作 + * + * @param redisTemplate + * @return + */ + @Bean + public ValueOperations valueOperations(RedisTemplate redisTemplate) { + return redisTemplate.opsForValue(); + } + + /** + * 对链表类型的数据操作 + * + * @param redisTemplate + * @return + */ + @Bean + public ListOperations listOperations(RedisTemplate redisTemplate) { + return redisTemplate.opsForList(); + } + + /** + * 对无序集合类型的数据操作 + * + * @param redisTemplate + * @return + */ + @Bean + public SetOperations setOperations(RedisTemplate redisTemplate) { + return redisTemplate.opsForSet(); + } + + /** + * 对有序集合类型的数据操作 + * + * @param redisTemplate + * @return + */ + @Bean + public ZSetOperations zSetOperations(RedisTemplate redisTemplate) { + return redisTemplate.opsForZSet(); + } + +} + + diff --git a/driver/src/main/java/xyz/longicorn/driver/config/ShiroConfig.java b/driver/src/main/java/xyz/longicorn/driver/config/ShiroConfig.java deleted file mode 100644 index 8fb2505..0000000 --- a/driver/src/main/java/xyz/longicorn/driver/config/ShiroConfig.java +++ /dev/null @@ -1,61 +0,0 @@ -package xyz.longicorn.driver.config; - -import org.apache.shiro.mgt.DefaultSessionStorageEvaluator; -import org.apache.shiro.mgt.DefaultSubjectDAO; -import org.apache.shiro.mgt.SecurityManager; -import org.apache.shiro.realm.Realm; -import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; -import org.apache.shiro.spring.web.ShiroFilterFactoryBean; -import org.apache.shiro.web.mgt.DefaultWebSecurityManager; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import javax.servlet.Filter; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; - -@Configuration -public class ShiroConfig { - @Autowired - private UserRealm userRealm; - - //ShiroFilter过滤所有请求 - @Bean(name = "shiroFilterFactoryBean") - public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager) { - ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); - Map filterMap = new LinkedHashMap<>(); - filterMap.put("auth", new AuthFilter()); // 设置验证过滤器 - - shiroFilterFactoryBean.setFilters(filterMap); - //给ShiroFilter配置安全管理器 - shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager); - //配置系统公共资源 - Map map = new HashMap(); - //匿名资源 一定是在受限资源上面 - map.put("/api/**", "anon"); - //受限资源需要认证和授权 - map.put("/**", "auth"); - - // 设置认证界面路径 -// shiroFilterFactoryBean.setLoginUrl("/user/login"); - shiroFilterFactoryBean.setFilterChainDefinitionMap(map); - return shiroFilterFactoryBean; - } - - - @Bean - public DefaultWebSecurityManager defaultWebSecurityManager(UserRealm userRealm) { - //设置自定义Realm - DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); - securityManager.setRealm(userRealm); - //关闭shiro自带的session - DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO(); - DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator(); - defaultSessionStorageEvaluator.setSessionStorageEnabled(false); - subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator); - securityManager.setSubjectDAO(subjectDAO); - return securityManager; - } -} diff --git a/driver/src/main/java/xyz/longicorn/driver/controller/PreviewController.java b/driver/src/main/java/xyz/longicorn/driver/controller/PreviewController.java index c6af329..9ac9c06 100644 --- a/driver/src/main/java/xyz/longicorn/driver/controller/PreviewController.java +++ b/driver/src/main/java/xyz/longicorn/driver/controller/PreviewController.java @@ -16,6 +16,7 @@ import java.io.FileInputStream; // 根据文件生成预览 @Controller @RestController +@RequestMapping("/picture") public class PreviewController { @Resource private FileService fileService; diff --git a/driver/src/main/java/xyz/longicorn/driver/controller/UserController.java b/driver/src/main/java/xyz/longicorn/driver/controller/UserController.java new file mode 100644 index 0000000..6dbcc3d --- /dev/null +++ b/driver/src/main/java/xyz/longicorn/driver/controller/UserController.java @@ -0,0 +1,35 @@ +package xyz.longicorn.driver.controller; + +import cn.hutool.core.util.IdUtil; +import lombok.SneakyThrows; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import xyz.longicorn.driver.dto.ApiResult; +import xyz.longicorn.driver.dto.LoginModel; +import xyz.longicorn.driver.pojo.LoginUser; +import xyz.longicorn.driver.service.UserService; + +import javax.annotation.Resource; +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/api/user") +public class UserController { + @Resource + private UserService userService; + + // @Validated用于验证字段的值 + // @RequestBody 只能Post请求且 content-type=>application/json + @SneakyThrows + @PostMapping("/login") + public ApiResult login(@Validated @RequestBody LoginModel model) { + Thread.sleep(5); + final LoginUser user = userService.login(model.getUsername(), model.getPassword()); + + return ApiResult.success(user); + } +} diff --git a/driver/src/main/java/xyz/longicorn/driver/dao/LoginUserDao.java b/driver/src/main/java/xyz/longicorn/driver/dao/LoginUserDao.java new file mode 100644 index 0000000..d1362f8 --- /dev/null +++ b/driver/src/main/java/xyz/longicorn/driver/dao/LoginUserDao.java @@ -0,0 +1,77 @@ +package xyz.longicorn.driver.dao; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; +import org.springframework.stereotype.Component; +import xyz.longicorn.driver.pojo.LoginUser; + +import java.util.concurrent.TimeUnit; + +@Component +public class LoginUserDao { + private final String REDIS_PREFIX = "driver:user:token:"; + private final RedisTemplate redisTemplate; + + public LoginUserDao(RedisTemplate redisTemplate) { + this.redisTemplate = redisTemplate; + } + + private String buildTokenKey(String token) { + return REDIS_PREFIX + token; + } + + public void refreshToken(String token) { + refreshToken(token, 1800); + } + + public void refreshToken(String token, long timeout) { + redisTemplate.expire(buildTokenKey(token), timeout, TimeUnit.SECONDS); + } + + public LoginUser save(LoginUser user) { + return save(user, 1800); + } + + public LoginUser save(LoginUser user, long timeout) { + //更改在redis里面查看key编码问题 + RedisSerializer redisSerializer = new StringRedisSerializer(); + redisTemplate.setKeySerializer(redisSerializer); + ValueOperations vo = redisTemplate.opsForValue(); + try { + String userJson = (new ObjectMapper()).writeValueAsString(user); + vo.set(buildTokenKey(user.getToken()), userJson, timeout, TimeUnit.SECONDS);//设置key并且设置有效时间 + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + return user; + } + + public LoginUser get(String token) { + ValueOperations vo = redisTemplate.opsForValue(); + final Object o = vo.get(buildTokenKey(token)); + if (null != o) { + try { + return (new ObjectMapper()).readValue(o.toString(), LoginUser.class); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + } + return null; + } + + public boolean exists(String token) { + return redisTemplate.hasKey(buildTokenKey(token)); + } + + void deleteByToken(String token) { + redisTemplate.delete(buildTokenKey(token)); + } + + void delete(LoginUser user) { + deleteByToken(user.getToken()); + } +} diff --git a/driver/src/main/java/xyz/longicorn/driver/dao/UserInfoMapper.java b/driver/src/main/java/xyz/longicorn/driver/dao/UserInfoMapper.java new file mode 100644 index 0000000..cc50c70 --- /dev/null +++ b/driver/src/main/java/xyz/longicorn/driver/dao/UserInfoMapper.java @@ -0,0 +1,9 @@ +package xyz.longicorn.driver.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; +import xyz.longicorn.driver.pojo.UserInfo; + +@Mapper +public interface UserInfoMapper extends BaseMapper { +} diff --git a/driver/src/main/java/xyz/longicorn/driver/dto/FileItem.java b/driver/src/main/java/xyz/longicorn/driver/dto/FileItem.java index 2002c5f..4e9b25e 100644 --- a/driver/src/main/java/xyz/longicorn/driver/dto/FileItem.java +++ b/driver/src/main/java/xyz/longicorn/driver/dto/FileItem.java @@ -67,8 +67,8 @@ public class FileItem { .setType(f.getType()) // .setPath(f.getPath()) // .setThumb(f.getPath()) - .setPath("http://localhost:8080/preview?hash=" + f.getHash()) - .setThumb("http://localhost:8080/thumb?hash=" + f.getHash()) + .setPath("http://localhost:8080/picture/preview?hash=" + f.getHash()) + .setThumb("http://localhost:8080/picture/thumb?hash=" + f.getHash()) .setUpdateTime(f.getUpdateTime()); } } diff --git a/driver/src/main/java/xyz/longicorn/driver/dto/LoginModel.java b/driver/src/main/java/xyz/longicorn/driver/dto/LoginModel.java new file mode 100644 index 0000000..2925ee6 --- /dev/null +++ b/driver/src/main/java/xyz/longicorn/driver/dto/LoginModel.java @@ -0,0 +1,15 @@ +package xyz.longicorn.driver.dto; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +@Data +public class LoginModel { + @NotBlank(message = "用户名必须填写") + private String username; + @NotBlank(message = "密码必须填写") + private String password; + // 验证码 + private String code; +} diff --git a/driver/src/main/java/xyz/longicorn/driver/dto/UserInfoDto.java b/driver/src/main/java/xyz/longicorn/driver/dto/UserInfoDto.java new file mode 100644 index 0000000..5627830 --- /dev/null +++ b/driver/src/main/java/xyz/longicorn/driver/dto/UserInfoDto.java @@ -0,0 +1,4 @@ +package xyz.longicorn.driver.dto; + +public class UserInfoDto { +} diff --git a/driver/src/main/java/xyz/longicorn/driver/pojo/LoginUser.java b/driver/src/main/java/xyz/longicorn/driver/pojo/LoginUser.java new file mode 100644 index 0000000..8d77e9e --- /dev/null +++ b/driver/src/main/java/xyz/longicorn/driver/pojo/LoginUser.java @@ -0,0 +1,28 @@ +package xyz.longicorn.driver.pojo; + + +import lombok.Data; +import org.springframework.data.annotation.Id; +import org.springframework.data.redis.core.RedisHash; +import org.springframework.data.redis.core.index.Indexed; + +import java.util.Set; + +@Data +@RedisHash(value = "driver_login_user", timeToLive = 3600) // 保持名称及有效期 +public class LoginUser { + @Id + private String token; + @Indexed + private String account; + private UserInfo userInfo; + + /** + * 用户权限 + */ + private Set permissions; + /** + * 用户角色 + */ + private Set roles; +} diff --git a/driver/src/main/java/xyz/longicorn/driver/pojo/ShareInfo.java b/driver/src/main/java/xyz/longicorn/driver/pojo/ShareInfo.java new file mode 100644 index 0000000..d2231bb --- /dev/null +++ b/driver/src/main/java/xyz/longicorn/driver/pojo/ShareInfo.java @@ -0,0 +1,30 @@ +package xyz.longicorn.driver.pojo; + + +import com.baomidou.mybatisplus.annotation.TableId; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; +import lombok.experimental.Accessors; + +import java.util.Date; + +@Data +@ToString +@AllArgsConstructor +@NoArgsConstructor +@Accessors(chain = true) +public class ShareInfo { + @TableId + private String id; + private String title; + private Integer uid; + private Long fileId; + private Integer type; + private String password; + private Integer live; + private Date createTime; + private Date updateTime; + private Integer status; +} diff --git a/driver/src/main/java/xyz/longicorn/driver/pojo/UserInfo.java b/driver/src/main/java/xyz/longicorn/driver/pojo/UserInfo.java new file mode 100644 index 0000000..9189552 --- /dev/null +++ b/driver/src/main/java/xyz/longicorn/driver/pojo/UserInfo.java @@ -0,0 +1,33 @@ +package xyz.longicorn.driver.pojo; + + +import com.baomidou.mybatisplus.annotation.TableId; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; +import lombok.experimental.Accessors; + +import java.util.Date; + +@Data +@ToString +@AllArgsConstructor +@NoArgsConstructor +@Accessors(chain = true) +public class UserInfo { + + private Integer id; + private String nickname; + private String email; + private String account; + private String password; + private String salt; + private String avatar; + private Integer sex; + private String lastIp; + private Date lastLogin; + private Date createTime; + private Date updateTime; + private Integer status; +} diff --git a/driver/src/main/java/xyz/longicorn/driver/pojo/enums/Category.java b/driver/src/main/java/xyz/longicorn/driver/pojo/enums/Category.java new file mode 100644 index 0000000..3ad8827 --- /dev/null +++ b/driver/src/main/java/xyz/longicorn/driver/pojo/enums/Category.java @@ -0,0 +1,21 @@ +package xyz.longicorn.driver.pojo.enums; + +import com.baomidou.mybatisplus.annotation.EnumValue; + +/** + * 文件的分类 + */ +public enum Category { + Image(1, "图片"), Document(2, "文档"), + Video(3, "视频"), Audio(4, "音频"), + Other(5, "其他"); + + @EnumValue + private final int value; + private final String name; + + Category(int value, String name) { + this.value = value; + this.name = name; + } +} diff --git a/driver/src/main/java/xyz/longicorn/driver/service/UserService.java b/driver/src/main/java/xyz/longicorn/driver/service/UserService.java new file mode 100644 index 0000000..858e3d5 --- /dev/null +++ b/driver/src/main/java/xyz/longicorn/driver/service/UserService.java @@ -0,0 +1,53 @@ +package xyz.longicorn.driver.service; + +import cn.hutool.core.util.IdUtil; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import org.springframework.stereotype.Service; +import org.springframework.util.DigestUtils; +import org.springframework.util.StringUtils; +import xyz.longicorn.driver.config.BizException; +import xyz.longicorn.driver.dao.LoginUserDao; +import xyz.longicorn.driver.dao.UserInfoMapper; +import xyz.longicorn.driver.pojo.LoginUser; +import xyz.longicorn.driver.pojo.UserInfo; + +import javax.annotation.Resource; +import java.util.UUID; + +@Service +public class UserService extends ServiceImpl { + @Resource + private LoginUserDao loginUserDao; + + /** + * 使用用户名和密码登录 + * + * @param account + * @param password + * @return + */ + public LoginUser login(String account, String password) { + // 查询数据条件 + QueryWrapper q = new QueryWrapper(); + // 只能根据账号查询数据 + q.eq("account", account); + q.last("limit 1"); + UserInfo userInfo = baseMapper.selectOne(q); + // + if (userInfo == null) throw BizException.create("用户名不正确"); + // 获取密码并验证 + String checkPassword = password + (StringUtils.hasLength(userInfo.getSalt()) ? userInfo.getSalt() : ""); + checkPassword = DigestUtils.md5DigestAsHex(checkPassword.getBytes()); // 生成用于判断的密码 + + if (!checkPassword.equals(userInfo.getPassword())) throw BizException.create("登录密码不正确"); + // 将登录数据保存到redis 用户后续判断 + LoginUser user = new LoginUser(); + user.setAccount(account); + // 生成接口需要的token + user.setToken(IdUtil.fastSimpleUUID()); // 可以使用jwt生成token + loginUserDao.save(user); // 保存用户登录信息到redis + user.setUserInfo(userInfo); + return user; + } +} diff --git a/driver/src/main/java/xyz/longicorn/driver/shiro/AuthFilter.java b/driver/src/main/java/xyz/longicorn/driver/shiro/AuthFilter.java new file mode 100644 index 0000000..730c233 --- /dev/null +++ b/driver/src/main/java/xyz/longicorn/driver/shiro/AuthFilter.java @@ -0,0 +1,162 @@ +package xyz.longicorn.driver.shiro; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.subject.Subject; +import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.RequestMethod; +import xyz.longicorn.driver.dao.LoginUserDao; +import xyz.longicorn.driver.pojo.LoginUser; + +import javax.annotation.Resource; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +@Slf4j +public class AuthFilter extends BasicHttpAuthenticationFilter { + + private LoginUserDao loginUserDao; + + // 构造把需要的资源 由其他对象传输过来 + public AuthFilter(LoginUserDao loginUserDao){ + this.loginUserDao = loginUserDao; + } + + + private static final String AUTH_TOKEN_HEADER = "Authorization"; + + //判断是否允许通过 + @Override + protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { + log.debug("判断是否允许通过"); + try { + // 执行登录 也可以重写此方法 自行实现登录 + return executeLogin(request, response); + } catch (Exception e) { + log.error("执行允许通过异常:" + e.getMessage(), e); + //responseError(response, "auth check fail"); + return false; + } + } + + + /** + * 是否需要进行登录请求(false将不会继续后续操纵) + * 简单的判断是否存在对应的凭证 + * + * @param request + * @param response + * @return + */ + @Override + protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) { + return ((HttpServletRequest) request).getHeader(AUTH_TOKEN_HEADER) != null; + } + + /** + * 读取请求的token头信息 + * 创建shiro token + * + * @param request + * @param response + * @return + */ + @Override + protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) { + HttpServletRequest req = (HttpServletRequest) request; + String token = req.getHeader(AUTHORIZATION_HEADER); // + if (StringUtils.hasLength(token)) { + return new SimpleHeaderToken(token); + } + return null; + } + + /** + * isAccessAllowed为false时调用,验证失败 + * + * @param request + * @param response + * @return + * @throws Exception + */ + @Override + protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { + this.sendChallenge(request, response); + responseError(response, "login token check failure!"); + return false; + } + + /** + * 验证成功调用 可以将一些信息放入内存,以及更新token有效期 + * + * @param token + * @param subject + * @param request + * @param response + * @return + * @throws Exception + */ + @Override + protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, + ServletRequest request, ServletResponse response) throws Exception { + // 获取token + String tokenValue = (String)token.getPrincipal(); + // 通过token 获取登录用户信息 + final LoginUser loginUser = loginUserDao.get(tokenValue); + if(null != loginUser){ + // 更新token有效期 + loginUserDao.refreshToken(tokenValue); + return true; + } + return false; + + } + + // 前置判断 - 过滤的主要流程 + // 过滤的入口 如果返回false 表示不需要过滤 不进行后续代码执行 + @Override + protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception { + // 针对 options 直接通过 + if (((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name())) { + ((HttpServletResponse) response).setStatus(HttpStatus.OK.value()); + setCors(response); // 直接允许跨域 + return false; + } + //判断用户是否想要登入 -- 判断header中有没有 token + if (!isLoginAttempt(request, response)) { + responseError(response, "verify token failure(没有token)"); + return false; + } + // 如果只是简单判断 可以不用调用父级的preHandle 当然也就不需要重写其他的方法 + return super.preHandle(request, response); + } + + private void setCors(ServletResponse response) { + HttpServletResponse httpServletResponse = (HttpServletResponse) response; + httpServletResponse.addHeader("Access-Control-Allow-Origin", "*"); + httpServletResponse.addHeader("Access-Control-Allow-Methods", "*"); + httpServletResponse.addHeader("Access-Control-Allow-Headers", "*"); + } + + private void responseError(ServletResponse response, Object msg) { + setCors(response); + HttpServletResponse httpResponse = (HttpServletResponse) response; + httpResponse.setStatus(401); + httpResponse.setCharacterEncoding("UTF-8"); + httpResponse.setContentType("application/json;charset=UTF-8"); + try { + String rj = msg instanceof String ? "{\"code\":401,\"message\":\"" + (String) msg + "\"}" : new ObjectMapper().writeValueAsString(msg); + httpResponse.getWriter().append(rj); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/driver/src/main/java/xyz/longicorn/driver/shiro/ShiroConfig.java b/driver/src/main/java/xyz/longicorn/driver/shiro/ShiroConfig.java new file mode 100644 index 0000000..318f871 --- /dev/null +++ b/driver/src/main/java/xyz/longicorn/driver/shiro/ShiroConfig.java @@ -0,0 +1,93 @@ +package xyz.longicorn.driver.shiro; + +import org.apache.shiro.mgt.DefaultSessionStorageEvaluator; +import org.apache.shiro.mgt.DefaultSubjectDAO; +import org.apache.shiro.spring.web.ShiroFilterFactoryBean; +import org.apache.shiro.web.mgt.DefaultWebSecurityManager; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import xyz.longicorn.driver.dao.LoginUserDao; + +import javax.annotation.Resource; +import javax.servlet.Filter; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +@Configuration +public class ShiroConfig { + @Resource + private LoginUserDao loginUserDao; + + // 配置哪些资源过滤 + //ShiroFilter过滤所有请求 + @Bean + public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager) { + + ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); + + Map filterMap = new LinkedHashMap<>(); // 需要有序 + filterMap.put("auth", new AuthFilter(loginUserDao)); // 设置验证过滤器 + shiroFilterFactoryBean.setFilters(filterMap); + + //给ShiroFilter配置安全管理器 + shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager); + + //配置系统公共资源 不要用HashMap来创建Map,资源的配置有先后顺序 + Map map = new LinkedHashMap<>(); + /** + * anon---------------org.apache.shiro.web.filter.authc.AnonymousFilter 没有参数,表示可以匿名使用。 + * authc--------------org.apache.shiro.web.filter.authc.FormAuthenticationFilter 表示需要认证(登录)才能使用,没有参数 + * authcBasic---------org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter 没有参数表示httpBasic认证 + * logout-------------org.apache.shiro.web.filter.authc.LogoutFilter + * noSessionCreation--org.apache.shiro.web.filter.session.NoSessionCreationFilter + * perms--------------org.apache.shiro.web.filter.authz.PermissionAuthorizationFilter 参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,例如/admins/user/**=perms["user:add:*,user:modify:*"],当有多个参数时必须每个参数都通过才通过,想当于isPermitedAll()方法。 + * port---------------org.apache.shiro.web.filter.authz.PortFilter port[8081],当请求的url的端口不是8081是跳转到schemal://serverName:8081?queryString,其中schmal是协议http或https等,serverName是你访问的host,8081是url配置里port的端口,queryString是你访问的url里的?后面的参数。 + * rest---------------org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter 根据请求的方法,相当于/admins/user/**=perms[user:method] ,其中method为post,get,delete等。 + * roles--------------org.apache.shiro.web.filter.authz.RolesAuthorizationFilter 参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,当有多个参数时,例如admins/user/**=roles["admin,guest"],每个参数通过才算通过,相当于hasAllRoles()方法。 + * ssl----------------org.apache.shiro.web.filter.authz.SslFilter 没有参数,表示安全的url请求,协议为https + * user---------------org.apache.shiro.web.filter.authz.UserFilter 没有参数表示必须存在用户,当登入操作时不做检查 + */ + //匿名资源 一定是在受限资源上面 + // 前面时资源的路径 后面是该资源的过滤器 + map.put("/druid/**", "anon"); + map.put("/api/user/login", "anon"); + map.put("/api/user/reg", "anon"); + map.put("/api/user/forget", "anon"); + map.put("/api/user/reset", "anon"); + map.put("/swagger**", "anon"); // + map.put("/swagger-resources/**","anon"); + map.put("/v2/api-docs","anon"); + map.put("/picture/**","anon"); + //受限资源需要认证和授权 + map.put("/**", "auth"); + //filterChainDefinitions的配置顺序为自上而下,以最上面的为准 + shiroFilterFactoryBean.setFilterChainDefinitionMap(map); + + //身份认证失败,则跳转到登录页面的配置 没有登录的用户请求需要登录的页面时自动跳转到登录页面,不是必须的属性, + // 不输入地址的话会自动寻找项目web项目的根目录下的”/login.jsp”页面。 +// shiroFilterFactoryBean.setLoginUrl(""); + //登录成功默认跳转页面,不配置则跳转至”/”。 + // 如果登陆前点击的一个需要登录的页面,则在登录自动跳转到那个需要登录的页面。不跳转到此。 +// shiroFilterFactoryBean.setSuccessUrl(""); + //没有权限默认跳转的页面 +// shiroFilterFactoryBean.setUnauthorizedUrl(""); + return shiroFilterFactoryBean; + } + + + @Bean + public DefaultWebSecurityManager defaultWebSecurityManager(UserRealm userRealm) { + //设置自定义Realm + DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); + securityManager.setRealm(userRealm); + //关闭shiro自带的session + DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO(); + DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator(); + defaultSessionStorageEvaluator.setSessionStorageEnabled(false); + subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator); + securityManager.setSubjectDAO(subjectDAO); + return securityManager; + } +} diff --git a/driver/src/main/java/xyz/longicorn/driver/shiro/SimpleHeaderToken.java b/driver/src/main/java/xyz/longicorn/driver/shiro/SimpleHeaderToken.java new file mode 100644 index 0000000..3a1f23f --- /dev/null +++ b/driver/src/main/java/xyz/longicorn/driver/shiro/SimpleHeaderToken.java @@ -0,0 +1,24 @@ +package xyz.longicorn.driver.shiro; + +import org.apache.shiro.authc.AuthenticationToken; + +/** + * 简单的token + */ +public class SimpleHeaderToken implements AuthenticationToken { + private String token; + + public SimpleHeaderToken(String token) { + this.token = token; + } + + @Override + public Object getPrincipal() { + return token; + } + + @Override + public Object getCredentials() { + return token; + } +} diff --git a/driver/src/main/java/xyz/longicorn/driver/config/UserRealm.java b/driver/src/main/java/xyz/longicorn/driver/shiro/UserRealm.java similarity index 73% rename from driver/src/main/java/xyz/longicorn/driver/config/UserRealm.java rename to driver/src/main/java/xyz/longicorn/driver/shiro/UserRealm.java index 686c7b4..4d7736b 100644 --- a/driver/src/main/java/xyz/longicorn/driver/config/UserRealm.java +++ b/driver/src/main/java/xyz/longicorn/driver/shiro/UserRealm.java @@ -1,4 +1,4 @@ -package xyz.longicorn.driver.config; +package xyz.longicorn.driver.shiro; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; @@ -9,13 +9,24 @@ import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.springframework.stereotype.Component; +import xyz.longicorn.driver.dao.LoginUserDao; +import xyz.longicorn.driver.pojo.LoginUser; +import javax.annotation.Resource; import java.util.HashSet; import java.util.Set; @Component public class UserRealm extends AuthorizingRealm { +@Resource + private LoginUserDao loginUserDao; + // 判断是否支持对应请求token + @Override + public boolean supports(AuthenticationToken token) { + return token != null && token instanceof SimpleHeaderToken; + } + // 处理授权 获取凭证对该凭证赋权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { @@ -25,9 +36,13 @@ public class UserRealm extends AuthorizingRealm { SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); Set roleSet = new HashSet<>(); // 角色列表 + roleSet.add("user"); + roleSet.add("vip"); info.setRoles(roleSet); Set permsSet = new HashSet<>(); // 权限列表 + permsSet.add("query-list"); + permsSet.add("upload-file"); info.setStringPermissions(permsSet); return info; } @@ -38,7 +53,8 @@ public class UserRealm extends AuthorizingRealm { throws AuthenticationException { String credentials = (String) authenticationToken.getCredentials(); // 获取凭证 System.out.println("用户凭证:" + credentials); - //TODO 完成token的验证 返回数据库信息 + //TODO 完成token的验证(是否有效) 返回数据库信息 + return new SimpleAuthenticationInfo(credentials, credentials, getName()); } } diff --git a/driver/src/main/java/xyz/longicorn/driver/util/JWTUtil.java b/driver/src/main/java/xyz/longicorn/driver/util/JWTUtil.java new file mode 100644 index 0000000..13dfde9 --- /dev/null +++ b/driver/src/main/java/xyz/longicorn/driver/util/JWTUtil.java @@ -0,0 +1,98 @@ +package xyz.longicorn.driver.util; + + +import com.auth0.jwt.JWT; +import com.auth0.jwt.JWTVerifier; +import com.auth0.jwt.algorithms.Algorithm; +import com.auth0.jwt.exceptions.JWTCreationException; +import com.auth0.jwt.interfaces.DecodedJWT; + +import javax.servlet.http.HttpServletRequest; +import java.text.SimpleDateFormat; +import java.util.Date; + +public class JWTUtil { + + //这里的token属性配置最好写在配置文件中,这里为了方便直接写成静态属性 + public static final long EXPIRE_TIME = 60 * 60 * 1000;//token到期时间60分钟,毫秒为单位 + public static final long REFRESH_EXPIRE_TIME = 30 * 60;//RefreshToken到期时间为30分钟,秒为单位 + private static final String TOKEN_SECRET = "ljdyaidarking**3nkjnj??"; //密钥盐 + /** + * 发行人 + */ + private static final String ISSUER = "DK"; + + /** + * 生成token + * + * @param account + * @param currentTime + * @return + */ + public static String create(String account, Long currentTime) { + + String token = null; + try { + Date expireAt = new Date(currentTime + EXPIRE_TIME); + token = JWT.create() + .withIssuer(ISSUER) + .withClaim("account", account)//存放数据 + .withClaim("currentTime", currentTime) + .withExpiresAt(expireAt)//过期时间 + .sign(Algorithm.HMAC256(TOKEN_SECRET)); + } catch (IllegalArgumentException | JWTCreationException je) { + + } + return token; + } + + + /** + * @param : [token] + * @return : java.lang.Boolean + * @throws : + * @Description :token验证 + * @author : lj + * @date : 2020-1-31 22:59 + */ + public static Boolean verify(String token) throws Exception { + try { + JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(TOKEN_SECRET)) + .withIssuer(ISSUER).build();//创建token验证器 + DecodedJWT decodedJWT = jwtVerifier.verify(token); + System.out.println("认证通过:"); + System.out.println("account: " + decodedJWT.getClaim("account").asString()); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + System.out.println("过期时间:" + sdf.format(decodedJWT.getExpiresAt())); + return true; + } catch (Exception e) { + return false; + } + } + + + public static String getAccount(HttpServletRequest request) { + String token = request.getHeader("token"); + return getAccount(token); + } + + public static String getAccount(String token) { + if (token == null) return null; + try { + DecodedJWT decodedJWT = JWT.decode(token); + return decodedJWT.getClaim("account").asString(); + } catch (JWTCreationException e) { + return null; + } + } + + public static Long getCurrentTime(String token) { + try { + DecodedJWT decodedJWT = JWT.decode(token); + return decodedJWT.getClaim("currentTime").asLong(); + } catch (JWTCreationException e) { + return null; + } + } + +} diff --git a/driver/src/main/java/xyz/longicorn/driver/util/RedisUtil.java b/driver/src/main/java/xyz/longicorn/driver/util/RedisUtil.java new file mode 100644 index 0000000..328da7a --- /dev/null +++ b/driver/src/main/java/xyz/longicorn/driver/util/RedisUtil.java @@ -0,0 +1,8 @@ +package xyz.longicorn.driver.util; + +import org.springframework.stereotype.Component; + +@Component +public class RedisUtil { + +} diff --git a/driver/src/main/resources/db.sql b/driver/src/main/resources/db.sql index 04c52e3..caa8b6d 100644 --- a/driver/src/main/resources/db.sql +++ b/driver/src/main/resources/db.sql @@ -1,7 +1,7 @@ -- 编号、用户编号、目录名称、上级目录编号、目录路径、创建时间、状态 create table folder_info ( - id bigint(20) primary key auto_increment, + id bigint(20) UNSIGNED primary key auto_increment, uid int(10) null default 1, name varchar(20) not null, parent_id bigint(20) null default 0 comment '上级目录编号,0表示根目录', @@ -17,17 +17,17 @@ create table folder_info # 编号、用户编号、文件名、目录编号 、类型、大小、位置、创建时间、状态、hash create table file_info ( - id bigint(20) not null primary key auto_increment, - uid int(10) not null, - name varchar(50) not null, - hash varchar(32) not null comment '文件MD5特征码', - folder_id bigint(20) null default 0 comment '所在目录的编号,0表示根目录', - type varchar(5) null, - size int null default 0, - path varchar(500) not null comment '文件的物理存储位置,可以是本地路径也可以是一个网址', -- file://d:/a/b/c.txt http:// - create_time datetime default current_timestamp, + id bigint(20) UNSIGNED not null primary key auto_increment, + uid int(10) not null, + name varchar(50) not null, + hash varchar(32) not null comment '文件MD5特征码', + folder_id bigint(20) null default 0 comment '所在目录的编号,0表示根目录', + type varchar(5) null, + size int null default 0, + path varchar(500) not null comment '文件的物理存储位置,可以是本地路径也可以是一个网址', -- file://d:/a/b/c.txt http:// + create_time datetime default current_timestamp, update_time datetime on update current_timestamp, - status tinyint(1) default 1, + status tinyint(1) default 1, index ix_file_name (name) ); -- 编号、分享名称、用户编号、分享的数据编号、分类类型(file|folder)、提取码、分享有效期、状态 @@ -45,3 +45,19 @@ create table share_info status tinyint(1) default 1, index ix_file_name (title) ); +CREATE TABLE user_info +( + id int(10) UNSIGNED NOT NULL, + nickname varchar(60) NOT NULL COMMENT '昵称', + email varchar(60) DEFAULT '邮箱', + account varchar(60) NOT NULL COMMENT '登录账号', + password varchar(32) NOT NULL COMMENT '登录密码', + salt varchar(10) COMMENT '登录密码混淆字符', + avatar varchar(255) COMMENT '登录密码', + sex tinyint(1) default 0 comment '0:未知 1男 2女', + last_login datetime null, + last_ip varchar(50) null, + create_time datetime default current_timestamp, + update_time datetime on update current_timestamp, + status tinyint(1) default 1 +); \ No newline at end of file diff --git a/web/package-lock.json b/web/package-lock.json index 4b5f29f..b8be418 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -18,7 +18,8 @@ "vue": "^3.2.25", "vue-router": "^4.0.15", "vue-simple-context-menu": "^4.0.2", - "vue-upload-component": "^2.8.22" + "vue-upload-component": "^2.8.22", + "vuex": "^4.0.2" }, "devDependencies": { "@vitejs/plugin-vue": "^2.3.1", @@ -1213,6 +1214,17 @@ "version": "2.8.22", "resolved": "https://registry.npmmirror.com/vue-upload-component/-/vue-upload-component-2.8.22.tgz", "integrity": "sha512-AJpETqiZrgqs8bwJQpWTFrRg3i6s7cUodRRZVnb1f94Jvpd0YYfzGY4zluBqPmssNSkUaYu7EteXaK8aW17Osw==" + }, + "node_modules/vuex": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/vuex/-/vuex-4.0.2.tgz", + "integrity": "sha512-M6r8uxELjZIK8kTKDGgZTYX/ahzblnzC4isU1tpmEuOIIKmV+TRdc+H4s8ds2NuZ7wpUTdGRzJRtoj+lI+pc0Q==", + "dependencies": { + "@vue/devtools-api": "^6.0.0-beta.11" + }, + "peerDependencies": { + "vue": "^3.0.2" + } } }, "dependencies": { @@ -2020,6 +2032,14 @@ "version": "2.8.22", "resolved": "https://registry.npmmirror.com/vue-upload-component/-/vue-upload-component-2.8.22.tgz", "integrity": "sha512-AJpETqiZrgqs8bwJQpWTFrRg3i6s7cUodRRZVnb1f94Jvpd0YYfzGY4zluBqPmssNSkUaYu7EteXaK8aW17Osw==" + }, + "vuex": { + "version": "4.0.2", + "resolved": "https://registry.npmmirror.com/vuex/-/vuex-4.0.2.tgz", + "integrity": "sha512-M6r8uxELjZIK8kTKDGgZTYX/ahzblnzC4isU1tpmEuOIIKmV+TRdc+H4s8ds2NuZ7wpUTdGRzJRtoj+lI+pc0Q==", + "requires": { + "@vue/devtools-api": "^6.0.0-beta.11" + } } } } diff --git a/web/package.json b/web/package.json index aee4bfd..93ddfb6 100644 --- a/web/package.json +++ b/web/package.json @@ -18,7 +18,8 @@ "vue": "^3.2.25", "vue-router": "^4.0.15", "vue-simple-context-menu": "^4.0.2", - "vue-upload-component": "^2.8.22" + "vue-upload-component": "^2.8.22", + "vuex": "^4.0.2" }, "devDependencies": { "@vitejs/plugin-vue": "^2.3.1", diff --git a/web/src/components/FileBlockItem.vue b/web/src/components/FileBlockItem.vue index 44eaa71..9980754 100644 --- a/web/src/components/FileBlockItem.vue +++ b/web/src/components/FileBlockItem.vue @@ -9,13 +9,20 @@ file.type == 'folder' ? formatDate(file.createTime) : formatSize(file.size) }} +