mirror of
https://gitee.com/lab1024/smart-admin.git
synced 2025-10-08 05:06:39 +08:00
2.0.0-satoken-alpha
This commit is contained in:
parent
6b05edde22
commit
231d9a9314
@ -33,6 +33,11 @@
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot-starter</artifactId>
|
||||
<version>1.27.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
@ -67,12 +72,6 @@
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!-- spring security -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-log4j2</artifactId>
|
||||
|
@ -0,0 +1,54 @@
|
||||
package net.lab1024.smartadmin.service.common.satoken;
|
||||
|
||||
import cn.dev33.satoken.action.SaTokenActionDefaultImpl;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import net.lab1024.smartadmin.service.common.annoation.NoNeedLogin;
|
||||
import net.lab1024.smartadmin.service.common.annoation.NoValidPrivilege;
|
||||
import net.lab1024.smartadmin.service.common.util.SmartStringUtil;
|
||||
import net.lab1024.smartadmin.service.module.system.menu.service.MenuEmployeeService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* [ sa-token 为支持SmartAdmin 设置所有方法默认存在权限,权限key为类目+方法名 ]
|
||||
*
|
||||
* @author yandanyang
|
||||
* @date 2021/10/13 17:48
|
||||
*/
|
||||
@Component
|
||||
public class SaTokenAuthAction extends SaTokenActionDefaultImpl {
|
||||
|
||||
@Autowired
|
||||
private MenuEmployeeService menuEmployeeService;
|
||||
|
||||
@Override
|
||||
public void validateAnnotation(AnnotatedElement target) {
|
||||
super.validateAnnotation(target);
|
||||
if (target instanceof Method) {
|
||||
Method method = (Method) target;
|
||||
NoNeedLogin noNeedLogin = method.getAnnotation(NoNeedLogin.class);
|
||||
if (noNeedLogin != null) {
|
||||
return;
|
||||
}
|
||||
NoValidPrivilege noValidPrivilege = method.getAnnotation(NoValidPrivilege.class);
|
||||
if (noValidPrivilege != null) {
|
||||
return;
|
||||
}
|
||||
Long employeeId = StpUtil.getLoginIdAsLong();
|
||||
Boolean isSuperman = menuEmployeeService.isSuperman(employeeId);
|
||||
if(isSuperman){
|
||||
return;
|
||||
}
|
||||
String className = method.getDeclaringClass().getName();
|
||||
String methodName = method.getName();
|
||||
List<String> list = SmartStringUtil.splitConvertToList(className, "\\.");
|
||||
String controllerName = list.get(list.size() - 1);
|
||||
String permissionName = controllerName + "." + methodName;
|
||||
StpUtil.stpLogic.checkPermission(permissionName);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package net.lab1024.smartadmin.service.common.satoken;
|
||||
|
||||
import cn.dev33.satoken.stp.StpInterface;
|
||||
import com.google.common.collect.Lists;
|
||||
import net.lab1024.smartadmin.service.module.system.employee.service.EmployeeService;
|
||||
import net.lab1024.smartadmin.service.module.system.login.domain.RequestEmployee;
|
||||
import net.lab1024.smartadmin.service.module.system.menu.domain.vo.MenuVO;
|
||||
import net.lab1024.smartadmin.service.module.system.menu.service.MenuEmployeeService;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* [ 获取某一用户对于的权限列表以及角色信息 ]
|
||||
*
|
||||
* @author yandanyang
|
||||
* @date 2021/10/13 16:56
|
||||
*/
|
||||
@Component
|
||||
public class SaTokenAuthStp implements StpInterface {
|
||||
|
||||
@Autowired
|
||||
private EmployeeService employeeService;
|
||||
@Autowired
|
||||
private MenuEmployeeService menuEmployeeService;
|
||||
|
||||
|
||||
@Override
|
||||
public List<String> getPermissionList(Object loginId, String loginType) {
|
||||
List<String> permissionList = Lists.newArrayList();
|
||||
|
||||
RequestEmployee requestEmployee = employeeService.getById(NumberUtils.toLong(loginId.toString()));
|
||||
List<MenuVO> menuList = menuEmployeeService.getMenuByRoleIdList(requestEmployee.getRoleList(), requestEmployee.getIsSuperMan());
|
||||
menuList.forEach(e -> {
|
||||
if(CollectionUtils.isNotEmpty(e.getPermsList())){
|
||||
permissionList.addAll(e.getPermsList());
|
||||
}
|
||||
});
|
||||
return permissionList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getRoleList(Object loginId, String loginType) {
|
||||
RequestEmployee loginInfoDTO = employeeService.getById(NumberUtils.toLong(loginId.toString()));
|
||||
List<Long> roleIdList = loginInfoDTO.getRoleList();
|
||||
List<String> roleList = roleIdList.stream().map(e -> e.toString()).collect(Collectors.toList());
|
||||
return roleList;
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
package net.lab1024.smartadmin.service.common.security;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import net.lab1024.smartadmin.service.common.code.ErrorCode;
|
||||
import net.lab1024.smartadmin.service.common.code.UserErrorCode;
|
||||
import net.lab1024.smartadmin.service.common.domain.ResponseDTO;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
*
|
||||
* [ 认证失败处理 ]
|
||||
*
|
||||
* @author
|
||||
* @date
|
||||
*/
|
||||
public class SecurityAuthenticationFailHandler implements AuthenticationEntryPoint {
|
||||
|
||||
@Override
|
||||
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException {
|
||||
this.outputResult(response, UserErrorCode.LOGIN_STATE_INVALID);
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出
|
||||
*
|
||||
* @param response
|
||||
* @param errorCode
|
||||
* @throws IOException
|
||||
*/
|
||||
private void outputResult(HttpServletResponse response, ErrorCode errorCode) throws IOException {
|
||||
String msg = JSONObject.toJSONString(ResponseDTO.error(errorCode));
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
response.getWriter().write(msg);
|
||||
response.flushBuffer();
|
||||
}
|
||||
}
|
@ -1,114 +0,0 @@
|
||||
package net.lab1024.smartadmin.service.common.security;
|
||||
|
||||
import net.lab1024.smartadmin.service.common.annoation.NoValidPrivilege;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.security.access.ConfigAttribute;
|
||||
import org.springframework.security.access.prepost.*;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 此类用于默认给所有接口添加权限 @privilegeCheck.checkPermission('%s')
|
||||
* %s 为类名.方法名
|
||||
* 和使用@PreAuthorize("@privilegeCheck.checkPermission('%s')") 效果一致
|
||||
* 避免所有接口都添加一遍 减轻工作量
|
||||
*
|
||||
* @author 罗伊
|
||||
* @date 2021-08-30 23:08
|
||||
*/
|
||||
public class SecurityMetadataSource extends PrePostAnnotationSecurityMetadataSource {
|
||||
|
||||
public static final String PRIVILEGE_CHECK_NAME = "privilegeCheck";
|
||||
|
||||
private static String EXPRESSION_FORMAT = "@privilegeCheck.checkPermission('%s')";
|
||||
|
||||
private final PrePostInvocationAttributeFactory attributeFactory;
|
||||
|
||||
private SecurityUrlMatchers securityUrlMatchers;
|
||||
|
||||
public SecurityMetadataSource(PrePostInvocationAttributeFactory attributeFactory, SecurityUrlMatchers securityUrlMatchers) {
|
||||
super(attributeFactory);
|
||||
this.attributeFactory = attributeFactory;
|
||||
this.securityUrlMatchers = securityUrlMatchers;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Collection<ConfigAttribute> getAttributes(Method method, Class<?> targetClass) {
|
||||
|
||||
//只对固定的包的所有接口进行控制
|
||||
if (!targetClass.getName().startsWith(securityUrlMatchers.getValidPackage())) {
|
||||
return super.getAttributes(method, targetClass);
|
||||
}
|
||||
//自己的控制
|
||||
GetMapping getMapping = method.getAnnotation(GetMapping.class);
|
||||
PostMapping postMapping = method.getAnnotation(PostMapping.class);
|
||||
RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
|
||||
if (getMapping == null && postMapping == null && requestMapping == null) {
|
||||
return super.getAttributes(method, targetClass);
|
||||
}
|
||||
|
||||
//是否需要权限
|
||||
NoValidPrivilege methodNoValidPrivilege = method.getAnnotation(NoValidPrivilege.class);
|
||||
if (methodNoValidPrivilege != null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
NoValidPrivilege classNoValidPrivilege = targetClass.getAnnotation(NoValidPrivilege.class);
|
||||
if (classNoValidPrivilege != null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
//是否添加security原有注解
|
||||
PreAuthorize preAuthorize = method.getAnnotation(PreAuthorize.class);
|
||||
if (preAuthorize != null) {
|
||||
return super.getAttributes(method, targetClass);
|
||||
}
|
||||
PostAuthorize postAuthorize = method.getAnnotation(PostAuthorize.class);
|
||||
if (postAuthorize != null) {
|
||||
return super.getAttributes(method, targetClass);
|
||||
}
|
||||
//URL匹配
|
||||
AntPathMatcher antPathMatcher = new AntPathMatcher();
|
||||
antPathMatcher.setCaseSensitive(false);
|
||||
antPathMatcher.setTrimTokens(true);
|
||||
//无需验证的URL集合
|
||||
List<String> noValidUrlList = securityUrlMatchers.getNoValidUrl();
|
||||
//获取方法的请求路径
|
||||
Set<String> methodUrl = securityUrlMatchers.getMethodUrl(method);
|
||||
if (this.contain(antPathMatcher, noValidUrlList, methodUrl)) {
|
||||
return super.getAttributes(method, targetClass);
|
||||
}
|
||||
ArrayList<ConfigAttribute> configAttributes = new ArrayList(1);
|
||||
String classFullName = targetClass.getName();
|
||||
String methodName = method.getName();
|
||||
String[] classNameArray = StringUtils.split(classFullName, "\\.");
|
||||
String controllerName = classNameArray[classNameArray.length - 1];
|
||||
String privilegeName = controllerName + "." + methodName;
|
||||
String preAuthorizeAttribute = String.format(EXPRESSION_FORMAT, privilegeName);
|
||||
PreInvocationAttribute pre = this.attributeFactory.createPreInvocationAttribute(null, null, preAuthorizeAttribute);
|
||||
if (pre != null) {
|
||||
configAttributes.add(pre);
|
||||
}
|
||||
return configAttributes;
|
||||
}
|
||||
|
||||
public Boolean contain(AntPathMatcher antPathMatcher, List<String> ignores, Set<String> urls) {
|
||||
if (CollectionUtils.isEmpty(ignores)) {
|
||||
return false;
|
||||
}
|
||||
for (String ignoreUrl : ignores) {
|
||||
for (String url : urls) {
|
||||
if (antPathMatcher.match(ignoreUrl, url)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
@ -1,193 +0,0 @@
|
||||
package net.lab1024.smartadmin.service.common.security;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.lab1024.smartadmin.service.common.annoation.NoNeedLogin;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.collections4.MapUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* [ ]
|
||||
*
|
||||
* @author 罗伊
|
||||
* @date 2021/8/31 10:20
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class SecurityUrlMatchers {
|
||||
|
||||
@Value("${project.module}")
|
||||
private String scanPackage;
|
||||
|
||||
@Autowired
|
||||
private WebApplicationContext applicationContext;
|
||||
|
||||
/**
|
||||
* 匿名访问URL
|
||||
*/
|
||||
private List<String> anonymousUrl = Lists.newArrayList();
|
||||
/**
|
||||
* 忽略的URL(注意,加入忽略的URL,无法进入Security filter)
|
||||
*/
|
||||
private List<String> ignoreUrl = Lists.newArrayList();
|
||||
|
||||
/**
|
||||
* 需要登录的
|
||||
*/
|
||||
private List<String> authenticatedUrl = Lists.newArrayList();
|
||||
|
||||
/**
|
||||
* 方法的请求路径
|
||||
*/
|
||||
private Map<Method, Set<String>> methodUrlMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 获取忽略的URL集合
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public synchronized List<String> getIgnoreUrl() {
|
||||
if (CollectionUtils.isNotEmpty(ignoreUrl)) {
|
||||
return ignoreUrl;
|
||||
}
|
||||
ignoreUrl.add("/swagger-ui.html");
|
||||
ignoreUrl.add("/swagger-resources/**");
|
||||
ignoreUrl.add("/webjars/**");
|
||||
ignoreUrl.add("/*/api-docs");
|
||||
log.info("忽略URL:{}", ignoreUrl);
|
||||
return ignoreUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* 需要登录认证的URL集合
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public synchronized List<String> getAuthenticatedUrlList() {
|
||||
if (CollectionUtils.isNotEmpty(authenticatedUrl)) {
|
||||
return authenticatedUrl;
|
||||
}
|
||||
authenticatedUrl.add("/admin/**");
|
||||
log.info("认证URL:{}", authenticatedUrl);
|
||||
return authenticatedUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取无需登录可以匿名访问的url信息
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private synchronized List<String> getAnonymousUrl() {
|
||||
if (CollectionUtils.isNotEmpty(anonymousUrl)) {
|
||||
return anonymousUrl;
|
||||
}
|
||||
Map<Method, Set<String>> methodSetMap = this.getMethodUrlMap();
|
||||
for (Entry<Method, Set<String>> entry : methodSetMap.entrySet()) {
|
||||
Method method = entry.getKey();
|
||||
NoNeedLogin noNeedLogin = method.getAnnotation(NoNeedLogin.class);
|
||||
if (null == noNeedLogin) {
|
||||
continue;
|
||||
}
|
||||
anonymousUrl.addAll(entry.getValue());
|
||||
}
|
||||
log.info("匿名URL:{}", anonymousUrl);
|
||||
return anonymousUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取每个方法的请求路径
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private synchronized Map<Method, Set<String>> getMethodUrlMap() {
|
||||
if (MapUtils.isNotEmpty(methodUrlMap)) {
|
||||
return methodUrlMap;
|
||||
}
|
||||
RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
|
||||
//获取url与类和方法的对应信息
|
||||
Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
|
||||
for (Entry<RequestMappingInfo, HandlerMethod> entry : map.entrySet()) {
|
||||
RequestMappingInfo requestMappingInfo = entry.getKey();
|
||||
Set<String> urls = requestMappingInfo.getPatternsCondition().getPatterns();
|
||||
if (CollectionUtils.isEmpty(urls)) {
|
||||
continue;
|
||||
}
|
||||
HandlerMethod handlerMethod = entry.getValue();
|
||||
methodUrlMap.put(handlerMethod.getMethod(), urls);
|
||||
}
|
||||
return methodUrlMap;
|
||||
}
|
||||
|
||||
public Set<String> getMethodUrl(Method method) {
|
||||
return methodUrlMap.get(method);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取需要校验的包路径
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String getValidPackage() {
|
||||
return scanPackage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 不需要权限校验的
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public List<String> getNoValidUrl() {
|
||||
List<String> noValidUrl = Lists.newArrayList();
|
||||
noValidUrl.addAll(this.getIgnoreUrl());
|
||||
noValidUrl.addAll(this.getAnonymousUrl());
|
||||
return noValidUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取需要忽略的url集合
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String[] getIgnoreUrlArray() {
|
||||
List<String> ignoreUrl = this.getIgnoreUrl();
|
||||
String[] ignoreUrlArray = ignoreUrl.toArray(new String[ignoreUrl.size()]);
|
||||
return ignoreUrlArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取需要匿名访问的url集合
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String[] getAnonymousUrlArray() {
|
||||
List<String> anonymousUrl = this.getAnonymousUrl();
|
||||
String[] anonymousUrlArray = anonymousUrl.toArray(new String[anonymousUrl.size()]);
|
||||
return anonymousUrlArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取需要认证的url集合
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public String[] getAuthenticatedUrlArray() {
|
||||
List<String> authenticatedUrl = this.getAuthenticatedUrlList();
|
||||
String[] authenticatedUrlArray = authenticatedUrl.toArray(new String[authenticatedUrl.size()]);
|
||||
return authenticatedUrlArray;
|
||||
}
|
||||
|
||||
}
|
@ -2,8 +2,6 @@ package net.lab1024.smartadmin.service.common.util;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.lab1024.smartadmin.service.module.system.login.domain.RequestEmployee;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
||||
/**
|
||||
* @author 罗伊
|
||||
@ -11,39 +9,27 @@ import org.springframework.security.core.context.SecurityContextHolder;
|
||||
@Slf4j
|
||||
public class SmartRequestUtil {
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private static final ThreadLocal<RequestEmployee> LOCAL_USER = new ThreadLocal<>();
|
||||
|
||||
public static void setRequestEmployee(RequestEmployee requestEmployee) {
|
||||
LOCAL_USER.set(requestEmployee);
|
||||
}
|
||||
|
||||
public static RequestEmployee getRequestEmployee() {
|
||||
try {
|
||||
return (RequestEmployee) getAuthentication().getPrincipal();
|
||||
} catch (Exception e) {
|
||||
log.error("获取用户信息异常:{}", e);
|
||||
}
|
||||
return null;
|
||||
return LOCAL_USER.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户认证信息
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static Authentication getAuthentication() {
|
||||
return SecurityContextHolder.getContext().getAuthentication();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户id
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static Long getRequestEmployeeId() {
|
||||
RequestEmployee requestUser = getRequestEmployee();
|
||||
if (null == requestUser) {
|
||||
RequestEmployee requestEmployee = getRequestEmployee();
|
||||
if (null == requestEmployee) {
|
||||
return null;
|
||||
}
|
||||
return requestUser.getEmployeeId();
|
||||
return requestEmployee.getEmployeeId();
|
||||
}
|
||||
|
||||
public static void remove() {
|
||||
LOCAL_USER.remove();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,107 @@
|
||||
package net.lab1024.smartadmin.service.config;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.interceptor.SaAnnotationInterceptor;
|
||||
import cn.dev33.satoken.interceptor.SaRouteInterceptor;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.strategy.SaStrategy;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.lab1024.smartadmin.service.common.annoation.NoNeedLogin;
|
||||
import net.lab1024.smartadmin.service.common.constant.UrlPrefixConst;
|
||||
import net.lab1024.smartadmin.service.common.util.SmartRequestUtil;
|
||||
import net.lab1024.smartadmin.service.module.system.login.domain.LoginUserDetail;
|
||||
import net.lab1024.smartadmin.service.module.system.login.service.JwtService;
|
||||
import org.apache.commons.lang3.math.NumberUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
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;
|
||||
|
||||
/**
|
||||
* [ ]
|
||||
*
|
||||
* @author yandanyang
|
||||
* @date 2021/10/13 16:36
|
||||
*/
|
||||
@Slf4j
|
||||
@Configuration
|
||||
public class SaTokenConfigure implements WebMvcConfigurer {
|
||||
|
||||
@Value("${access-control-allow-origin}")
|
||||
private String accessControlAllowOrigin;
|
||||
|
||||
@Autowired
|
||||
private JwtService jwtService;
|
||||
|
||||
/**
|
||||
* 注册Sa-Token的注解拦截器,打开注解式鉴权功能
|
||||
*
|
||||
* @param registry
|
||||
*/
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
|
||||
registry.addInterceptor(new SaRouteInterceptor((req, res, handler) -> {
|
||||
//跨域
|
||||
this.crossDomainConfig((HttpServletResponse) res.getSource());
|
||||
boolean isHandlerMethod = handler instanceof HandlerMethod;
|
||||
if (!isHandlerMethod) {
|
||||
return;
|
||||
}
|
||||
HttpServletRequest request = (HttpServletRequest) req.getSource();
|
||||
String uri = request.getRequestURI();
|
||||
String contextPath = request.getContextPath();
|
||||
String target = uri.replaceFirst(contextPath, "");
|
||||
//忽略URL
|
||||
if (target.startsWith("/swagger")) {
|
||||
return;
|
||||
}
|
||||
//支持服务重启后 token依然有效 ,如果实现SaTokenDao 为redis或第三方持久化存储的话可放弃此部分代码
|
||||
String tokenValue = StpUtil.getTokenValue();
|
||||
LoginUserDetail employeeLoginInfoDTO = jwtService.getEmployeeLoginBO(tokenValue);
|
||||
if (employeeLoginInfoDTO != null) {
|
||||
StpUtil.stpLogic.saveTokenToIdMapping(tokenValue, employeeLoginInfoDTO.getEmployeeId(), SaManager.getConfig().getTimeout());
|
||||
SmartRequestUtil.setRequestEmployee(employeeLoginInfoDTO);
|
||||
return;
|
||||
}
|
||||
//无需登录
|
||||
NoNeedLogin noNeedLogin = ((HandlerMethod) handler).getMethodAnnotation(NoNeedLogin.class);
|
||||
if (noNeedLogin != null) {
|
||||
return;
|
||||
}
|
||||
//其他情况验证登录
|
||||
StpUtil.checkLogin();
|
||||
})).addPathPatterns(UrlPrefixConst.SYSTEM + "/**");
|
||||
registry.addInterceptor(new SaAnnotationInterceptor()).addPathPatterns(UrlPrefixConst.SYSTEM + "/**");
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置跨域
|
||||
*
|
||||
* @param response
|
||||
*/
|
||||
private void crossDomainConfig(HttpServletResponse response) {
|
||||
response.setHeader("Access-Control-Allow-Origin", accessControlAllowOrigin);
|
||||
response.setHeader("Access-Control-Allow-Credentials", "true");
|
||||
response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE, PATCH");
|
||||
response.setHeader("Access-Control-Expose-Headers", "*");
|
||||
response.setHeader("Access-Control-Allow-Headers", "Authentication,Origin, X-Requested-With, Content-Type, " + "Accept, x-access-token");
|
||||
response.setHeader("Cache-Control", "no-cache");
|
||||
response.setHeader("Pragma", "no-cache");
|
||||
response.setHeader("Expires ", "-1");
|
||||
}
|
||||
|
||||
|
||||
@Autowired
|
||||
public void rewriteSaStrategy() {
|
||||
// 重写 Token 生成策略
|
||||
SaStrategy.me.createToken = (loginId, loginType) -> {
|
||||
return jwtService.generateJwtToken(NumberUtils.toLong(loginId.toString()));
|
||||
};
|
||||
}
|
||||
}
|
@ -1,100 +0,0 @@
|
||||
package net.lab1024.smartadmin.service.config;
|
||||
|
||||
import net.lab1024.smartadmin.service.common.security.SecurityUrlMatchers;
|
||||
import net.lab1024.smartadmin.service.filter.SecurityTokenFilter;
|
||||
import net.lab1024.smartadmin.service.common.security.SecurityAuthenticationFailHandler;
|
||||
import net.lab1024.smartadmin.service.module.system.login.service.JwtService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.WebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Spring Security
|
||||
*
|
||||
* @author zhuoda
|
||||
* @date 2021/8/3 17:50
|
||||
*/
|
||||
@Configuration
|
||||
public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Value("${access-control-allow-origin}")
|
||||
private String accessControlAllowOrigin;
|
||||
/**
|
||||
* url
|
||||
*/
|
||||
@Autowired
|
||||
private SecurityUrlMatchers securityUrlMatchers;
|
||||
|
||||
/**
|
||||
* 获取TOKEN 解析类
|
||||
*/
|
||||
@Autowired
|
||||
private JwtService loginTokenService;
|
||||
|
||||
/**
|
||||
* 跨域配置
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Bean
|
||||
public CorsFilter corsFilter() {
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
config.setAllowCredentials(true);
|
||||
// 设置访问源地址
|
||||
config.addAllowedOriginPattern(accessControlAllowOrigin);
|
||||
// 设置访问源请求头
|
||||
config.addAllowedHeader("*");
|
||||
// 设置访问源请求方法
|
||||
config.addAllowedMethod("*");
|
||||
// 对接口配置跨域设置
|
||||
source.registerCorsConfiguration("/**", config);
|
||||
return new CorsFilter(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity httpSecurity) throws Exception {
|
||||
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry interceptUrlRegistry = httpSecurity
|
||||
// CSRF禁用,因为不使用session
|
||||
.csrf().disable()
|
||||
// 认证失败处理类
|
||||
.exceptionHandling().authenticationEntryPoint(new SecurityAuthenticationFailHandler()).and()
|
||||
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
|
||||
// 过滤请求
|
||||
.authorizeRequests();
|
||||
//可以匿名登录的URL
|
||||
String [] anonymousUrlArray = securityUrlMatchers.getAnonymousUrlArray();
|
||||
interceptUrlRegistry.antMatchers(anonymousUrlArray).permitAll();
|
||||
|
||||
//登录的URL
|
||||
String [] authenticatedUrlArray = securityUrlMatchers.getAuthenticatedUrlArray();
|
||||
interceptUrlRegistry.antMatchers(authenticatedUrlArray).authenticated();
|
||||
|
||||
httpSecurity.addFilterBefore(new SecurityTokenFilter(loginTokenService), UsernamePasswordAuthenticationFilter.class);
|
||||
httpSecurity.addFilterBefore(corsFilter(), SecurityTokenFilter.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(WebSecurity web) {
|
||||
// 忽略url
|
||||
WebSecurity.IgnoredRequestConfigurer ignoring = web.ignoring();
|
||||
List<String> ignoreUrlListList = securityUrlMatchers.getIgnoreUrl();
|
||||
for (String url : ignoreUrlListList) {
|
||||
ignoring.antMatchers(url);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
package net.lab1024.smartadmin.service.config;
|
||||
|
||||
import net.lab1024.smartadmin.service.common.security.SecurityMetadataSource;
|
||||
import net.lab1024.smartadmin.service.common.security.SecurityUrlMatchers;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.expression.method.ExpressionBasedAnnotationAttributeFactory;
|
||||
import org.springframework.security.access.method.MethodSecurityMetadataSource;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
|
||||
|
||||
/**
|
||||
* 此类用于注入自己的 method校验
|
||||
* SmartSecurityMetadataSource
|
||||
* @author zhuoda
|
||||
* @date 2021-08-31 0:01
|
||||
*/
|
||||
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||
public class SecurityMethodConfig extends GlobalMethodSecurityConfiguration {
|
||||
|
||||
@Autowired
|
||||
private SecurityUrlMatchers securityUrlMatchers;
|
||||
|
||||
@Override
|
||||
public MethodSecurityMetadataSource customMethodSecurityMetadataSource(){
|
||||
ExpressionBasedAnnotationAttributeFactory attributeFactory = new ExpressionBasedAnnotationAttributeFactory(this.getExpressionHandler());
|
||||
return new SecurityMetadataSource(attributeFactory, securityUrlMatchers);
|
||||
}
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
package net.lab1024.smartadmin.service.filter;
|
||||
|
||||
import net.lab1024.smartadmin.service.common.constant.RequestHeaderConst;
|
||||
import net.lab1024.smartadmin.service.module.system.login.domain.LoginUserDetail;
|
||||
import net.lab1024.smartadmin.service.module.system.login.service.JwtService;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* [ ]
|
||||
* 注意此处不能 加入@Component
|
||||
* 否则对应ignoreUrl的相关请求 将会进入此Filter,并会覆盖CorsFilter
|
||||
*
|
||||
* @author 罗伊
|
||||
* @date
|
||||
*/
|
||||
|
||||
public class SecurityTokenFilter extends OncePerRequestFilter {
|
||||
|
||||
private JwtService loginTokenService;
|
||||
|
||||
public SecurityTokenFilter(JwtService loginTokenService) {
|
||||
this.loginTokenService = loginTokenService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
|
||||
throws ServletException, IOException {
|
||||
//需要做token校验, 消息头的token优先于请求query参数的token
|
||||
String xHeaderToken = request.getHeader(RequestHeaderConst.TOKEN);
|
||||
String xRequestToken = request.getParameter(RequestHeaderConst.TOKEN);
|
||||
String xAccessToken = null != xHeaderToken ? xHeaderToken : xRequestToken;
|
||||
if (StringUtils.isBlank(xAccessToken)) {
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
//清理spring security
|
||||
SecurityContextHolder.clearContext();
|
||||
LoginUserDetail loginUserDetail = loginTokenService.getEmployeeLoginBO(xAccessToken);
|
||||
if (null != loginUserDetail) {
|
||||
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUserDetail, null, loginUserDetail.getAuthorities());
|
||||
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
|
||||
}
|
||||
// 若未给予spring security上下文用户授权 则会授权失败 进入AuthenticationEntryPointImpl
|
||||
chain.doFilter(request, response);
|
||||
}
|
||||
}
|
@ -1,19 +1,18 @@
|
||||
package net.lab1024.smartadmin.service.handler;
|
||||
|
||||
import cn.dev33.satoken.exception.NotLoginException;
|
||||
import cn.dev33.satoken.exception.NotPermissionException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.lab1024.smartadmin.service.common.code.SystemErrorCode;
|
||||
import net.lab1024.smartadmin.service.common.code.UserErrorCode;
|
||||
import net.lab1024.smartadmin.service.common.domain.ResponseDTO;
|
||||
import net.lab1024.smartadmin.service.common.domain.SystemEnvironment;
|
||||
import net.lab1024.smartadmin.service.common.enumeration.SystemEnvironmentEnum;
|
||||
import net.lab1024.smartadmin.service.common.exception.BusinessException;
|
||||
import org.springframework.beans.TypeMismatchException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
@ -81,14 +80,26 @@ public class GlobalExceptionHandler {
|
||||
* 权限异常
|
||||
*/
|
||||
@ResponseBody
|
||||
@ExceptionHandler({AccessDeniedException.class})
|
||||
public ResponseDTO<?> permissionExceptionHandler(AccessDeniedException e) {
|
||||
@ExceptionHandler({NotPermissionException.class})
|
||||
public ResponseDTO<?> permissionExceptionHandler(NotPermissionException e) {
|
||||
if (!systemEnvironment.isProd()) {
|
||||
log.error("全局参数异常,URL:{}", getCurrentRequestUrl(), e);
|
||||
}
|
||||
return ResponseDTO.error(UserErrorCode.NO_PERMISSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* 未登录异常
|
||||
*/
|
||||
@ResponseBody
|
||||
@ExceptionHandler({NotLoginException.class})
|
||||
public ResponseDTO<?> notLoginExceptionHandler(NotLoginException e) {
|
||||
if (!systemEnvironment.isProd()) {
|
||||
log.error("全局参数异常,URL:{}", getCurrentRequestUrl(), e);
|
||||
}
|
||||
return ResponseDTO.error(UserErrorCode.LOGIN_STATE_INVALID);
|
||||
}
|
||||
|
||||
/**
|
||||
* 业务异常
|
||||
*/
|
||||
|
@ -1,10 +1,6 @@
|
||||
package net.lab1024.smartadmin.service.module.system.login.domain;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* 员工登陆BO
|
||||
@ -13,63 +9,11 @@ import java.util.Collection;
|
||||
* @date 2021/8/4 11:15
|
||||
*/
|
||||
@Data
|
||||
public class LoginUserDetail extends RequestEmployee implements UserDetails {
|
||||
public class LoginUserDetail extends RequestEmployee {
|
||||
|
||||
/**
|
||||
* 登录密码
|
||||
*/
|
||||
private String loginPassword;
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return this.getLoginPassword();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return this.getLoginName();
|
||||
}
|
||||
|
||||
/**
|
||||
* 账户是否未过期,过期无法验证
|
||||
*/
|
||||
@Override
|
||||
public boolean isAccountNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定用户是否解锁,锁定的用户无法进行身份验证
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean isAccountNonLocked() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 指示是否已过期的用户的凭据(密码),过期的凭据防止认证
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean isCredentialsNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否可用 ,禁用的用户不能身份验证
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.lab1024.smartadmin.service.module.system.login.service;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
@ -21,11 +22,6 @@ import java.util.Date;
|
||||
@Service
|
||||
public class JwtService {
|
||||
|
||||
/**
|
||||
* 默认 token 过期时间 1 天
|
||||
*/
|
||||
private static final int EXPIRE_SECONDS = 24 * 3600;
|
||||
|
||||
/**
|
||||
* 默认 jwt key
|
||||
*/
|
||||
@ -53,7 +49,7 @@ public class JwtService {
|
||||
return Jwts.builder()
|
||||
.setClaims(jwtClaims)
|
||||
.setIssuedAt(new Date(nowTimeMilli))
|
||||
.setExpiration(new Date(nowTimeMilli + EXPIRE_SECONDS * 1000))
|
||||
.setExpiration(new Date(SaManager.getConfig().getTimeout() * 1000))
|
||||
.signWith(SignatureAlgorithm.HS512, JWT_KEY)
|
||||
.compact();
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.lab1024.smartadmin.service.module.system.login.service;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.lab1024.smartadmin.service.common.code.UserErrorCode;
|
||||
import net.lab1024.smartadmin.service.common.constant.StringConst;
|
||||
@ -86,7 +87,9 @@ public class LoginService {
|
||||
}
|
||||
|
||||
// 生成 登录token
|
||||
String token = jwtService.generateJwtToken(employeeEntity.getEmployeeId());
|
||||
Long employeeId = employeeEntity.getEmployeeId();
|
||||
StpUtil.login(employeeId);
|
||||
String token = StpUtil.getTokenValue();
|
||||
// 获取前端菜单以及功能权限
|
||||
MenuLoginBO menuLoginBORespDTO = menuEmployeeService.queryMenuTreeByEmployeeId(employeeEntity.getEmployeeId());
|
||||
// 查询部门
|
||||
|
@ -115,7 +115,7 @@ public class MenuEmployeeService {
|
||||
* @param roleIdList
|
||||
* @return
|
||||
*/
|
||||
private List<MenuVO> getMenuByRoleIdList(List<Long> roleIdList, Boolean isSuperman) {
|
||||
public List<MenuVO> getMenuByRoleIdList(List<Long> roleIdList, Boolean isSuperman) {
|
||||
if (CollectionUtils.isEmpty(roleIdList)) {
|
||||
return Lists.newArrayList();
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package net.lab1024.smartadmin.service.module.system.menu.service;
|
||||
|
||||
import net.lab1024.smartadmin.service.common.security.SecurityMetadataSource;
|
||||
import net.lab1024.smartadmin.service.common.util.SmartRequestUtil;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@ -18,7 +17,7 @@ import javax.servlet.http.HttpServletRequest;
|
||||
* @author lihaifan
|
||||
* @date 2021/8/5 17:14
|
||||
*/
|
||||
@Service(SecurityMetadataSource.PRIVILEGE_CHECK_NAME)
|
||||
@Service
|
||||
public class MenuPermissionService {
|
||||
|
||||
@Autowired
|
||||
|
@ -94,3 +94,12 @@ heart-beat.intervalTime=300000
|
||||
######################### cache config #########################
|
||||
spring.cache.type=caffeine
|
||||
|
||||
######################### sa-token config #########################
|
||||
# token名称 (同时也是cookie名称)
|
||||
sa-token.token-name=x-access-token
|
||||
# token有效期,单位s 默认30天, -1代表永不过期
|
||||
sa-token.timeout=2592000
|
||||
# 是否输出banner
|
||||
sa-token.is-print=false
|
||||
# 是否输出操作日志
|
||||
sa-token.is-log=false
|
Loading…
Reference in New Issue
Block a user