2.0.0-satoken-alpha

This commit is contained in:
yandanyang 2021-11-10 19:22:25 +08:00
parent 6b05edde22
commit 231d9a9314
18 changed files with 269 additions and 641 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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);
}
/**
* 业务异常
*/

View File

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

View File

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

View File

@ -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());
// 查询部门

View File

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

View File

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

View File

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