集成shiro

This commit is contained in:
LittleBoy 2022-05-16 17:27:22 +08:00
parent 0f7149f1b0
commit b1ca1ace25
34 changed files with 1160 additions and 206 deletions

View File

@ -91,6 +91,17 @@
<artifactId>shiro-spring</artifactId>
<version>1.9.0</version>
</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>-->
<!-- <groupId>org.springframework.amqp</groupId>-->

View File

@ -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();
}
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -16,6 +16,7 @@ import java.io.FileInputStream;
// 根据文件生成预览
@Controller
@RestController
@RequestMapping("/picture")
public class PreviewController {
@Resource
private FileService fileService;

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -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> {
}

View File

@ -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());
}
}

View File

@ -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;
}

View File

@ -0,0 +1,4 @@
package xyz.longicorn.driver.dto;
public class UserInfoDto {
}

View File

@ -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;
}

View File

@ -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;
}

View 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;
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View 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();
}
}
}

View File

@ -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为postgetdelete等
* 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;
}
}

View File

@ -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;
}
}

View File

@ -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<String> roleSet = new HashSet<>(); // 角色列表
roleSet.add("user");
roleSet.add("vip");
info.setRoles(roleSet);
Set<String> 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());
}
}

View 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;
}
}
}

View File

@ -0,0 +1,8 @@
package xyz.longicorn.driver.util;
import org.springframework.stereotype.Component;
@Component
public class RedisUtil {
}

View File

@ -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,7 +17,7 @@ create table folder_info
# 编号、用户编号、文件名、目录编号 、类型、大小、位置、创建时间、状态、hash
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,
name varchar(50) not null,
hash varchar(32) not null comment '文件MD5特征码',
@ -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
);

22
web/package-lock.json generated
View File

@ -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"
}
}
}
}

View File

@ -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",

View File

@ -9,13 +9,20 @@
file.type == 'folder' ? formatDate(file.createTime) : formatSize(file.size)
}}
</div>
<el-image-viewer v-if="imageViewer.show"
:url-list="imageViewer.previewSrcList"
:hide-on-click-modal="true" @close="imageViewer.show=false"
:teleported="true"/>
</div>
</template>
<script>
import {ref} from "@vue/reactivity";
import {ref} from "vue";
import {dayjs} from "element-plus";
import FileIcon from "./FileIcon.vue";
import {reactive} from "vue";
import strings from "../service/strings";
import {useRouter, useRoute} from 'vue-router'
export default {
name: "FileBlockItem",
@ -32,19 +39,27 @@ export default {
setup(props) {
/**
*
* @type {FileIconInstance}
* @type {Ref<FileIconInstance>}
*/
const fileItemIcon = ref(null)
const router = useRouter(), route = useRoute();
const file = props.file;
const imageViewer = reactive({
show: false,
previewSrcList: [file.path]
})
const showFile = (file) => {
if (file.type != 'folder') {
console.log(fileItemIcon)
// fileItemIcon.showPreview();
if (file.type == 'folder') {
router.push(route.path + '?path=' + file.path);
return;
} else if (strings.isImage(file.type)) {
imageViewer.show = true
return;
}
location.hash = '?path=' + file.path
// location.hash = '?path=' + file.path
}
return {
file: props.file, showFile, fileItemIcon
file: props.file, showFile, fileItemIcon, imageViewer
}
},
methods: {

View File

@ -1,6 +1,6 @@
<template>
<span style="display:inline-block">
<el-image v-if="type == 'picture'"
<el-image v-if="type == 'picture'" ref="img"
:src="currentSrc" :previewSrcList="[currentPreview]"
: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 ExeIcon from '../assets/icons/exe.svg'
import {ElMessage} from "element-plus";
import strings from "../service/strings";
export default {
//
@ -38,7 +39,7 @@ export default {
},
mounted() {
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.currentPreview = this.file.path || UnknownIcon; //
this.type = 'picture';
@ -49,7 +50,9 @@ export default {
methods: {
showPreview(file) {
//
ElMessage('暂不支持此文件的预览');
// ElMessage('');
const img = this.$refs.img;
img.clickHandler();
console.log(file)
}
}

View File

@ -3,16 +3,18 @@ import {createApp} from "vue";
// 导入element-plus的依赖
import ElementPlus from "element-plus";
import "element-plus/dist/index.css";
import './assets/main.less'
import ContextMenu from 'vue-simple-context-menu'
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 index from './router'
const app = createApp(App)
app.component('ContextMenu', ContextMenu);
app.use(ElementPlus)
.use(index) // 使用路由实例
.use(router) // 使用路由实例
.use(store)
.mount("#app");

View File

@ -1,41 +1,105 @@
<style scoped>
#page-login {
width: 500px;
margin: 200px auto 0;
height: 100vh;
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>
<template>
<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
ref="form"
:model="loginForm"
:rules="rules"
status-icon
label-width="120px"
class="demo-ruleForm"
size="large"
@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-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-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>
<div class="forget-tips">
<router-link class="reset-link" to="/forget">密码忘记了,点我重置密码</router-link>
</div>
</el-form>
</div>
</div>
</div>
</template>
<script>
import {defineComponent, reactive, ref} from 'vue'
import {ElMessage, ElMessageBox} from "element-plus";
import {useRouter} from 'vue-router'
import {useStore} from "vuex";
export default defineComponent({
name: 'Login',
method:{
gotoMain(){
method: {
gotoMain() {
this.$router.push('/');
}
},
@ -50,6 +114,7 @@ export default defineComponent({
password: '',
username: ''
})
const checkPassword = (rule, value, callback) => {
if (!value) callback(Error('请填写密码')) //
else callback() //
@ -63,18 +128,31 @@ export default defineComponent({
]
})
const router = useRouter(); //
const store = useStore();
const loginLoading = ref(false)
const submitForm = () => {
form.value.validate((isValid) => {
if(loginLoading.value) return;
//
form.value.validate(async (isValid) => {
if (isValid) {
// loadingtrue
loginLoading.value = true
try {
const _data = await store.dispatch('login', loginForm)
// token
// localStorage.setItem('token',_data);
ElMessage.success('登录成功')
router.replace('/')
} else {
ElMessage.info('验证不通过')
router.replace('/').then().catch();
} catch (e) {
ElMessage.error(e.message || '登录错误')
} finally {
loginLoading.value = false
}
}
})
};
return {
loginForm, submitForm, rules, form
loginForm, submitForm, rules, form, loginLoading
}
}
})

View File

@ -1,13 +1,26 @@
import {createRouter, createWebHistory} from 'vue-router'
import routes from "./routes";
import routes from "./routes";
import store from "../service/store";
// 路由器实例对象
const router = createRouter({
history: createWebHistory(),
routes
});
router.beforeEach((to,from,next)=>{
console.log(from.path ,'==>',to.path)
next()
//
const loginPath = '/login', // 登录路径
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;

View File

@ -1,3 +1,9 @@
import store from "./store";
import {
ElMessage
} from "element-plus";
import router from "../router";
const API_PATH = "http://localhost:8080"
/**
@ -8,6 +14,10 @@ const API_PATH = "http://localhost:8080"
*/
function request(api, method = 'GET', postData = {}, progressChange = null) {
//return fetch(API_PATH + api).then(res => res.json());
/**
*
* @type {RequestInit}
*/
let options = {
method
};
@ -21,7 +31,7 @@ function request(api, method = 'GET', postData = {}, progressChange = null) {
} else if (method.toUpperCase() == 'FILE') {
options = {
method: 'POST',
body: postData // 参数
body: postData, // 参数
}
} else {
options = {
@ -32,16 +42,35 @@ function request(api, method = 'GET', postData = {}, progressChange = null) {
body: JSON.stringify(postData) // 参数
}
}
let Authorization = store.getters.userToken
if (Authorization) {
options = {
headers: {
Authorization
},
...options
}
}
return new Promise((resolve, reject) => {
/**
*
* @param {ApiResult} result
*/
const processResult = result => {
const processResult = (result, a, b) => {
if (result.code === 0) { // 判断响应码是否正常
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))
}
}
@ -49,6 +78,7 @@ function request(api, method = 'GET', postData = {}, progressChange = null) {
.then(res => res.json())
.then(processResult)
.catch(e => {
console.log(e)
reject(e);
})
});
@ -56,9 +86,11 @@ function request(api, method = 'GET', postData = {}, progressChange = null) {
export default {
user: {
login() {
login(params) {
return request('/api/user/login', 'POST', params);
},
signup() {
signup(params) {
return request('/api/user/reg', 'POST', params);
}
},
folder: {

53
web/src/service/store.js Normal file
View 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

View 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
}

View File

@ -35,3 +35,26 @@ declare type ShareInfo = {
updateTime?: string,
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
}