若依 1.0

This commit is contained in:
RuoYi
2020-05-20 08:57:11 +08:00
parent 0d44dfa38c
commit eda0dfae86
532 changed files with 47340 additions and 8 deletions

View File

@@ -0,0 +1,26 @@
package com.ruoyi.common.security.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.scheduling.annotation.EnableAsync;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
// 表示通过aop框架暴露该代理对象,AopContext能够访问
@EnableAspectJAutoProxy(exposeProxy = true)
// 指定要扫描的Mapper类的包的路径
@MapperScan("com.ruoyi.**.mapper")
// 开启线程异步执行
@EnableAsync
public @interface EnableCustomConfig
{
}

View File

@@ -0,0 +1,27 @@
package com.ruoyi.common.security.annotation;
import org.springframework.cloud.openfeign.EnableFeignClients;
import java.lang.annotation.*;
/**
* 自定义feign注解
* 添加basePackages路径
*
* @author ruoyi
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@EnableFeignClients
public @interface EnableRyFeignClients
{
String[] value() default {};
String[] basePackages() default { "com.ruoyi" };
Class<?>[] basePackageClasses() default {};
Class<?>[] defaultConfiguration() default {};
Class<?>[] clients() default {};
}

View File

@@ -0,0 +1,30 @@
package com.ruoyi.common.security.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Import;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import com.ruoyi.common.security.component.RyResourceServerAutoConfiguration;
import com.ruoyi.common.security.component.RySecurityBeanDefinitionRegistrar;
/**
* 自定义资源服务注解
*
* @author ruoyi
*/
@Documented
@Inherited
@EnableResourceServer
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Import({ RyResourceServerAutoConfiguration.class, RySecurityBeanDefinitionRegistrar.class })
public @interface EnableRyResourceServer
{
}

View File

@@ -0,0 +1,24 @@
package com.ruoyi.common.security.annotation;
import java.lang.annotation.*;
/**
* 服务调用不鉴权注解
*
* @author ruoyi
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Inner
{
/**
* 是否AOP统一处理
*/
boolean value() default true;
/**
* 需要特殊判空的字段(预留)
*/
String[] field() default {};
}

View File

@@ -0,0 +1,44 @@
package com.ruoyi.common.security.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Component;
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.security.annotation.Inner;
/**
* 服务间接口不鉴权处理逻辑
*
* @author ruoyi
*/
@Aspect
@Component
public class InnerAspect implements Ordered
{
private final Logger log = LoggerFactory.getLogger(InnerAspect.class);
@Around("@annotation(inner)")
public Object around(ProceedingJoinPoint point, Inner inner) throws Throwable
{
String header = ServletUtils.getRequest().getHeader(SecurityConstants.FROM);
if (inner.value() && !StringUtils.equals(SecurityConstants.FROM_IN, header))
{
log.warn("访问接口 {} 没有权限", point.getSignature().getName());
throw new AccessDeniedException("Access is denied");
}
return point.proceed();
}
@Override
public int getOrder()
{
return Ordered.HIGHEST_PRECEDENCE + 1;
}
}

View File

@@ -0,0 +1,35 @@
package com.ruoyi.common.security.component;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSON;
import com.ruoyi.common.core.constant.HttpStatus;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.utils.ServletUtils;
/**
* 自定义认证失败的异常
*
* @author ruoyi
*/
@Component
public class ResourceAuthExceptionEntryPoint implements AuthenticationEntryPoint
{
private final Logger logger = LoggerFactory.getLogger(ResourceAuthExceptionEntryPoint.class);
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException
{
logger.info("令牌不合法,禁止访问 {}", request.getRequestURI());
String msg = authException.getMessage();
ServletUtils.renderString(response, JSON.toJSONString(R.failed(HttpStatus.UNAUTHORIZED, msg)));
}
}

View File

@@ -0,0 +1,37 @@
package com.ruoyi.common.security.component;
import java.io.IOException;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Primary;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
@ConfigurationPropertiesScan
@ComponentScan("com.ruoyi.common.security")
public class RyResourceServerAutoConfiguration
{
@Bean
@Primary
@LoadBalanced
public RestTemplate lbRestTemplate()
{
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(new DefaultResponseErrorHandler()
{
@Override
public void handleError(ClientHttpResponse response) throws IOException
{
if (response.getRawStatusCode() != HttpStatus.BAD_REQUEST.value())
{
super.handleError(response);
}
}
});
return restTemplate;
}
}

View File

@@ -0,0 +1,64 @@
package com.ruoyi.common.security.component;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.DefaultAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
import org.springframework.security.oauth2.provider.token.UserAuthenticationConverter;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.web.client.RestTemplate;
import com.ruoyi.common.security.component.properties.PermitAllUrlProperties;
public class RyResourceServerConfigurerAdapter extends ResourceServerConfigurerAdapter
{
@Autowired
protected ResourceAuthExceptionEntryPoint resourceAuthExceptionEntryPoint;
@Autowired
protected RemoteTokenServices remoteTokenServices;
@Autowired
private AccessDeniedHandler accessDeniedHandler;
@Autowired
private PermitAllUrlProperties permitAllUrl;
@Autowired
private RestTemplate lbRestTemplate;
/**
* 默认的配置,对外暴露
*
* @param httpSecurity
* @throws Exception
*/
@Override
public void configure(HttpSecurity httpSecurity) throws Exception
{
//允许使用iframe 嵌套避免swagger-ui 不被加载的问题
httpSecurity.headers().frameOptions().disable();
ExpressionUrlAuthorizationConfigurer<HttpSecurity>
.ExpressionInterceptUrlRegistry registry = httpSecurity
.authorizeRequests();
permitAllUrl.getUrls()
.forEach(url -> registry.antMatchers(url).permitAll());
registry.anyRequest().authenticated()
.and().csrf().disable();
}
@Override
public void configure(ResourceServerSecurityConfigurer resources)
{
DefaultAccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();
UserAuthenticationConverter userTokenConverter = new RyUserAuthenticationConverter();
accessTokenConverter.setUserTokenConverter(userTokenConverter);
remoteTokenServices.setRestTemplate(lbRestTemplate);
remoteTokenServices.setAccessTokenConverter(accessTokenConverter);
resources.authenticationEntryPoint(resourceAuthExceptionEntryPoint).accessDeniedHandler(accessDeniedHandler)
.tokenServices(remoteTokenServices);
}
}

View File

@@ -0,0 +1,55 @@
package com.ruoyi.common.security.component;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.AllNestedConditions;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration;
import org.springframework.cloud.security.oauth2.client.AccessTokenContextRelay;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.OAuth2ClientContext;
import org.springframework.security.oauth2.config.annotation.web.configuration.OAuth2ClientConfiguration;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfiguration;
import java.lang.annotation.*;
@Configuration
@AutoConfigureAfter(OAuth2AutoConfiguration.class)
@ConditionalOnWebApplication
@ConditionalOnProperty("security.oauth2.client.client-id")
public class RyResourceServerTokenRelayAutoConfiguration
{
@Bean
public AccessTokenContextRelay accessTokenContextRelay(OAuth2ClientContext context)
{
return new AccessTokenContextRelay(context);
}
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OAuth2OnClientInResourceServerCondition.class)
@interface ConditionalOnOAuth2ClientInResourceServer
{
}
private static class OAuth2OnClientInResourceServerCondition extends AllNestedConditions
{
public OAuth2OnClientInResourceServerCondition()
{
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnBean(ResourceServerConfiguration.class)
static class Server
{
}
@ConditionalOnBean(OAuth2ClientConfiguration.class)
static class Client
{
}
}
}

View File

@@ -0,0 +1,34 @@
package com.ruoyi.common.security.component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
import com.ruoyi.common.core.constant.SecurityConstants;
public class RySecurityBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar
{
private final Logger log = LoggerFactory.getLogger(RySecurityBeanDefinitionRegistrar.class);
/**
* 根据注解值动态注入资源服务器的相关属性
*
* @param metadata 注解信息
* @param registry 注册器
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry)
{
if (registry.isBeanNameInUse(SecurityConstants.RESOURCE_SERVER_CONFIGURER))
{
log.warn("本地存在资源服务器配置,覆盖默认配置:" + SecurityConstants.RESOURCE_SERVER_CONFIGURER);
return;
}
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
beanDefinition.setBeanClass(RyResourceServerConfigurerAdapter.class);
registry.registerBeanDefinition(SecurityConstants.RESOURCE_SERVER_CONFIGURER, beanDefinition);
}
}

View File

@@ -0,0 +1,67 @@
package com.ruoyi.common.security.component;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.oauth2.provider.token.UserAuthenticationConverter;
import org.springframework.util.StringUtils;
import com.ruoyi.common.core.constant.SecurityConstants;
import com.ruoyi.common.core.text.Convert;
import com.ruoyi.common.security.domain.LoginUser;
/**
* 根据checktoken 的结果转化用户信息
*
* @author ruoyi
*/
public class RyUserAuthenticationConverter implements UserAuthenticationConverter
{
private static final String N_A = "N/A";
@Override
public Map<String, ?> convertUserAuthentication(Authentication authentication)
{
Map<String, Object> response = new LinkedHashMap<>();
response.put(USERNAME, authentication.getName());
if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty())
{
response.put(AUTHORITIES, AuthorityUtils.authorityListToSet(authentication.getAuthorities()));
}
return response;
}
@Override
public Authentication extractAuthentication(Map<String, ?> map)
{
if (map.containsKey(USERNAME))
{
Collection<? extends GrantedAuthority> authorities = getAuthorities(map);
String username = (String) map.get(SecurityConstants.DETAILS_USERNAME);
Long id = Convert.toLong(map.get(SecurityConstants.DETAILS_USER_ID));
Long deptId = Convert.toLong(map.get(SecurityConstants.DETAILS_DEPT_ID));
LoginUser user = new LoginUser(id, deptId, username, N_A, true, true, true, true, authorities);
return new UsernamePasswordAuthenticationToken(user, N_A, authorities);
}
return null;
}
private Collection<? extends GrantedAuthority> getAuthorities(Map<String, ?> map)
{
Object authorities = map.get(AUTHORITIES);
if (authorities instanceof String)
{
return AuthorityUtils.commaSeparatedStringToAuthorityList((String) authorities);
}
if (authorities instanceof Collection)
{
return AuthorityUtils.commaSeparatedStringToAuthorityList(
StringUtils.collectionToCommaDelimitedString((Collection<?>) authorities));
}
throw new IllegalArgumentException("Authorities must be either a String or a Collection");
}
}

View File

@@ -0,0 +1,27 @@
package com.ruoyi.common.security.component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
/**
* OAuth2 自定义异常处理
*
* @author ruoyi
*/
public class RyWebResponseExceptionTranslator implements WebResponseExceptionTranslator<OAuth2Exception>
{
private final Logger logger = LoggerFactory.getLogger(RyWebResponseExceptionTranslator.class);
@Override
public ResponseEntity<OAuth2Exception> translate(Exception e)
{
OAuth2Exception oAuth2Exception = (OAuth2Exception) e;
logger.error("RyWebResponseExceptionTranslator.status:{},oAuth2ErrorCode:{},message:{}",
oAuth2Exception.getHttpErrorCode(), oAuth2Exception.getOAuth2ErrorCode(), oAuth2Exception.getMessage());
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(oAuth2Exception);
}
}

View File

@@ -0,0 +1,74 @@
package com.ruoyi.common.security.component.properties;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Pattern;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import com.ruoyi.common.core.utils.ReUtil;
import com.ruoyi.common.security.annotation.Inner;
/**
* 资源服务器对外直接暴露url
*
* @author ruoyi
*/
@Configuration
@ConfigurationProperties(prefix = "security.oauth2.ignore")
public class PermitAllUrlProperties implements InitializingBean, ApplicationContextAware
{
private static final Pattern PATTERN = Pattern.compile("\\{(.*?)\\}");
private ApplicationContext applicationContext;
private List<String> urls = new ArrayList<>();
public String ASTERISK = "*";
@Override
public void afterPropertiesSet()
{
RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
map.keySet().forEach(info -> {
HandlerMethod handlerMethod = map.get(info);
// 获取方法上边的注解 替代path variable 为 *
Inner method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), Inner.class);
Optional.ofNullable(method).ifPresent(inner -> info.getPatternsCondition().getPatterns()
.forEach(url -> urls.add(ReUtil.replaceAll(url, PATTERN, ASTERISK))));
// 获取类上边的注解, 替代path variable 为 *
Inner controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), Inner.class);
Optional.ofNullable(controller).ifPresent(inner -> info.getPatternsCondition().getPatterns()
.forEach(url -> urls.add(ReUtil.replaceAll(url, PATTERN, ASTERISK))));
});
}
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException
{
this.applicationContext = context;
}
public List<String> getUrls()
{
return urls;
}
public void setUrls(List<String> urls)
{
this.urls = urls;
}
}

View File

@@ -0,0 +1,52 @@
package com.ruoyi.common.security.domain;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
/**
* 登录用户身份权限
*
* @author ruoyi
*/
public class LoginUser extends User
{
private static final long serialVersionUID = 1L;
/**
* 用户ID
*/
private Long userId;
/**
* 部门ID
*/
private Long deptId;
public LoginUser(Long userId, Long deptId, String username, String password, boolean enabled,
boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked,
Collection<? extends GrantedAuthority> authorities)
{
super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
this.userId = userId;
this.deptId = deptId;
}
public Long getUserId()
{
return userId;
}
public void setUserId(Long userId)
{
this.userId = userId;
}
public Long getDeptId()
{
return deptId;
}
public void setDeptId(Long deptId)
{
this.deptId = deptId;
}
}

View File

@@ -0,0 +1,20 @@
package com.ruoyi.common.security.feign;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import feign.RequestInterceptor;
/**
* Feign配置注册
*
* @author ruoyi
**/
@Configuration
public class OAuth2FeignConfig
{
@Bean
public RequestInterceptor requestInterceptor()
{
return new OAuth2FeignRequestInterceptor();
}
}

View File

@@ -0,0 +1,43 @@
package com.ruoyi.common.security.feign;
import java.util.Collection;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails;
import org.springframework.stereotype.Component;
import com.ruoyi.common.core.constant.SecurityConstants;
import com.ruoyi.common.core.utils.StringUtils;
import feign.RequestInterceptor;
import feign.RequestTemplate;
/**
* feign请求拦截器
*
* @author ruoyi
*/
@Component
public class OAuth2FeignRequestInterceptor implements RequestInterceptor
{
private final String AUTHORIZATION_HEADER = "Authorization";
private final String BEARER_TOKEN_TYPE = "Bearer";
@Override
public void apply(RequestTemplate requestTemplate)
{
Collection<String> fromHeader = requestTemplate.headers().get(SecurityConstants.FROM);
if (StringUtils.isNotEmpty(fromHeader) && fromHeader.contains(SecurityConstants.FROM_IN))
{
return;
}
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication authentication = securityContext.getAuthentication();
if (authentication != null && authentication.getDetails() instanceof OAuth2AuthenticationDetails)
{
OAuth2AuthenticationDetails dateils = (OAuth2AuthenticationDetails) authentication.getDetails();
requestTemplate.header(AUTHORIZATION_HEADER,
String.format("%s %s", BEARER_TOKEN_TYPE, dateils.getTokenValue()));
}
}
}

View File

@@ -0,0 +1,30 @@
package com.ruoyi.common.security.handler;
import org.springframework.context.ApplicationListener;
import org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
/**
* 认证失败事件处理器
*
* @author ruoyi
*/
public abstract class AbstractAuthenticationFailureEvenHandler
implements ApplicationListener<AbstractAuthenticationFailureEvent>
{
@Override
public void onApplicationEvent(AbstractAuthenticationFailureEvent event)
{
AuthenticationException authenticationException = event.getException();
Authentication authentication = (Authentication) event.getSource();
handle(authenticationException, authentication);
}
/**
* 处理登录失败方法
*/
public abstract void handle(AuthenticationException authenticationException, Authentication authentication);
}

View File

@@ -0,0 +1,30 @@
package com.ruoyi.common.security.handler;
import org.springframework.context.ApplicationListener;
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
import org.springframework.security.core.Authentication;
import com.ruoyi.common.core.utils.StringUtils;
/**
* 认证成功事件处理器
*
* @author ruoyi
*/
public abstract class AbstractAuthenticationSuccessEventHandler
implements ApplicationListener<AuthenticationSuccessEvent>
{
@Override
public void onApplicationEvent(AuthenticationSuccessEvent event)
{
Authentication authentication = (Authentication) event.getSource();
if (StringUtils.isNotEmpty(authentication.getAuthorities()))
{
handle(authentication);
}
}
/**
* 处理登录成功方法
*/
public abstract void handle(Authentication authentication);
}

View File

@@ -0,0 +1,33 @@
package com.ruoyi.common.security.handler;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.oauth2.provider.error.OAuth2AccessDeniedHandler;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSON;
import com.ruoyi.common.core.constant.HttpStatus;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.utils.ServletUtils;
/**
* 自定义访问无权限资源时的异常
*
* @author ruoyi
*/
@Component
public class CustomAccessDeniedHandler extends OAuth2AccessDeniedHandler
{
private final Logger logger = LoggerFactory.getLogger(CustomAccessDeniedHandler.class);
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException authException)
{
logger.info("权限不足,请联系管理员 {}", request.getRequestURI());
String msg = authException.getMessage();
ServletUtils.renderString(response, JSON.toJSONString(R.failed(HttpStatus.FORBIDDEN, msg)));
}
}

View File

@@ -0,0 +1,167 @@
package com.ruoyi.common.security.service;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.PatternMatchUtils;
import org.springframework.util.StringUtils;
import com.ruoyi.common.security.domain.LoginUser;
import com.ruoyi.common.security.utils.SecurityUtils;
/**
* RuoYi首创 自定义权限实现ss取自SpringSecurity首字母
*
* @author ruoyi
*/
@Service("ss")
public class PermissionService
{
/** 所有权限标识 */
private static final String ALL_PERMISSION = "*:*:*";
/** 管理员角色权限标识 */
private static final String SUPER_ADMIN = "admin";
private static final String ROLE_DELIMETER = ",";
private static final String PERMISSION_DELIMETER = ",";
/**
* 验证用户是否具备某权限
*
* @param permission 权限字符串
* @return 用户是否具备某权限
*/
public boolean hasPermi(String permission)
{
if (StringUtils.isEmpty(permission))
{
return false;
}
LoginUser loginUser = SecurityUtils.getLoginUser();
if (StringUtils.isEmpty(loginUser) || CollectionUtils.isEmpty(loginUser.getAuthorities()))
{
return false;
}
return hasPermissions(loginUser.getAuthorities(), permission);
}
/**
* 验证用户是否不具备某权限,与 hasPermi逻辑相反
*
* @param permission 权限字符串
* @return 用户是否不具备某权限
*/
public boolean lacksPermi(String permission)
{
return hasPermi(permission) != true;
}
/**
* 验证用户是否具有以下任意一个权限
*
* @param permissions 以 PERMISSION_NAMES_DELIMETER 为分隔符的权限列表
* @return 用户是否具有以下任意一个权限
*/
public boolean hasAnyPermi(String permissions)
{
if (StringUtils.isEmpty(permissions))
{
return false;
}
LoginUser loginUser = SecurityUtils.getLoginUser();
if (StringUtils.isEmpty(loginUser) || CollectionUtils.isEmpty(loginUser.getAuthorities()))
{
return false;
}
Collection<? extends GrantedAuthority> authorities = loginUser.getAuthorities();
for (String permission : permissions.split(PERMISSION_DELIMETER))
{
if (permission != null && hasPermissions(authorities, permission))
{
return true;
}
}
return false;
}
/**
* 判断用户是否拥有某个角色
*
* @param role 角色字符串
* @return 用户是否具备某角色
*/
public boolean hasRole(String role)
{
if (StringUtils.isEmpty(role))
{
return false;
}
LoginUser loginUser = SecurityUtils.getLoginUser();
if (StringUtils.isEmpty(loginUser) || CollectionUtils.isEmpty(loginUser.getAuthorities()))
{
return false;
}
for (GrantedAuthority authorities : loginUser.getAuthorities())
{
String roleKey = authorities.getAuthority();
if (SUPER_ADMIN.contains(roleKey) || roleKey.contains(role))
{
return true;
}
}
return false;
}
/**
* 验证用户是否不具备某角色,与 isRole逻辑相反。
*
* @param role 角色名称
* @return 用户是否不具备某角色
*/
public boolean lacksRole(String role)
{
return hasRole(role) != true;
}
/**
* 验证用户是否具有以下任意一个角色
*
* @param roles 以 ROLE_NAMES_DELIMETER 为分隔符的角色列表
* @return 用户是否具有以下任意一个角色
*/
public boolean hasAnyRoles(String roles)
{
if (StringUtils.isEmpty(roles))
{
return false;
}
LoginUser loginUser = SecurityUtils.getLoginUser();
if (StringUtils.isEmpty(loginUser) || CollectionUtils.isEmpty(loginUser.getAuthorities()))
{
return false;
}
for (String role : roles.split(ROLE_DELIMETER))
{
if (hasRole(role))
{
return true;
}
}
return false;
}
/**
* 判断是否包含权限
*
* @param permissions 权限列表
* @param permission 权限字符串
* @return 用户是否具备某权限
*/
private boolean hasPermissions(Collection<? extends GrantedAuthority> authorities, String permission)
{
return authorities.stream().map(GrantedAuthority::getAuthority).filter(StringUtils::hasText)
.anyMatch(x -> ALL_PERMISSION.contains(x) || PatternMatchUtils.simpleMatch(permission, x));
}
}

View File

@@ -0,0 +1,27 @@
package com.ruoyi.common.security.service;
import javax.sql.DataSource;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import com.ruoyi.common.core.constant.CacheConstants;
/**
* 重写原生方法支持redis缓存
*
* @author ruoyi
*/
public class RedisClientDetailsService extends JdbcClientDetailsService
{
public RedisClientDetailsService(DataSource dataSource)
{
super(dataSource);
}
@Override
@Cacheable(value = CacheConstants.CLIENT_DETAILS_KEY, key = "#clientId", unless = "#result == null")
public ClientDetails loadClientByClientId(String clientId)
{
return super.loadClientByClientId(clientId);
}
}

View File

@@ -0,0 +1,92 @@
package com.ruoyi.common.security.service;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import com.ruoyi.common.core.constant.SecurityConstants;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.enums.UserStatus;
import com.ruoyi.common.core.exception.BaseException;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.security.domain.LoginUser;
import com.ruoyi.system.api.RemoteUserService;
import com.ruoyi.system.api.domain.SysUser;
import com.ruoyi.system.api.model.UserInfo;
/**
* 用户详细信息
*
* @author ruoyi
*/
@Service
public class UserDetailsServiceImpl implements UserDetailsService
{
private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class);
@Autowired
private RemoteUserService remoteUserService;
@Override
public UserDetails loadUserByUsername(String username)
{
R<UserInfo> userResult = remoteUserService.info(username, SecurityConstants.FROM_IN);
checkUser(userResult, username);
return getUserDetails(userResult);
}
public void checkUser(R<UserInfo> userResult, String username)
{
if (StringUtils.isNull(userResult) || StringUtils.isNull(userResult.getData()))
{
log.info("登录用户:{} 不存在.", username);
throw new UsernameNotFoundException("登录用户:" + username + " 不存在");
}
else if (UserStatus.DELETED.getCode().equals(userResult.getData().getSysUser().getDelFlag()))
{
log.info("登录用户:{} 已被删除.", username);
throw new BaseException("对不起,您的账号:" + username + " 已被删除");
}
else if (UserStatus.DISABLE.getCode().equals(userResult.getData().getSysUser().getStatus()))
{
log.info("登录用户:{} 已被停用.", username);
throw new BaseException("对不起,您的账号:" + username + " 已停用");
}
}
/**
* 构建userdetails
*
* @param result 用户信息
* @return
*/
private UserDetails getUserDetails(R<UserInfo> result)
{
UserInfo info = result.getData();
Set<String> dbAuthsSet = new HashSet<String>();
if (StringUtils.isNotEmpty(info.getRoles()))
{
// 获取角色
Arrays.stream(info.getRoles()).forEach(role -> dbAuthsSet.add(role));
// 获取资源
dbAuthsSet.addAll(Arrays.asList(info.getPermissions()));
}
Collection<? extends GrantedAuthority> authorities = AuthorityUtils
.createAuthorityList(dbAuthsSet.toArray(new String[0]));
SysUser user = info.getSysUser();
// 构造security用户
return new LoginUser(user.getUserId(), user.getDeptId(), user.getUserName(), user.getPassword(), true, true,
true, true, authorities);
}
}

View File

@@ -0,0 +1,92 @@
package com.ruoyi.common.security.utils;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import com.ruoyi.common.security.domain.LoginUser;
/**
* 权限获取工具类
*
* @author ruoyi
*/
public class SecurityUtils
{
/**
* 获取Authentication
*/
public static Authentication getAuthentication()
{
return SecurityContextHolder.getContext().getAuthentication();
}
/**
* 获取用户
*/
public static String getUsername()
{
return getLoginUser().getUsername();
}
/**
* 获取用户
*/
public static LoginUser getLoginUser(Authentication authentication)
{
Object principal = authentication.getPrincipal();
if (principal instanceof LoginUser)
{
return (LoginUser) principal;
}
return null;
}
/**
* 获取用户
*/
public static LoginUser getLoginUser()
{
Authentication authentication = getAuthentication();
if (authentication == null)
{
return null;
}
return getLoginUser(authentication);
}
/**
* 生成BCryptPasswordEncoder密码
*
* @param password 密码
* @return 加密字符串
*/
public static String encryptPassword(String password)
{
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
return passwordEncoder.encode(password);
}
/**
* 判断密码是否相同
*
* @param rawPassword 真实密码
* @param encodedPassword 加密后字符
* @return 结果
*/
public static boolean matchesPassword(String rawPassword, String encodedPassword)
{
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
return passwordEncoder.matches(rawPassword, encodedPassword);
}
/**
* 是否为管理员
*
* @param userId 用户ID
* @return 结果
*/
public static boolean isAdmin(Long userId)
{
return userId != null && 1L == userId;
}
}

View File

@@ -0,0 +1,7 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.ruoyi.common.security.service.UserDetailsServiceImpl,\
com.ruoyi.common.security.handler.CustomAccessDeniedHandler,\
com.ruoyi.common.security.component.ResourceAuthExceptionEntryPoint,\
com.ruoyi.common.security.aspect.InnerAspect