!15 合并 新功能/satoken 分支

This commit is contained in:
疯狂的狮子Li
2022-01-28 11:35:54 +00:00
parent fe7b636ef8
commit db70abf9f0
93 changed files with 1799 additions and 2192 deletions

View File

@@ -11,8 +11,6 @@ import java.lang.annotation.*;
@Inherited
// 表示通过aop框架暴露该代理对象,AopContext能够访问
@EnableAspectJAutoProxy(exposeProxy = true)
//// 指定要扫描的Mapper类的包的路径
//@MapperScan("com.ruoyi.**.mapper")
// 开启线程异步执行
@EnableAsync
public @interface EnableCustomConfig {

View File

@@ -1,18 +0,0 @@
package com.ruoyi.common.security.annotation;
import java.lang.annotation.*;
/**
* 内部认证注解
*
* @author ruoyi
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface InnerAuth {
/**
* 是否校验用户信息
*/
boolean isUser() default false;
}

View File

@@ -1,18 +0,0 @@
package com.ruoyi.common.security.annotation;
/**
* 权限注解的验证模式
*
* @author ruoyi
*/
public enum Logical {
/**
* 必须具有所有的元素
*/
AND,
/**
* 只需具有其中一个元素
*/
OR
}

View File

@@ -1,16 +0,0 @@
package com.ruoyi.common.security.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 登录认证:只有登录之后才能进入该方法
*
* @author ruoyi
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface RequiresLogin {
}

View File

@@ -1,25 +0,0 @@
package com.ruoyi.common.security.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 权限认证:必须具有指定权限才能进入该方法
*
* @author ruoyi
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface RequiresPermissions {
/**
* 需要校验的权限码
*/
String[] value() default {};
/**
* 验证模式AND | OR默认AND
*/
Logical logical() default Logical.AND;
}

View File

@@ -1,25 +0,0 @@
package com.ruoyi.common.security.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 角色认证:必须具有指定角色标识才能进入该方法
*
* @author ruoyi
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface RequiresRoles {
/**
* 需要校验的角色标识
*/
String[] value() default {};
/**
* 验证逻辑AND | OR默认AND
*/
Logical logical() default Logical.AND;
}

View File

@@ -1,46 +0,0 @@
package com.ruoyi.common.security.aspect;
import com.ruoyi.common.core.constant.SecurityConstants;
import com.ruoyi.common.core.exception.InnerAuthException;
import com.ruoyi.common.core.utils.ServletUtils;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.security.annotation.InnerAuth;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
/**
* 内部服务调用验证处理
*
* @author ruoyi
*/
@Aspect
@Component
public class InnerAuthAspect implements Ordered {
@Around("@annotation(innerAuth)")
public Object innerAround(ProceedingJoinPoint point, InnerAuth innerAuth) throws Throwable {
String source = ServletUtils.getRequest().getHeader(SecurityConstants.FROM_SOURCE);
// 内部请求验证
if (!StringUtils.equals(SecurityConstants.INNER, source)) {
throw new InnerAuthException("没有内部访问权限,不允许访问");
}
String userid = ServletUtils.getRequest().getHeader(SecurityConstants.DETAILS_USER_ID);
String username = ServletUtils.getRequest().getHeader(SecurityConstants.DETAILS_USERNAME);
// 用户信息验证
if (innerAuth.isUser() && (StringUtils.isEmpty(userid) || StringUtils.isEmpty(username))) {
throw new InnerAuthException("没有设置用户信息,不允许访问 ");
}
return point.proceed();
}
/**
* 确保在权限认证aop执行前执行
*/
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE + 1;
}
}

View File

@@ -1,87 +0,0 @@
package com.ruoyi.common.security.aspect;
import com.ruoyi.common.security.annotation.RequiresLogin;
import com.ruoyi.common.security.annotation.RequiresPermissions;
import com.ruoyi.common.security.annotation.RequiresRoles;
import com.ruoyi.common.security.auth.AuthUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* 基于 Spring Aop 的注解鉴权
*
* @author kong
*/
@Aspect
@Component
public class PreAuthorizeAspect {
/**
* 构建
*/
public PreAuthorizeAspect() {
}
/**
* 定义AOP签名 (切入所有使用鉴权注解的方法)
*/
public static final String POINTCUT_SIGN = " @annotation(com.ruoyi.common.security.annotation.RequiresLogin) || "
+ "@annotation(com.ruoyi.common.security.annotation.RequiresPermissions) || "
+ "@annotation(com.ruoyi.common.security.annotation.RequiresRoles)";
/**
* 声明AOP签名
*/
@Pointcut(POINTCUT_SIGN)
public void pointcut() {
}
/**
* 环绕切入
*
* @param joinPoint 切面对象
* @return 底层方法执行后的返回值
* @throws Throwable 底层方法抛出的异常
*/
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// 注解鉴权
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
checkMethodAnnotation(signature.getMethod());
try {
// 执行原有逻辑
Object obj = joinPoint.proceed();
return obj;
} catch (Throwable e) {
throw e;
}
}
/**
* 对一个Method对象进行注解检查
*/
public void checkMethodAnnotation(Method method) {
// 校验 @RequiresLogin 注解
RequiresLogin requiresLogin = method.getAnnotation(RequiresLogin.class);
if (requiresLogin != null) {
AuthUtil.checkLogin();
}
// 校验 @RequiresRoles 注解
RequiresRoles requiresRoles = method.getAnnotation(RequiresRoles.class);
if (requiresRoles != null) {
AuthUtil.checkRole(requiresRoles);
}
// 校验 @RequiresPermissions 注解
RequiresPermissions requiresPermissions = method.getAnnotation(RequiresPermissions.class);
if (requiresPermissions != null) {
AuthUtil.checkPermi(requiresPermissions);
}
}
}

View File

@@ -1,319 +0,0 @@
package com.ruoyi.common.security.auth;
import com.ruoyi.common.core.exception.auth.NotLoginException;
import com.ruoyi.common.core.exception.auth.NotPermissionException;
import com.ruoyi.common.core.exception.auth.NotRoleException;
import com.ruoyi.common.core.utils.SpringUtils;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.security.annotation.Logical;
import com.ruoyi.common.security.annotation.RequiresLogin;
import com.ruoyi.common.security.annotation.RequiresPermissions;
import com.ruoyi.common.security.annotation.RequiresRoles;
import com.ruoyi.common.security.service.TokenService;
import com.ruoyi.common.security.utils.SecurityUtils;
import com.ruoyi.system.api.model.LoginUser;
import org.springframework.util.PatternMatchUtils;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
/**
* Token 权限验证,逻辑实现类
*
* @author ruoyi
*/
public class AuthLogic {
/**
* 所有权限标识
*/
private static final String ALL_PERMISSION = "*:*:*";
/**
* 管理员角色权限标识
*/
private static final String SUPER_ADMIN = "admin";
public TokenService tokenService = SpringUtils.getBean(TokenService.class);
/**
* 会话注销
*/
public void logout() {
String token = SecurityUtils.getToken();
if (token == null) {
return;
}
logoutByToken(token);
}
/**
* 会话注销根据指定Token
*/
public void logoutByToken(String token) {
tokenService.delLoginUser(token);
}
/**
* 检验用户是否已经登录,如未登录,则抛出异常
*/
public void checkLogin() {
getLoginUser();
}
/**
* 获取当前用户缓存信息, 如果未登录,则抛出异常
*
* @return 用户缓存信息
*/
public LoginUser getLoginUser() {
String token = SecurityUtils.getToken();
if (token == null) {
throw new NotLoginException("未提供token");
}
LoginUser loginUser = SecurityUtils.getLoginUser();
if (loginUser == null) {
throw new NotLoginException("无效的token");
}
return loginUser;
}
/**
* 获取当前用户缓存信息, 如果未登录,则抛出异常
*
* @param token 前端传递的认证信息
* @return 用户缓存信息
*/
public LoginUser getLoginUser(String token) {
return tokenService.getLoginUser(token);
}
/**
* 验证当前用户有效期, 如果相差不足360分钟自动刷新缓存
*
* @param loginUser 当前用户信息
*/
public void verifyLoginUserExpire(LoginUser loginUser) {
tokenService.verifyToken(loginUser);
}
/**
* 验证用户是否具备某权限
*
* @param permission 权限字符串
* @return 用户是否具备某权限
*/
public boolean hasPermi(String permission) {
return hasPermi(getPermiList(), permission);
}
/**
* 验证用户是否具备某权限, 如果验证未通过,则抛出异常: NotPermissionException
*
* @param permission 权限字符串
* @return 用户是否具备某权限
*/
public void checkPermi(String permission) {
if (!hasPermi(getPermiList(), permission)) {
throw new NotPermissionException(permission);
}
}
/**
* 根据注解(@RequiresPermissions)鉴权, 如果验证未通过,则抛出异常: NotPermissionException
*
* @param requiresPermissions 注解对象
*/
public void checkPermi(RequiresPermissions requiresPermissions) {
if (requiresPermissions.logical() == Logical.AND) {
checkPermiAnd(requiresPermissions.value());
} else {
checkPermiOr(requiresPermissions.value());
}
}
/**
* 验证用户是否含有指定权限,必须全部拥有
*
* @param permissions 权限列表
*/
public void checkPermiAnd(String... permissions) {
Set<String> permissionList = getPermiList();
for (String permission : permissions) {
if (!hasPermi(permissionList, permission)) {
throw new NotPermissionException(permission);
}
}
}
/**
* 验证用户是否含有指定权限,只需包含其中一个
*
* @param permissions 权限码数组
*/
public void checkPermiOr(String... permissions) {
Set<String> permissionList = getPermiList();
for (String permission : permissions) {
if (hasPermi(permissionList, permission)) {
return;
}
}
if (permissions.length > 0) {
throw new NotPermissionException(permissions);
}
}
/**
* 判断用户是否拥有某个角色
*
* @param role 角色标识
* @return 用户是否具备某角色
*/
public boolean hasRole(String role) {
return hasRole(getRoleList(), role);
}
/**
* 判断用户是否拥有某个角色, 如果验证未通过,则抛出异常: NotRoleException
*
* @param role 角色标识
*/
public void checkRole(String role) {
if (!hasRole(role)) {
throw new NotRoleException(role);
}
}
/**
* 根据注解(@RequiresRoles)鉴权
*
* @param requiresRoles 注解对象
*/
public void checkRole(RequiresRoles requiresRoles) {
if (requiresRoles.logical() == Logical.AND) {
checkRoleAnd(requiresRoles.value());
} else {
checkRoleOr(requiresRoles.value());
}
}
/**
* 验证用户是否含有指定角色,必须全部拥有
*
* @param roles 角色标识数组
*/
public void checkRoleAnd(String... roles) {
Set<String> roleList = getRoleList();
for (String role : roles) {
if (!hasRole(roleList, role)) {
throw new NotRoleException(role);
}
}
}
/**
* 验证用户是否含有指定角色,只需包含其中一个
*
* @param roles 角色标识数组
*/
public void checkRoleOr(String... roles) {
Set<String> roleList = getRoleList();
for (String role : roles) {
if (hasRole(roleList, role)) {
return;
}
}
if (roles.length > 0) {
throw new NotRoleException(roles);
}
}
/**
* 根据注解(@RequiresLogin)鉴权
*
* @param at 注解对象
*/
public void checkByAnnotation(RequiresLogin at) {
this.checkLogin();
}
/**
* 根据注解(@RequiresRoles)鉴权
*
* @param at 注解对象
*/
public void checkByAnnotation(RequiresRoles at) {
String[] roleArray = at.value();
if (at.logical() == Logical.AND) {
this.checkRoleAnd(roleArray);
} else {
this.checkRoleOr(roleArray);
}
}
/**
* 根据注解(@RequiresPermissions)鉴权
*
* @param at 注解对象
*/
public void checkByAnnotation(RequiresPermissions at) {
String[] permissionArray = at.value();
if (at.logical() == Logical.AND) {
this.checkPermiAnd(permissionArray);
} else {
this.checkPermiOr(permissionArray);
}
}
/**
* 获取当前账号的角色列表
*
* @return 角色列表
*/
public Set<String> getRoleList() {
try {
LoginUser loginUser = getLoginUser();
return loginUser.getRoles();
} catch (Exception e) {
return new HashSet<>();
}
}
/**
* 获取当前账号的权限列表
*
* @return 权限列表
*/
public Set<String> getPermiList() {
try {
LoginUser loginUser = getLoginUser();
return loginUser.getPermissions();
} catch (Exception e) {
return new HashSet<>();
}
}
/**
* 判断是否包含权限
*
* @param authorities 权限列表
* @param permission 权限字符串
* @return 用户是否具备某权限
*/
public boolean hasPermi(Collection<String> authorities, String permission) {
return authorities.stream().filter(StringUtils::isNotBlank)
.anyMatch(x -> ALL_PERMISSION.contains(x) || PatternMatchUtils.simpleMatch(x, permission));
}
/**
* 判断是否包含角色
*
* @param roles 角色列表
* @param role 角色
* @return 用户是否具备某角色权限
*/
public boolean hasRole(Collection<String> roles, String role) {
return roles.stream().filter(StringUtils::isNotBlank)
.anyMatch(x -> SUPER_ADMIN.contains(x) || PatternMatchUtils.simpleMatch(x, role));
}
}

View File

@@ -1,146 +0,0 @@
package com.ruoyi.common.security.auth;
import com.ruoyi.common.security.annotation.RequiresPermissions;
import com.ruoyi.common.security.annotation.RequiresRoles;
import com.ruoyi.system.api.model.LoginUser;
/**
* Token 权限验证工具类
*
* @author ruoyi
*/
public class AuthUtil {
/**
* 底层的 AuthLogic 对象
*/
public static AuthLogic authLogic = new AuthLogic();
/**
* 会话注销
*/
public static void logout() {
authLogic.logout();
}
/**
* 会话注销根据指定Token
*
* @param tokenValue 指定token
*/
public static void logoutByToken(String token) {
authLogic.logoutByToken(token);
}
/**
* 检验当前会话是否已经登录,如未登录,则抛出异常
*/
public static void checkLogin() {
authLogic.checkLogin();
}
/**
* 获取当前登录用户信息
*/
public static LoginUser getLoginUser(String token) {
return authLogic.getLoginUser(token);
}
/**
* 验证当前用户有效期
*/
public static void verifyLoginUserExpire(LoginUser loginUser) {
authLogic.verifyLoginUserExpire(loginUser);
}
/**
* 当前账号是否含有指定角色标识, 返回true或false
*
* @param role 角色标识
* @return 是否含有指定角色标识
*/
public static boolean hasRole(String role) {
return authLogic.hasRole(role);
}
/**
* 当前账号是否含有指定角色标识, 如果验证未通过,则抛出异常: NotRoleException
*
* @param role 角色标识
*/
public static void checkRole(String role) {
authLogic.checkRole(role);
}
/**
* 根据注解传入参数鉴权, 如果验证未通过,则抛出异常: NotRoleException
*
* @param requiresRoles 角色权限注解
*/
public static void checkRole(RequiresRoles requiresRoles) {
authLogic.checkRole(requiresRoles);
}
/**
* 当前账号是否含有指定角色标识 [指定多个,必须全部验证通过]
*
* @param roles 角色标识数组
*/
public static void checkRoleAnd(String... roles) {
authLogic.checkRoleAnd(roles);
}
/**
* 当前账号是否含有指定角色标识 [指定多个,只要其一验证通过即可]
*
* @param roles 角色标识数组
*/
public static void checkRoleOr(String... roles) {
authLogic.checkRoleOr(roles);
}
/**
* 当前账号是否含有指定权限, 返回true或false
*
* @param permission 权限码
* @return 是否含有指定权限
*/
public static boolean hasPermi(String permission) {
return authLogic.hasPermi(permission);
}
/**
* 当前账号是否含有指定权限, 如果验证未通过,则抛出异常: NotPermissionException
*
* @param permission 权限码
*/
public static void checkPermi(String permission) {
authLogic.checkPermi(permission);
}
/**
* 根据注解传入参数鉴权, 如果验证未通过,则抛出异常: NotPermissionException
*
* @param requiresPermissions 权限注解
*/
public static void checkPermi(RequiresPermissions requiresPermissions) {
authLogic.checkPermi(requiresPermissions);
}
/**
* 当前账号是否含有指定权限 [指定多个,必须全部验证通过]
*
* @param permissions 权限码数组
*/
public static void checkPermiAnd(String... permissions) {
authLogic.checkPermiAnd(permissions);
}
/**
* 当前账号是否含有指定权限 [指定多个,只要其一验证通过即可]
*
* @param permissions 权限码数组
*/
public static void checkPermiOr(String... permissions) {
authLogic.checkPermiOr(permissions);
}
}

View File

@@ -0,0 +1,59 @@
package com.ruoyi.common.security.config;
import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.id.SaIdUtil;
import cn.dev33.satoken.interceptor.SaAnnotationInterceptor;
import cn.dev33.satoken.interceptor.SaRouteInterceptor;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.util.SaResult;
import com.ruoyi.common.core.constant.HttpStatus;
import com.ruoyi.common.satoken.utils.LoginHelper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Sa-Token 整合 jwt (Style模式)
*
* @author Lion Li
*/
@Configuration
public class SecurityConfiguration implements WebMvcConfigurer {
/**
* 注册sa-token的拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册路由拦截器,自定义验证规则
// 这里只处理登录缓存清理 具体拦截再网关处理
registry.addInterceptor(new SaRouteInterceptor((request, response, handler) -> {
// 获取所有的
SaRouter.match("/**");
}) {
@SuppressWarnings("all")
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
LoginHelper.clearCache();
}
}).addPathPatterns("/**");
// 注解拦截器
registry.addInterceptor(new SaAnnotationInterceptor()).addPathPatterns("/**");
}
/**
* 校验是否从网关转发
*/
@Bean
public SaServletFilter getSaServletFilter() {
return new SaServletFilter()
.addInclude("/**")
.setAuth(obj -> SaIdUtil.checkCurrentRequestToken())
.setError(e -> SaResult.error("认证失败,无法访问系统资源").setCode(HttpStatus.UNAUTHORIZED));
}
}

View File

@@ -1,32 +0,0 @@
package com.ruoyi.common.security.config;
import com.ruoyi.common.security.interceptor.HeaderInterceptor;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 拦截器配置
*
* @author ruoyi
*/
public class WebMvcConfig implements WebMvcConfigurer {
/**
* 不需要拦截地址
*/
public static final String[] excludeUrls = {"/login", "/logout", "/refresh"};
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(getHeaderInterceptor())
.addPathPatterns("/**")
.excludePathPatterns(excludeUrls)
.order(-10);
}
/**
* 自定义请求头拦截器
*/
public HeaderInterceptor getHeaderInterceptor() {
return new HeaderInterceptor();
}
}

View File

@@ -1,14 +0,0 @@
package com.ruoyi.common.security.feign;
/**
* Feign 配置注册
*
* @author ruoyi
**/
//@Configuration
//public class DubboAutoConfiguration {
// @Bean
// public Filter requestInterceptor() {
// return new DubboRequestFilter();
// }
//}

View File

@@ -1,49 +0,0 @@
//package com.ruoyi.common.security.feign;
//
//import com.ruoyi.common.core.constant.SecurityConstants;
//import com.ruoyi.common.core.utils.ServletUtils;
//import com.ruoyi.common.core.utils.StringUtils;
//import com.ruoyi.common.core.utils.ip.IpUtils;
//import org.apache.dubbo.common.constants.CommonConstants;
//import org.apache.dubbo.common.extension.Activate;
//import org.apache.dubbo.rpc.*;
//import org.springframework.stereotype.Component;
//
//import javax.servlet.http.HttpServletRequest;
//import java.util.Map;
//
///**
// * feign 请求拦截器
// *
// * @author ruoyi
// */
//@Activate(group = {CommonConstants.CONSUMER}, order = -10000)
//@Component
//public class DubboRequestFilter implements Filter {
// @Override
// public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
// //执行接口调用逻辑
// Result result = invoker.invoke(invocation);
// HttpServletRequest httpServletRequest = ServletUtils.getRequest();
// if (httpServletRequest != null) {
// Map<String, String> headers = ServletUtils.getHeaders(httpServletRequest);
// // 传递用户信息请求头,防止丢失
// String userId = headers.get(SecurityConstants.DETAILS_USER_ID);
// if (StringUtils.isNotEmpty(userId)) {
// RpcContext.getServerContext().setAttachment(SecurityConstants.DETAILS_USER_ID, userId);
// }
// String userName = headers.get(SecurityConstants.DETAILS_USERNAME);
// if (StringUtils.isNotEmpty(userName)) {
// RpcContext.getServerContext().setAttachment(SecurityConstants.DETAILS_USERNAME, userName);
// }
// String authentication = headers.get(SecurityConstants.AUTHORIZATION_HEADER);
// if (StringUtils.isNotEmpty(authentication)) {
// RpcContext.getServerContext().setAttachment(SecurityConstants.AUTHORIZATION_HEADER, authentication);
// }
//
// // 配置客户端IP
// RpcContext.getServerContext().setAttachment("X-Forwarded-For", IpUtils.getIpAddr(ServletUtils.getRequest()));
// }
// return result;
// }
//}

View File

@@ -1,12 +1,13 @@
package com.ruoyi.common.security.handler;
import cn.dev33.satoken.exception.IdTokenInvalidException;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import cn.hutool.core.util.ObjectUtil;
import com.ruoyi.common.core.constant.HttpStatus;
import com.ruoyi.common.core.exception.DemoModeException;
import com.ruoyi.common.core.exception.InnerAuthException;
import com.ruoyi.common.core.exception.ServiceException;
import com.ruoyi.common.core.exception.auth.NotPermissionException;
import com.ruoyi.common.core.exception.auth.NotRoleException;
import com.ruoyi.common.core.web.domain.AjaxResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindException;
@@ -46,6 +47,26 @@ public class GlobalExceptionHandler {
return AjaxResult.error(HttpStatus.FORBIDDEN, "没有访问权限,请联系管理员授权");
}
/**
* 认证失败
*/
@ExceptionHandler(NotLoginException.class)
public AjaxResult handleNotLoginException(NotLoginException e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址'{}',认证失败'{}',无法访问系统资源", requestURI, e.getMessage());
return AjaxResult.error(HttpStatus.UNAUTHORIZED, "认证失败,无法访问系统资源");
}
/**
* 无效认证
*/
@ExceptionHandler(IdTokenInvalidException.class)
public AjaxResult handleIdTokenInvalidException(IdTokenInvalidException e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址'{}',内网认证失败'{}',无法访问系统资源", requestURI, e.getMessage());
return AjaxResult.error(HttpStatus.UNAUTHORIZED, "认证失败,无法访问系统资源");
}
/**
* 请求方式不支持
*/
@@ -107,14 +128,6 @@ public class GlobalExceptionHandler {
return AjaxResult.error(message);
}
/**
* 内部认证异常
*/
@ExceptionHandler(InnerAuthException.class)
public AjaxResult handleInnerAuthException(InnerAuthException e) {
return AjaxResult.error(e.getMessage());
}
/**
* 演示模式异常
*/

View File

@@ -1,50 +0,0 @@
package com.ruoyi.common.security.interceptor;
import cn.hutool.core.util.ObjectUtil;
import com.ruoyi.common.core.constant.SecurityConstants;
import com.ruoyi.common.core.context.SecurityContextHolder;
import com.ruoyi.common.core.utils.ServletUtils;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.security.auth.AuthUtil;
import com.ruoyi.common.security.utils.SecurityUtils;
import com.ruoyi.system.api.model.LoginUser;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.AsyncHandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 自定义请求头拦截器将Header数据封装到线程变量中方便获取
* 注意:此拦截器会同时验证当前用户有效期自动刷新有效期
*
* @author ruoyi
*/
public class HeaderInterceptor implements AsyncHandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) {
return true;
}
SecurityContextHolder.setUserId(ServletUtils.getHeader(request, SecurityConstants.DETAILS_USER_ID));
SecurityContextHolder.setUserName(ServletUtils.getHeader(request, SecurityConstants.DETAILS_USERNAME));
SecurityContextHolder.setUserKey(ServletUtils.getHeader(request, SecurityConstants.USER_KEY));
String token = SecurityUtils.getToken();
if (StringUtils.isNotEmpty(token)) {
LoginUser loginUser = AuthUtil.getLoginUser(token);
if (ObjectUtil.isNotNull(loginUser)) {
AuthUtil.verifyLoginUserExpire(loginUser);
SecurityContextHolder.set(SecurityConstants.LOGIN_USER, loginUser);
}
}
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
SecurityContextHolder.remove();
}
}

View File

@@ -1,150 +0,0 @@
package com.ruoyi.common.security.service;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import com.ruoyi.common.core.constant.CacheConstants;
import com.ruoyi.common.core.constant.SecurityConstants;
import com.ruoyi.common.core.utils.JwtUtils;
import com.ruoyi.common.core.utils.ServletUtils;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.redis.utils.RedisUtils;
import com.ruoyi.common.security.utils.SecurityUtils;
import com.ruoyi.system.api.model.LoginUser;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* token验证处理
*
* @author ruoyi
*/
@Component
public class TokenService {
protected static final long MILLIS_SECOND = 1000;
protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;
private final static long expireTime = CacheConstants.EXPIRATION;
private final static String ACCESS_TOKEN = CacheConstants.LOGIN_TOKEN_KEY;
private final static Long MILLIS_MINUTE_TEN = CacheConstants.REFRESH_TIME * MILLIS_MINUTE;
/**
* 创建令牌
*/
public Map<String, Object> createToken(LoginUser loginUser) {
String token = IdUtil.fastUUID();
Long userId = loginUser.getSysUser().getUserId();
String userName = loginUser.getSysUser().getUserName();
loginUser.setToken(token);
loginUser.setUserid(userId);
loginUser.setUsername(userName);
loginUser.setIpaddr(ServletUtils.getClientIP());
refreshToken(loginUser);
// Jwt存储信息
Map<String, Object> claimsMap = new HashMap<String, Object>();
claimsMap.put(SecurityConstants.USER_KEY, token);
claimsMap.put(SecurityConstants.DETAILS_USER_ID, userId);
claimsMap.put(SecurityConstants.DETAILS_USERNAME, userName);
// 接口返回信息
Map<String, Object> rspMap = new HashMap<String, Object>();
rspMap.put("access_token", JwtUtils.createToken(claimsMap));
rspMap.put("expires_in", expireTime);
return rspMap;
}
/**
* 获取用户身份信息
*
* @return 用户信息
*/
public LoginUser getLoginUser() {
return getLoginUser(ServletUtils.getRequest());
}
/**
* 获取用户身份信息
*
* @return 用户信息
*/
public LoginUser getLoginUser(HttpServletRequest request) {
// 获取请求携带的令牌
String token = SecurityUtils.getToken(request);
return getLoginUser(token);
}
/**
* 获取用户身份信息
*
* @return 用户信息
*/
public LoginUser getLoginUser(String token) {
LoginUser user = null;
try {
if (StringUtils.isNotEmpty(token)) {
String userkey = JwtUtils.getUserKey(token);
user = RedisUtils.getCacheObject(getTokenKey(userkey));
return user;
}
} catch (Exception e) {
}
return user;
}
/**
* 设置用户身份信息
*/
public void setLoginUser(LoginUser loginUser) {
if (ObjectUtil.isNotNull(loginUser) && StringUtils.isNotEmpty(loginUser.getToken())) {
refreshToken(loginUser);
}
}
/**
* 删除用户缓存信息
*/
public void delLoginUser(String token) {
if (StringUtils.isNotEmpty(token)) {
String userkey = JwtUtils.getUserKey(token);
RedisUtils.deleteObject(getTokenKey(userkey));
}
}
/**
* 验证令牌有效期相差不足120分钟自动刷新缓存
*
* @param loginUser
*/
public void verifyToken(LoginUser loginUser) {
long expireTime = loginUser.getExpireTime();
long currentTime = System.currentTimeMillis();
if (expireTime - currentTime <= MILLIS_MINUTE_TEN) {
refreshToken(loginUser);
}
}
/**
* 刷新令牌有效期
*
* @param loginUser 登录信息
*/
public void refreshToken(LoginUser loginUser) {
loginUser.setLoginTime(System.currentTimeMillis());
loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
// 根据uuid将loginUser缓存
String userKey = getTokenKey(loginUser.getToken());
RedisUtils.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
}
private String getTokenKey(String token) {
return ACCESS_TOKEN + token;
}
}

View File

@@ -1,75 +1,16 @@
package com.ruoyi.common.security.utils;
import com.ruoyi.common.core.constant.SecurityConstants;
import com.ruoyi.common.core.constant.TokenConstants;
import com.ruoyi.common.core.context.SecurityContextHolder;
import com.ruoyi.common.core.utils.ServletUtils;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.system.api.model.LoginUser;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import javax.servlet.http.HttpServletRequest;
/**
* 权限获取工具类
*
* @author ruoyi
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class SecurityUtils {
/**
* 获取用户ID
*/
public static Long getUserId() {
return SecurityContextHolder.getUserId();
}
/**
* 获取用户名称
*/
public static String getUsername() {
return SecurityContextHolder.getUserName();
}
/**
* 获取用户key
*/
public static String getUserKey() {
return SecurityContextHolder.getUserKey();
}
/**
* 获取登录用户信息
*/
public static LoginUser getLoginUser() {
return SecurityContextHolder.get(SecurityConstants.LOGIN_USER, LoginUser.class);
}
/**
* 获取请求token
*/
public static String getToken() {
return getToken(ServletUtils.getRequest());
}
/**
* 根据request获取请求token
*/
public static String getToken(HttpServletRequest request) {
// 从header获取token标识
String token = request.getHeader(TokenConstants.AUTHENTICATION);
return replaceTokenPrefix(token);
}
/**
* 裁剪token前缀
*/
public static String replaceTokenPrefix(String token) {
// 如果前端设置了令牌前缀,则裁剪掉前缀
if (StringUtils.isNotEmpty(token) && token.startsWith(TokenConstants.PREFIX)) {
token = token.replaceFirst(TokenConstants.PREFIX, "");
}
return token;
}
/**
* 是否为管理员

View File

@@ -1,6 +1,3 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.ruoyi.common.security.config.WebMvcConfig,\
com.ruoyi.common.security.service.TokenService,\
com.ruoyi.common.security.aspect.PreAuthorizeAspect,\
com.ruoyi.common.security.aspect.InnerAuthAspect,\
com.ruoyi.common.security.handler.GlobalExceptionHandler
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.ruoyi.common.security.handler.GlobalExceptionHandler,\
com.ruoyi.common.security.config.SecurityConfiguration