集成shiro
This commit is contained in:
parent
0f7149f1b0
commit
b1ca1ace25
@ -91,6 +91,17 @@
|
|||||||
<artifactId>shiro-spring</artifactId>
|
<artifactId>shiro-spring</artifactId>
|
||||||
<version>1.9.0</version>
|
<version>1.9.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.hutool</groupId>
|
||||||
|
<artifactId>hutool-all</artifactId>
|
||||||
|
<version>5.8.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.auth0</groupId>
|
||||||
|
<artifactId>java-jwt</artifactId>
|
||||||
|
<version>3.8.3</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
<!-- <dependency>-->
|
<!-- <dependency>-->
|
||||||
<!-- <groupId>org.springframework.amqp</groupId>-->
|
<!-- <groupId>org.springframework.amqp</groupId>-->
|
||||||
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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<String, Object> redisTemplate(RedisConnectionFactory factory) {
|
||||||
|
|
||||||
|
RedisTemplate<String, Object> 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<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
|
||||||
|
return redisTemplate.opsForHash();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对redis字符串类型数据操作
|
||||||
|
*
|
||||||
|
* @param redisTemplate
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public ValueOperations<String, Object> valueOperations(RedisTemplate<String, Object> redisTemplate) {
|
||||||
|
return redisTemplate.opsForValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对链表类型的数据操作
|
||||||
|
*
|
||||||
|
* @param redisTemplate
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
|
||||||
|
return redisTemplate.opsForList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对无序集合类型的数据操作
|
||||||
|
*
|
||||||
|
* @param redisTemplate
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
|
||||||
|
return redisTemplate.opsForSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对有序集合类型的数据操作
|
||||||
|
*
|
||||||
|
* @param redisTemplate
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
|
||||||
|
return redisTemplate.opsForZSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -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<String, Filter> filterMap = new LinkedHashMap<>();
|
|
||||||
filterMap.put("auth", new AuthFilter()); // 设置验证过滤器
|
|
||||||
|
|
||||||
shiroFilterFactoryBean.setFilters(filterMap);
|
|
||||||
//给ShiroFilter配置安全管理器
|
|
||||||
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
|
|
||||||
//配置系统公共资源
|
|
||||||
Map<String, String> map = new HashMap<String, String>();
|
|
||||||
//匿名资源 一定是在受限资源上面
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -16,6 +16,7 @@ import java.io.FileInputStream;
|
|||||||
// 根据文件生成预览
|
// 根据文件生成预览
|
||||||
@Controller
|
@Controller
|
||||||
@RestController
|
@RestController
|
||||||
|
@RequestMapping("/picture")
|
||||||
public class PreviewController {
|
public class PreviewController {
|
||||||
@Resource
|
@Resource
|
||||||
private FileService fileService;
|
private FileService fileService;
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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<String, Object> 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<String, Object> 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());
|
||||||
|
}
|
||||||
|
}
|
@ -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<UserInfo> {
|
||||||
|
}
|
@ -67,8 +67,8 @@ public class FileItem {
|
|||||||
.setType(f.getType())
|
.setType(f.getType())
|
||||||
// .setPath(f.getPath())
|
// .setPath(f.getPath())
|
||||||
// .setThumb(f.getPath())
|
// .setThumb(f.getPath())
|
||||||
.setPath("http://localhost:8080/preview?hash=" + f.getHash())
|
.setPath("http://localhost:8080/picture/preview?hash=" + f.getHash())
|
||||||
.setThumb("http://localhost:8080/thumb?hash=" + f.getHash())
|
.setThumb("http://localhost:8080/picture/thumb?hash=" + f.getHash())
|
||||||
.setUpdateTime(f.getUpdateTime());
|
.setUpdateTime(f.getUpdateTime());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
package xyz.longicorn.driver.dto;
|
||||||
|
|
||||||
|
public class UserInfoDto {
|
||||||
|
}
|
@ -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<String> permissions;
|
||||||
|
/**
|
||||||
|
* 用户角色
|
||||||
|
*/
|
||||||
|
private Set<String> roles;
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
33
driver/src/main/java/xyz/longicorn/driver/pojo/UserInfo.java
Normal file
33
driver/src/main/java/xyz/longicorn/driver/pojo/UserInfo.java
Normal file
@ -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;
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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<UserInfoMapper, UserInfo> {
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
}
|
162
driver/src/main/java/xyz/longicorn/driver/shiro/AuthFilter.java
Normal file
162
driver/src/main/java/xyz/longicorn/driver/shiro/AuthFilter.java
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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<String, Filter> filterMap = new LinkedHashMap<>(); // 需要有序
|
||||||
|
filterMap.put("auth", new AuthFilter(loginUserDao)); // 设置验证过滤器
|
||||||
|
shiroFilterFactoryBean.setFilters(filterMap);
|
||||||
|
|
||||||
|
//给ShiroFilter配置安全管理器
|
||||||
|
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
|
||||||
|
|
||||||
|
//配置系统公共资源 不要用HashMap来创建Map,资源的配置有先后顺序
|
||||||
|
Map<String, String> 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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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.AuthenticationException;
|
||||||
import org.apache.shiro.authc.AuthenticationInfo;
|
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.realm.AuthorizingRealm;
|
||||||
import org.apache.shiro.subject.PrincipalCollection;
|
import org.apache.shiro.subject.PrincipalCollection;
|
||||||
import org.springframework.stereotype.Component;
|
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.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class UserRealm extends AuthorizingRealm {
|
public class UserRealm extends AuthorizingRealm {
|
||||||
|
@Resource
|
||||||
|
private LoginUserDao loginUserDao;
|
||||||
|
// 判断是否支持对应请求token
|
||||||
|
@Override
|
||||||
|
public boolean supports(AuthenticationToken token) {
|
||||||
|
return token != null && token instanceof SimpleHeaderToken;
|
||||||
|
}
|
||||||
|
|
||||||
// 处理授权 获取凭证对该凭证赋权
|
// 处理授权 获取凭证对该凭证赋权
|
||||||
@Override
|
@Override
|
||||||
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
|
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
|
||||||
@ -25,9 +36,13 @@ public class UserRealm extends AuthorizingRealm {
|
|||||||
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
|
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
|
||||||
|
|
||||||
Set<String> roleSet = new HashSet<>(); // 角色列表
|
Set<String> roleSet = new HashSet<>(); // 角色列表
|
||||||
|
roleSet.add("user");
|
||||||
|
roleSet.add("vip");
|
||||||
info.setRoles(roleSet);
|
info.setRoles(roleSet);
|
||||||
|
|
||||||
Set<String> permsSet = new HashSet<>(); // 权限列表
|
Set<String> permsSet = new HashSet<>(); // 权限列表
|
||||||
|
permsSet.add("query-list");
|
||||||
|
permsSet.add("upload-file");
|
||||||
info.setStringPermissions(permsSet);
|
info.setStringPermissions(permsSet);
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
@ -38,7 +53,8 @@ public class UserRealm extends AuthorizingRealm {
|
|||||||
throws AuthenticationException {
|
throws AuthenticationException {
|
||||||
String credentials = (String) authenticationToken.getCredentials(); // 获取凭证
|
String credentials = (String) authenticationToken.getCredentials(); // 获取凭证
|
||||||
System.out.println("用户凭证:" + credentials);
|
System.out.println("用户凭证:" + credentials);
|
||||||
//TODO 完成token的验证 返回数据库信息
|
//TODO 完成token的验证(是否有效) 返回数据库信息
|
||||||
|
|
||||||
return new SimpleAuthenticationInfo(credentials, credentials, getName());
|
return new SimpleAuthenticationInfo(credentials, credentials, getName());
|
||||||
}
|
}
|
||||||
}
|
}
|
98
driver/src/main/java/xyz/longicorn/driver/util/JWTUtil.java
Normal file
98
driver/src/main/java/xyz/longicorn/driver/util/JWTUtil.java
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package xyz.longicorn.driver.util;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class RedisUtil {
|
||||||
|
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
-- 编号、用户编号、目录名称、上级目录编号、目录路径、创建时间、状态
|
-- 编号、用户编号、目录名称、上级目录编号、目录路径、创建时间、状态
|
||||||
create table folder_info
|
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,
|
uid int(10) null default 1,
|
||||||
name varchar(20) not null,
|
name varchar(20) not null,
|
||||||
parent_id bigint(20) null default 0 comment '上级目录编号,0表示根目录',
|
parent_id bigint(20) null default 0 comment '上级目录编号,0表示根目录',
|
||||||
@ -17,7 +17,7 @@ create table folder_info
|
|||||||
# 编号、用户编号、文件名、目录编号 、类型、大小、位置、创建时间、状态、hash
|
# 编号、用户编号、文件名、目录编号 、类型、大小、位置、创建时间、状态、hash
|
||||||
create table file_info
|
create table file_info
|
||||||
(
|
(
|
||||||
id bigint(20) not null primary key auto_increment,
|
id bigint(20) UNSIGNED not null primary key auto_increment,
|
||||||
uid int(10) not null,
|
uid int(10) not null,
|
||||||
name varchar(50) not null,
|
name varchar(50) not null,
|
||||||
hash varchar(32) not null comment '文件MD5特征码',
|
hash varchar(32) not null comment '文件MD5特征码',
|
||||||
@ -45,3 +45,19 @@ create table share_info
|
|||||||
status tinyint(1) default 1,
|
status tinyint(1) default 1,
|
||||||
index ix_file_name (title)
|
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
|
||||||
|
);
|
22
web/package-lock.json
generated
22
web/package-lock.json
generated
@ -18,7 +18,8 @@
|
|||||||
"vue": "^3.2.25",
|
"vue": "^3.2.25",
|
||||||
"vue-router": "^4.0.15",
|
"vue-router": "^4.0.15",
|
||||||
"vue-simple-context-menu": "^4.0.2",
|
"vue-simple-context-menu": "^4.0.2",
|
||||||
"vue-upload-component": "^2.8.22"
|
"vue-upload-component": "^2.8.22",
|
||||||
|
"vuex": "^4.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^2.3.1",
|
"@vitejs/plugin-vue": "^2.3.1",
|
||||||
@ -1213,6 +1214,17 @@
|
|||||||
"version": "2.8.22",
|
"version": "2.8.22",
|
||||||
"resolved": "https://registry.npmmirror.com/vue-upload-component/-/vue-upload-component-2.8.22.tgz",
|
"resolved": "https://registry.npmmirror.com/vue-upload-component/-/vue-upload-component-2.8.22.tgz",
|
||||||
"integrity": "sha512-AJpETqiZrgqs8bwJQpWTFrRg3i6s7cUodRRZVnb1f94Jvpd0YYfzGY4zluBqPmssNSkUaYu7EteXaK8aW17Osw=="
|
"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": {
|
"dependencies": {
|
||||||
@ -2020,6 +2032,14 @@
|
|||||||
"version": "2.8.22",
|
"version": "2.8.22",
|
||||||
"resolved": "https://registry.npmmirror.com/vue-upload-component/-/vue-upload-component-2.8.22.tgz",
|
"resolved": "https://registry.npmmirror.com/vue-upload-component/-/vue-upload-component-2.8.22.tgz",
|
||||||
"integrity": "sha512-AJpETqiZrgqs8bwJQpWTFrRg3i6s7cUodRRZVnb1f94Jvpd0YYfzGY4zluBqPmssNSkUaYu7EteXaK8aW17Osw=="
|
"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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,8 @@
|
|||||||
"vue": "^3.2.25",
|
"vue": "^3.2.25",
|
||||||
"vue-router": "^4.0.15",
|
"vue-router": "^4.0.15",
|
||||||
"vue-simple-context-menu": "^4.0.2",
|
"vue-simple-context-menu": "^4.0.2",
|
||||||
"vue-upload-component": "^2.8.22"
|
"vue-upload-component": "^2.8.22",
|
||||||
|
"vuex": "^4.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^2.3.1",
|
"@vitejs/plugin-vue": "^2.3.1",
|
||||||
|
@ -9,13 +9,20 @@
|
|||||||
file.type == 'folder' ? formatDate(file.createTime) : formatSize(file.size)
|
file.type == 'folder' ? formatDate(file.createTime) : formatSize(file.size)
|
||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
|
<el-image-viewer v-if="imageViewer.show"
|
||||||
|
:url-list="imageViewer.previewSrcList"
|
||||||
|
:hide-on-click-modal="true" @close="imageViewer.show=false"
|
||||||
|
:teleported="true"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {ref} from "@vue/reactivity";
|
import {ref} from "vue";
|
||||||
import {dayjs} from "element-plus";
|
import {dayjs} from "element-plus";
|
||||||
import FileIcon from "./FileIcon.vue";
|
import FileIcon from "./FileIcon.vue";
|
||||||
|
import {reactive} from "vue";
|
||||||
|
import strings from "../service/strings";
|
||||||
|
import {useRouter, useRoute} from 'vue-router'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "FileBlockItem",
|
name: "FileBlockItem",
|
||||||
@ -32,19 +39,27 @@ export default {
|
|||||||
setup(props) {
|
setup(props) {
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {FileIconInstance}
|
* @type {Ref<FileIconInstance>}
|
||||||
*/
|
*/
|
||||||
const fileItemIcon = ref(null)
|
const fileItemIcon = ref(null)
|
||||||
|
const router = useRouter(), route = useRoute();
|
||||||
|
const file = props.file;
|
||||||
|
const imageViewer = reactive({
|
||||||
|
show: false,
|
||||||
|
previewSrcList: [file.path]
|
||||||
|
})
|
||||||
const showFile = (file) => {
|
const showFile = (file) => {
|
||||||
if (file.type != 'folder') {
|
if (file.type == 'folder') {
|
||||||
console.log(fileItemIcon)
|
router.push(route.path + '?path=' + file.path);
|
||||||
// fileItemIcon.showPreview();
|
return;
|
||||||
|
} else if (strings.isImage(file.type)) {
|
||||||
|
imageViewer.show = true
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
location.hash = '?path=' + file.path
|
// location.hash = '?path=' + file.path
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
file: props.file, showFile, fileItemIcon
|
file: props.file, showFile, fileItemIcon, imageViewer
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<span style="display:inline-block">
|
<span style="display:inline-block">
|
||||||
<el-image v-if="type == 'picture'"
|
<el-image v-if="type == 'picture'" ref="img"
|
||||||
:src="currentSrc" :previewSrcList="[currentPreview]"
|
:src="currentSrc" :previewSrcList="[currentPreview]"
|
||||||
:initial-index="4" fit="cover" :hide-on-click-modal="true"
|
:initial-index="4" fit="cover" :hide-on-click-modal="true"
|
||||||
/>
|
/>
|
||||||
@ -14,6 +14,7 @@ import FolderIcon from '../assets/icons/folder.svg'
|
|||||||
import UnknownIcon from '../assets/icons/icon.svg'
|
import UnknownIcon from '../assets/icons/icon.svg'
|
||||||
import ExeIcon from '../assets/icons/exe.svg'
|
import ExeIcon from '../assets/icons/exe.svg'
|
||||||
import {ElMessage} from "element-plus";
|
import {ElMessage} from "element-plus";
|
||||||
|
import strings from "../service/strings";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
// 定义组件所需的属性
|
// 定义组件所需的属性
|
||||||
@ -38,7 +39,7 @@ export default {
|
|||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
const type = this.file.type.toLowerCase(); // 拿到文件类型
|
const type = this.file.type.toLowerCase(); // 拿到文件类型
|
||||||
if (['png', 'jpg', 'jpeg', 'gif', 'webp'].includes(type)) {
|
if (strings.isImage(type)) {
|
||||||
this.currentSrc = this.file.thumb || UnknownIcon; // 图片
|
this.currentSrc = this.file.thumb || UnknownIcon; // 图片
|
||||||
this.currentPreview = this.file.path || UnknownIcon; // 图片
|
this.currentPreview = this.file.path || UnknownIcon; // 图片
|
||||||
this.type = 'picture';
|
this.type = 'picture';
|
||||||
@ -49,7 +50,9 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
showPreview(file) {
|
showPreview(file) {
|
||||||
//
|
//
|
||||||
ElMessage('暂不支持此文件的预览');
|
// ElMessage('暂不支持此文件的预览');
|
||||||
|
const img = this.$refs.img;
|
||||||
|
img.clickHandler();
|
||||||
console.log(file)
|
console.log(file)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,16 +3,18 @@ import {createApp} from "vue";
|
|||||||
// 导入element-plus的依赖
|
// 导入element-plus的依赖
|
||||||
import ElementPlus from "element-plus";
|
import ElementPlus from "element-plus";
|
||||||
import "element-plus/dist/index.css";
|
import "element-plus/dist/index.css";
|
||||||
import './assets/main.less'
|
|
||||||
|
|
||||||
import ContextMenu from 'vue-simple-context-menu'
|
import ContextMenu from 'vue-simple-context-menu'
|
||||||
import 'vue-simple-context-menu/dist/vue-simple-context-menu.css';
|
import 'vue-simple-context-menu/dist/vue-simple-context-menu.css';
|
||||||
|
import './assets/main.less'
|
||||||
|
import store from './service/store'
|
||||||
|
import router from './router'
|
||||||
|
|
||||||
// 导入入口组件
|
// 导入入口组件
|
||||||
import App from "./App.vue";
|
import App from "./App.vue";
|
||||||
import index from './router'
|
|
||||||
|
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
app.component('ContextMenu', ContextMenu);
|
app.component('ContextMenu', ContextMenu);
|
||||||
app.use(ElementPlus)
|
app.use(ElementPlus)
|
||||||
.use(index) // 使用路由实例
|
.use(router) // 使用路由实例
|
||||||
|
.use(store)
|
||||||
.mount("#app");
|
.mount("#app");
|
||||||
|
@ -1,41 +1,105 @@
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
#page-login {
|
#page-login {
|
||||||
width: 500px;
|
height: 100vh;
|
||||||
margin: 200px auto 0;
|
display: flex;
|
||||||
|
background-color: pink;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-wrapper {
|
||||||
|
width: 800px;
|
||||||
|
margin: auto;
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 5px;
|
||||||
|
box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-picture {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
border-right: dashed 1px #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo-icon {
|
||||||
|
width: 300px;
|
||||||
|
height: 300px;
|
||||||
|
margin: auto;
|
||||||
|
fill: #999999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-form {
|
||||||
|
width: 350px;
|
||||||
|
padding: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-item {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.forget-tips {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reset-link {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #999999;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<template>
|
<template>
|
||||||
<div id="page-login">
|
<div id="page-login">
|
||||||
|
<div class="login-wrapper d-flex">
|
||||||
|
<div class="flex-1 left-picture">
|
||||||
|
<svg class="logo-icon" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink" width="1000" height="1000">
|
||||||
|
<path
|
||||||
|
d="M549.766 97.925c19.974 4.609 50.703 35.339 62.995 55.312 35.339-13.828 36.875 41.484 46.094 55.312 23.047-16.901 46.094-35.339 46.094-55.312 0-21.51-30.729-35.339-30.729-41.484 0-12.292 38.411-12.292 41.484-12.292 50.703 0 106.016 39.948 106.016 96.797 0 27.656-9.219 41.484-33.802 70.677h32.266c21.51 0 27.656-3.073 55.312 7.682C923.125 294.591 960 348.367 960 389.852c0 3.073 0 9.219-1.536 13.828-4.609 3.073-12.292 3.073-16.901 4.609-15.365 1.536-32.266 6.146-47.63 7.682-47.63 0-50.703 0-53.776-1.536-1.536 1.536-1.536 0-1.536 4.609 0 3.073 7.682 44.557 10.755 67.604 9.219 56.849 12.292 115.234 19.974 173.62 1.536 7.682 7.682 15.365 9.219 23.047 3.073 13.828 6.146 27.656 6.146 39.948 0 153.646-245.833 208.958-321.119 208.958h-86.042c-115.234-9.219-248.906-52.24-301.145-138.281-4.609-7.682-18.437-39.948-18.437-50.703v-36.875c3.073-16.901 7.682-33.802 21.51-50.703v-84.505l12.292-132.135c-12.292 1.536-33.802 1.536-38.411 1.536-21.51 0-38.411-3.073-61.458-6.146-7.682-1.536-18.437-3.073-24.583-6.146-4.609-1.536-3.073-12.292-3.073-13.828 0-44.557 44.557-107.552 98.333-119.844 4.609-1.536 13.828-1.536 19.974-3.073l41.484-1.536c-13.828-10.755-36.875-46.094-36.875-59.922v-29.193c13.828-58.385 62.995-82.969 106.016-82.969 1.536 0 41.484 0 41.484 12.292 0 6.146-30.729 19.974-30.729 41.484 0 1.536 6.146 32.266 16.901 32.266 3.073 0-1.536-3.073 3.073-3.073 3.073 0 32.266 10.755 36.875 10.755h12.292l3.073-3.073c-19.974-16.901-30.729-36.875-36.875-53.776 12.292 7.682 18.438 9.219 29.193 9.219 27.656 0 46.094-15.365 78.359-29.193 24.583-10.755 52.24-13.828 78.359-18.437-12.292-9.219-24.583-16.901-36.875-23.047l38.411-1.536c7.68 1.537 15.362 4.61 23.044 6.146z"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="login-form">
|
||||||
|
<h1 class="title">天牛网盘</h1>
|
||||||
<el-form
|
<el-form
|
||||||
ref="form"
|
ref="form"
|
||||||
:model="loginForm"
|
:model="loginForm"
|
||||||
:rules="rules"
|
:rules="rules"
|
||||||
status-icon
|
status-icon
|
||||||
label-width="120px"
|
size="large"
|
||||||
class="demo-ruleForm"
|
@keyup.enter="submitForm"
|
||||||
>
|
>
|
||||||
<el-form-item label="" prop="username">
|
<el-form-item label="" prop="username" class="input-item">
|
||||||
<el-input v-model="loginForm.username" placeholder="用户名" autocomplete="off"/>
|
<el-input v-model="loginForm.username" placeholder="用户名" autocomplete="off"/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="" prop="password">
|
<el-form-item label="" prop="password" class="input-item">
|
||||||
<el-input v-model="loginForm.password" placeholder="登录密码" type="password" autocomplete="off"/>
|
<el-input v-model="loginForm.password" placeholder="登录密码" type="password" autocomplete="off"/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button style="width: 100%" type="primary" @click="submitForm">登录</el-button>
|
<el-button :loading="loginLoading" style="width: 100%" type="primary" @click="submitForm">登录
|
||||||
|
</el-button>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
<div class="forget-tips">
|
||||||
|
<router-link class="reset-link" to="/forget">密码忘记了,点我重置密码!</router-link>
|
||||||
|
</div>
|
||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {defineComponent, reactive, ref} from 'vue'
|
import {defineComponent, reactive, ref} from 'vue'
|
||||||
import {ElMessage, ElMessageBox} from "element-plus";
|
import {ElMessage, ElMessageBox} from "element-plus";
|
||||||
import {useRouter} from 'vue-router'
|
import {useRouter} from 'vue-router'
|
||||||
|
import {useStore} from "vuex";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'Login',
|
name: 'Login',
|
||||||
method:{
|
method: {
|
||||||
gotoMain(){
|
gotoMain() {
|
||||||
this.$router.push('/');
|
this.$router.push('/');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -50,6 +114,7 @@ export default defineComponent({
|
|||||||
password: '',
|
password: '',
|
||||||
username: ''
|
username: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
const checkPassword = (rule, value, callback) => {
|
const checkPassword = (rule, value, callback) => {
|
||||||
if (!value) callback(Error('请填写密码')) // 验证不通过
|
if (!value) callback(Error('请填写密码')) // 验证不通过
|
||||||
else callback() // 验证通过
|
else callback() // 验证通过
|
||||||
@ -63,18 +128,31 @@ export default defineComponent({
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
const router = useRouter(); // 获取路由对象
|
const router = useRouter(); // 获取路由对象
|
||||||
|
const store = useStore();
|
||||||
|
const loginLoading = ref(false)
|
||||||
const submitForm = () => {
|
const submitForm = () => {
|
||||||
form.value.validate((isValid) => {
|
if(loginLoading.value) return;
|
||||||
|
// 验证表单
|
||||||
|
form.value.validate(async (isValid) => {
|
||||||
if (isValid) {
|
if (isValid) {
|
||||||
|
// 设置loading未true
|
||||||
|
loginLoading.value = true
|
||||||
|
try {
|
||||||
|
const _data = await store.dispatch('login', loginForm)
|
||||||
|
// 登录成功后 需要保存token
|
||||||
|
// localStorage.setItem('token',_data);
|
||||||
ElMessage.success('登录成功')
|
ElMessage.success('登录成功')
|
||||||
router.replace('/')
|
router.replace('/').then().catch();
|
||||||
} else {
|
} catch (e) {
|
||||||
ElMessage.info('验证不通过')
|
ElMessage.error(e.message || '登录错误')
|
||||||
|
} finally {
|
||||||
|
loginLoading.value = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
return {
|
return {
|
||||||
loginForm, submitForm, rules, form
|
loginForm, submitForm, rules, form, loginLoading
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -1,13 +1,26 @@
|
|||||||
import {createRouter, createWebHistory} from 'vue-router'
|
import {createRouter, createWebHistory} from 'vue-router'
|
||||||
import routes from "./routes";
|
|
||||||
|
|
||||||
|
import routes from "./routes";
|
||||||
|
import store from "../service/store";
|
||||||
|
|
||||||
|
// 路由器实例对象
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(),
|
history: createWebHistory(),
|
||||||
routes
|
routes
|
||||||
});
|
});
|
||||||
router.beforeEach((to,from,next)=>{
|
//
|
||||||
console.log(from.path ,'==>',to.path)
|
const loginPath = '/login', // 登录路径
|
||||||
next()
|
anonymousPaths = [loginPath, '/reg']; // 匿名可访问路径
|
||||||
|
// 全局导航守卫
|
||||||
|
router.beforeEach((to, from, next) => {
|
||||||
|
const toPath = to.path; // 获取要展示得路径
|
||||||
|
if (anonymousPaths.includes(toPath)) next() // 判断是否可匿名访问
|
||||||
|
else {
|
||||||
|
// 验证token
|
||||||
|
if (!store.getters.userToken) next(loginPath) // 没有登录直接打开登录页面
|
||||||
|
// if(!localStorage.getItem('token')) next(loginPath)
|
||||||
|
else next();
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export default router;
|
export default router;
|
@ -1,3 +1,9 @@
|
|||||||
|
import store from "./store";
|
||||||
|
import {
|
||||||
|
ElMessage
|
||||||
|
} from "element-plus";
|
||||||
|
import router from "../router";
|
||||||
|
|
||||||
const API_PATH = "http://localhost:8080"
|
const API_PATH = "http://localhost:8080"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -8,6 +14,10 @@ const API_PATH = "http://localhost:8080"
|
|||||||
*/
|
*/
|
||||||
function request(api, method = 'GET', postData = {}, progressChange = null) {
|
function request(api, method = 'GET', postData = {}, progressChange = null) {
|
||||||
//return fetch(API_PATH + api).then(res => res.json());
|
//return fetch(API_PATH + api).then(res => res.json());
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {RequestInit}
|
||||||
|
*/
|
||||||
let options = {
|
let options = {
|
||||||
method
|
method
|
||||||
};
|
};
|
||||||
@ -21,7 +31,7 @@ function request(api, method = 'GET', postData = {}, progressChange = null) {
|
|||||||
} else if (method.toUpperCase() == 'FILE') {
|
} else if (method.toUpperCase() == 'FILE') {
|
||||||
options = {
|
options = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: postData // 参数
|
body: postData, // 参数
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
options = {
|
options = {
|
||||||
@ -32,16 +42,35 @@ function request(api, method = 'GET', postData = {}, progressChange = null) {
|
|||||||
body: JSON.stringify(postData) // 参数
|
body: JSON.stringify(postData) // 参数
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let Authorization = store.getters.userToken
|
||||||
|
if (Authorization) {
|
||||||
|
options = {
|
||||||
|
headers: {
|
||||||
|
Authorization
|
||||||
|
},
|
||||||
|
...options
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {ApiResult} result
|
* @param {ApiResult} result
|
||||||
*/
|
*/
|
||||||
const processResult = result => {
|
const processResult = (result, a, b) => {
|
||||||
if (result.code === 0) { // 判断响应码是否正常
|
if (result.code === 0) { // 判断响应码是否正常
|
||||||
resolve(result.data)
|
resolve(result.data)
|
||||||
} else { // 有异常 直接抛出错误
|
} else {
|
||||||
|
const route = router.currentRoute.value;
|
||||||
|
// 未登录或token无效
|
||||||
|
if (result.code == 401) {
|
||||||
|
store.commit('clearToken') // 清除token
|
||||||
|
ElMessage.error('登录凭证验证失败,请重新登录');
|
||||||
|
if (route.path != '/login') router.replace('/login').then(() => console.log('auth show login')).catch();
|
||||||
|
reject(null)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 其他异常 直接抛出错误
|
||||||
reject(new Error(result.message))
|
reject(new Error(result.message))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -49,6 +78,7 @@ function request(api, method = 'GET', postData = {}, progressChange = null) {
|
|||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(processResult)
|
.then(processResult)
|
||||||
.catch(e => {
|
.catch(e => {
|
||||||
|
console.log(e)
|
||||||
reject(e);
|
reject(e);
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
@ -56,9 +86,11 @@ function request(api, method = 'GET', postData = {}, progressChange = null) {
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
user: {
|
user: {
|
||||||
login() {
|
login(params) {
|
||||||
|
return request('/api/user/login', 'POST', params);
|
||||||
},
|
},
|
||||||
signup() {
|
signup(params) {
|
||||||
|
return request('/api/user/reg', 'POST', params);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
folder: {
|
folder: {
|
||||||
|
53
web/src/service/store.js
Normal file
53
web/src/service/store.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import {createStore} from "vuex"
|
||||||
|
import api from "./api";
|
||||||
|
|
||||||
|
const TOKEN_KEY = 'user_auth_token';
|
||||||
|
const store = createStore({
|
||||||
|
state: {
|
||||||
|
/**
|
||||||
|
* @type {UserInfo}
|
||||||
|
*/
|
||||||
|
loginUser: null,
|
||||||
|
token: null
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
// 此选项中的方法必须同步
|
||||||
|
setLoginUserInfo(state, info) {
|
||||||
|
state.loginUser = info;
|
||||||
|
},
|
||||||
|
setToken(state, token) {
|
||||||
|
state.token = token;
|
||||||
|
// sessionStorage.setItem(TOKEN_KEY,token);
|
||||||
|
localStorage.setItem(TOKEN_KEY, token);
|
||||||
|
},
|
||||||
|
clearToken(state){
|
||||||
|
state.token = null;
|
||||||
|
localStorage.removeItem(TOKEN_KEY);
|
||||||
|
sessionStorage.removeItem(TOKEN_KEY)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
login({commit}, data) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
api.user.login(data).then(
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {LoginUser} ret
|
||||||
|
*/
|
||||||
|
ret => {
|
||||||
|
commit('setToken', ret.token);
|
||||||
|
commit('setLoginUserInfo', ret.userInfo)
|
||||||
|
resolve()
|
||||||
|
}).catch(e => reject(e))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getters: {
|
||||||
|
userToken(state) {
|
||||||
|
const token = state.token || sessionStorage.getItem(TOKEN_KEY) || localStorage.getItem(TOKEN_KEY);
|
||||||
|
if(!state.token) state.token = token;
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
export default store
|
12
web/src/service/strings.js
Normal file
12
web/src/service/strings.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} type
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export function isImage(type) {
|
||||||
|
return type ? ['png', 'jpg', 'jpeg', 'gif', 'webp'].includes(type.toLowerCase()) : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
isImage
|
||||||
|
}
|
23
web/src/service/type.d.ts
vendored
23
web/src/service/type.d.ts
vendored
@ -35,3 +35,26 @@ declare type ShareInfo = {
|
|||||||
updateTime?: string,
|
updateTime?: string,
|
||||||
status: number
|
status: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare type UserInfo = {
|
||||||
|
account: string
|
||||||
|
avatar: string
|
||||||
|
createTime: string
|
||||||
|
email: string
|
||||||
|
id: number
|
||||||
|
lastIp: string
|
||||||
|
lastLogin: string
|
||||||
|
nickname: string
|
||||||
|
password: string
|
||||||
|
salt: string
|
||||||
|
sex: number
|
||||||
|
status: number
|
||||||
|
updateTime: string
|
||||||
|
}
|
||||||
|
declare type LoginUser = {
|
||||||
|
account: string
|
||||||
|
permissions: string[]
|
||||||
|
roles: string[]
|
||||||
|
token: string
|
||||||
|
userInfo: UserInfo
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user