Initial commit
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.linln.component</groupId>
|
||||
<artifactId>shiro</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>组件:Shiro权限</name>
|
||||
|
||||
<parent>
|
||||
<groupId>com.linln</groupId>
|
||||
<artifactId>component</artifactId>
|
||||
<version>2.0.3</version>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.linln</groupId>
|
||||
<artifactId>common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.linln.modules</groupId>
|
||||
<artifactId>system</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!--shiro权限管理框架-->
|
||||
<dependency>
|
||||
<groupId>org.apache.shiro</groupId>
|
||||
<artifactId>shiro-spring</artifactId>
|
||||
<version>${shiro.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.shiro</groupId>
|
||||
<artifactId>shiro-ehcache</artifactId>
|
||||
<version>${shiro.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,109 @@
|
||||
package com.linln.component.shiro;
|
||||
|
||||
import com.linln.common.constant.AdminConst;
|
||||
import com.linln.common.enums.StatusEnum;
|
||||
import com.linln.modules.system.domain.Role;
|
||||
import com.linln.modules.system.domain.User;
|
||||
import com.linln.modules.system.service.UserService;
|
||||
import org.apache.shiro.authc.*;
|
||||
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;
|
||||
import org.apache.shiro.authz.AuthorizationInfo;
|
||||
import org.apache.shiro.authz.SimpleAuthorizationInfo;
|
||||
import org.apache.shiro.codec.CodecSupport;
|
||||
import org.apache.shiro.realm.AuthorizingRealm;
|
||||
import org.apache.shiro.subject.PrincipalCollection;
|
||||
import org.apache.shiro.util.ByteSource;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author gion
|
||||
* @date 2018/8/14
|
||||
*/
|
||||
public class AuthRealm extends AuthorizingRealm {
|
||||
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
|
||||
/**
|
||||
* 授权逻辑
|
||||
*/
|
||||
@Override
|
||||
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
|
||||
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
|
||||
// 获取用户Principal对象
|
||||
User user = (User) principal.getPrimaryPrincipal();
|
||||
|
||||
// 管理员拥有所有权限
|
||||
if (user.getId().equals(AdminConst.ADMIN_ID)) {
|
||||
info.addRole(AdminConst.ADMIN_ROLE_NAME);
|
||||
info.addStringPermission("*:*:*");
|
||||
return info;
|
||||
}
|
||||
|
||||
// 赋予角色和资源授权
|
||||
Set<Role> roles = ShiroUtil.getSubjectRoles();
|
||||
roles.forEach(role -> {
|
||||
info.addRole(role.getName());
|
||||
role.getMenus().forEach(menu -> {
|
||||
String perms = menu.getPerms();
|
||||
if (menu.getStatus().equals(StatusEnum.OK.getCode())
|
||||
&& !StringUtils.isEmpty(perms) && !perms.contains("*")) {
|
||||
info.addStringPermission(perms);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* 认证逻辑
|
||||
*/
|
||||
@Override
|
||||
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
|
||||
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
|
||||
// 获取数据库中的用户名密码
|
||||
User user = userService.getByName(token.getUsername());
|
||||
|
||||
// 判断用户名是否存在
|
||||
if (user == null) {
|
||||
throw new UnknownAccountException();
|
||||
} else if (user.getStatus().equals(StatusEnum.FREEZED.getCode())) {
|
||||
throw new LockedAccountException();
|
||||
}
|
||||
|
||||
// 对盐进行加密处理
|
||||
ByteSource salt = ByteSource.Util.bytes(user.getSalt());
|
||||
|
||||
/* 传入密码自动判断是否正确
|
||||
* 参数1:传入对象给Principal
|
||||
* 参数2:正确的用户密码
|
||||
* 参数3:加盐处理
|
||||
* 参数4:固定写法
|
||||
*/
|
||||
return new SimpleAuthenticationInfo(user, user.getPassword(), salt, getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义密码验证匹配器
|
||||
*/
|
||||
@PostConstruct
|
||||
public void initCredentialsMatcher() {
|
||||
setCredentialsMatcher(new SimpleCredentialsMatcher() {
|
||||
@Override
|
||||
public boolean doCredentialsMatch(AuthenticationToken authenticationToken, AuthenticationInfo authenticationInfo) {
|
||||
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
|
||||
SimpleAuthenticationInfo info = (SimpleAuthenticationInfo) authenticationInfo;
|
||||
// 获取明文密码及密码盐
|
||||
String password = String.valueOf(token.getPassword());
|
||||
String salt = CodecSupport.toString(info.getCredentialsSalt().getBytes());
|
||||
|
||||
return equals(ShiroUtil.encrypt(password, salt), info.getCredentials());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package com.linln.component.shiro;
|
||||
|
||||
import com.linln.common.utils.EncryptUtil;
|
||||
import com.linln.common.utils.HttpServletUtil;
|
||||
import com.linln.common.utils.SpringContextUtil;
|
||||
import com.linln.modules.system.domain.Role;
|
||||
import com.linln.modules.system.domain.User;
|
||||
import com.linln.modules.system.service.RoleService;
|
||||
import com.linln.modules.system.service.UserService;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.hibernate.Hibernate;
|
||||
import org.hibernate.LazyInitializationException;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Shiro工具类
|
||||
*
|
||||
* @author gion
|
||||
* @date 2018/8/14
|
||||
*/
|
||||
public class ShiroUtil {
|
||||
|
||||
/**
|
||||
* 多个IP的分隔符
|
||||
*/
|
||||
private static final char IP_SPLIT = ',';
|
||||
|
||||
/**
|
||||
* IP验证正则对象
|
||||
*/
|
||||
private static final Pattern IP_PATTERN = Pattern.compile("\\d{1,3}(\\.\\d{1,3}){3,5}$");
|
||||
|
||||
/**
|
||||
* 加密算法
|
||||
*/
|
||||
public final static String HASH_ALGORITHM_NAME = EncryptUtil.HASH_ALGORITHM_NAME;
|
||||
|
||||
/**
|
||||
* 循环次数
|
||||
*/
|
||||
public final static int HASH_ITERATIONS = EncryptUtil.HASH_ITERATIONS;
|
||||
|
||||
/**
|
||||
* 加密处理(64位字符)
|
||||
* 备注:采用自定义的密码加密方式,其原理与SimpleHash一致,
|
||||
* 为的是在多个模块间可以使用同一套加密方式,方便共用系统用户。
|
||||
*
|
||||
* @param password 密码
|
||||
* @param salt 密码盐
|
||||
*/
|
||||
public static String encrypt(String password, String salt) {
|
||||
return EncryptUtil.encrypt(password, salt, HASH_ALGORITHM_NAME, HASH_ITERATIONS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取随机盐值
|
||||
*/
|
||||
public static String getRandomSalt() {
|
||||
return EncryptUtil.getRandomSalt();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户对象
|
||||
*/
|
||||
public static User getSubject() {
|
||||
User user = (User) SecurityUtils.getSubject().getPrincipal();
|
||||
|
||||
// 初始化延迟加载的部门信息
|
||||
if (user != null && !Hibernate.isInitialized(user.getDept())) {
|
||||
try {
|
||||
Hibernate.initialize(user.getDept());
|
||||
} catch (LazyInitializationException e) {
|
||||
// 部门数据延迟加载超时,重新查询用户数据(用于更新“记住我”状态登录的数据)
|
||||
UserService userService = SpringContextUtil.getBean(UserService.class);
|
||||
User reload = userService.getById(user.getId());
|
||||
Hibernate.initialize(reload.getDept());
|
||||
// 将重载用户数据拷贝到登录用户中
|
||||
BeanUtils.copyProperties(reload, user, "roles");
|
||||
}
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户角色列表
|
||||
*/
|
||||
public static Set<Role> getSubjectRoles() {
|
||||
User user = (User) SecurityUtils.getSubject().getPrincipal();
|
||||
|
||||
// 如果用户为空,则返回空列表
|
||||
if (user == null) {
|
||||
user = new User();
|
||||
}
|
||||
|
||||
// 判断角色列表是否已缓存
|
||||
if (!Hibernate.isInitialized(user.getRoles())) {
|
||||
try {
|
||||
Hibernate.initialize(user.getRoles());
|
||||
} catch (LazyInitializationException e) {
|
||||
// 延迟加载超时,重新查询角色列表数据
|
||||
RoleService roleService = SpringContextUtil.getBean(RoleService.class);
|
||||
user.setRoles(roleService.getUserOkRoleList(user.getId()));
|
||||
}
|
||||
}
|
||||
|
||||
return user.getRoles();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户IP地址
|
||||
*/
|
||||
public static String getIp() {
|
||||
HttpServletRequest request = HttpServletUtil.getRequest();
|
||||
// 反向代理时获取真实ip
|
||||
String ip = request.getHeader("x-forwarded-for");
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("Proxy-Client-IP");
|
||||
}
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("X-Forwarded-For");
|
||||
}
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("WL-Proxy-Client-IP");
|
||||
}
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("X-Real-IP");
|
||||
}
|
||||
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getRemoteAddr();
|
||||
}
|
||||
|
||||
// 校验IP地址,防止恶意伪造请求头信息,暂时只允许ipv4的地址
|
||||
if (StringUtils.hasText(ip)) {
|
||||
ip = ip.substring(ip.lastIndexOf(IP_SPLIT) + 1).trim();
|
||||
if (!IP_PATTERN.matcher(ip).matches()) {
|
||||
ip = "ipv4 unknown";
|
||||
}
|
||||
}
|
||||
|
||||
return ip;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.linln.component.shiro;
|
||||
|
||||
import org.apache.shiro.subject.Subject;
|
||||
import org.apache.shiro.web.filter.AccessControlFilter;
|
||||
import org.apache.shiro.web.util.WebUtils;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* 处理session超时问题拦截器
|
||||
* @author gion
|
||||
* @date 2018/8/14
|
||||
*/
|
||||
public class UserAuthFilter extends AccessControlFilter {
|
||||
|
||||
@Override
|
||||
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
|
||||
if (isLoginRequest(request, response)) {
|
||||
return true;
|
||||
} else {
|
||||
Subject subject = getSubject(request, response);
|
||||
return subject.getPrincipal() != null && (subject.isRemembered() || subject.isAuthenticated());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
|
||||
HttpServletRequest httpRequest = WebUtils.toHttp(request);
|
||||
HttpServletResponse httpResponse = WebUtils.toHttp(response);
|
||||
|
||||
if (httpRequest.getHeader("X-Requested-With") != null
|
||||
&& "XMLHttpRequest".equalsIgnoreCase(httpRequest.getHeader("X-Requested-With"))) {
|
||||
httpResponse.sendError(HttpStatus.UNAUTHORIZED.value());
|
||||
} else {
|
||||
redirectToLogin(request, response);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.linln.component.shiro.config;
|
||||
|
||||
import com.linln.modules.system.domain.User;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.apache.shiro.UnavailableSecurityManagerException;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.domain.AuditorAware;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* 审核员自动赋值配置
|
||||
*
|
||||
* @author gion
|
||||
* @date 2018/8/14
|
||||
*/
|
||||
@Configuration
|
||||
public class AuditorConfig implements AuditorAware<User> {
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Optional<User> getCurrentAuditor() {
|
||||
try {
|
||||
Subject subject = SecurityUtils.getSubject();
|
||||
User user = (User) subject.getPrincipal();
|
||||
return Optional.ofNullable(user);
|
||||
} catch (UnavailableSecurityManagerException e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.linln.component.shiro.config;
|
||||
|
||||
import org.springframework.context.annotation.ComponentScan;
|
||||
|
||||
/**
|
||||
* @author gion
|
||||
* @date 2021/3/19
|
||||
*/
|
||||
@ComponentScan(basePackages = "com.linln.component.shiro")
|
||||
public class ShiroAutoConfig {
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
package com.linln.component.shiro.config;
|
||||
|
||||
import com.linln.component.shiro.AuthRealm;
|
||||
import com.linln.component.shiro.UserAuthFilter;
|
||||
import com.linln.component.shiro.config.properties.ShiroProjectProperties;
|
||||
import com.linln.component.shiro.remember.RememberMeManager;
|
||||
import net.sf.ehcache.CacheManager;
|
||||
import org.apache.shiro.cache.ehcache.EhCacheManager;
|
||||
import org.apache.shiro.codec.Base64;
|
||||
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
|
||||
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
|
||||
import org.apache.shiro.web.mgt.CookieRememberMeManager;
|
||||
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
|
||||
import org.apache.shiro.web.servlet.SimpleCookie;
|
||||
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
/**
|
||||
* @author gion
|
||||
* @date 2018/8/14
|
||||
*/
|
||||
@Configuration
|
||||
public class ShiroConfig {
|
||||
|
||||
@Bean
|
||||
public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager, ShiroProjectProperties properties) {
|
||||
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
|
||||
shiroFilterFactoryBean.setSecurityManager(securityManager);
|
||||
|
||||
/**
|
||||
* 添加自定义拦截器,重写user认证方式,处理session超时问题
|
||||
*/
|
||||
HashMap<String, Filter> myFilters = new HashMap<>(16);
|
||||
myFilters.put("userAuth", new UserAuthFilter());
|
||||
shiroFilterFactoryBean.setFilters(myFilters);
|
||||
|
||||
/**
|
||||
* 过滤规则(注意优先级)
|
||||
* —anon 无需认证(登录)可访问
|
||||
* —authc 必须认证才可访问
|
||||
* —perms[标识] 拥有资源权限才可访问
|
||||
* —role 拥有角色权限才可访问
|
||||
* —user 认证和自动登录可访问
|
||||
*/
|
||||
LinkedHashMap<String, String> filterMap = new LinkedHashMap<>();
|
||||
filterMap.put("/login", "anon");
|
||||
filterMap.put("/logout", "anon");
|
||||
filterMap.put("/captcha", "anon");
|
||||
filterMap.put("/noAuth", "anon");
|
||||
filterMap.put("/css/**", "anon");
|
||||
filterMap.put("/js/**", "anon");
|
||||
filterMap.put("/images/**", "anon");
|
||||
filterMap.put("/lib/**", "anon");
|
||||
filterMap.put("/favicon.ico", "anon");
|
||||
// 通过yml配置文件方式配置的[anon]忽略规则
|
||||
String[] excludes = properties.getExcludes().split(",");
|
||||
for (String exclude : excludes) {
|
||||
if (!StringUtils.isEmpty(exclude.trim())) {
|
||||
filterMap.put(exclude, "anon");
|
||||
}
|
||||
}
|
||||
// 拦截根目录下所有路径,需要放行的路径必须在之前添加
|
||||
filterMap.put("/**", "userAuth");
|
||||
|
||||
// 设置过滤规则
|
||||
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
|
||||
// 设置登录页面
|
||||
shiroFilterFactoryBean.setLoginUrl("/login");
|
||||
// 未授权错误页面
|
||||
shiroFilterFactoryBean.setUnauthorizedUrl("/noAuth");
|
||||
|
||||
return shiroFilterFactoryBean;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DefaultWebSecurityManager getDefaultWebSecurityManager(AuthRealm authRealm,
|
||||
EhCacheManager cacheManager,
|
||||
DefaultWebSessionManager sessionManager,
|
||||
CookieRememberMeManager rememberMeManager) {
|
||||
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
|
||||
securityManager.setRealm(authRealm);
|
||||
securityManager.setCacheManager(cacheManager);
|
||||
securityManager.setSessionManager(sessionManager);
|
||||
securityManager.setRememberMeManager(rememberMeManager);
|
||||
return securityManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义的Realm
|
||||
*/
|
||||
@Bean
|
||||
public AuthRealm getRealm(EhCacheManager ehCacheManager) {
|
||||
AuthRealm authRealm = new AuthRealm();
|
||||
authRealm.setCacheManager(ehCacheManager);
|
||||
return authRealm;
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存管理器-使用Ehcache实现缓存
|
||||
*/
|
||||
@Bean
|
||||
public EhCacheManager ehCacheManager(CacheManager cacheManager) {
|
||||
EhCacheManager ehCacheManager = new EhCacheManager();
|
||||
ehCacheManager.setCacheManager(cacheManager);
|
||||
return ehCacheManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* session管理器
|
||||
*/
|
||||
@Bean
|
||||
public DefaultWebSessionManager getDefaultWebSessionManager(EhCacheManager cacheManager, ShiroProjectProperties properties) {
|
||||
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
|
||||
sessionManager.setCacheManager(cacheManager);
|
||||
sessionManager.setGlobalSessionTimeout(properties.getGlobalSessionTimeout() * 1000);
|
||||
sessionManager.setSessionValidationInterval(properties.getSessionValidationInterval() * 1000);
|
||||
sessionManager.setDeleteInvalidSessions(true);
|
||||
sessionManager.validateSessions();
|
||||
// 去掉登录页面地址栏jsessionid
|
||||
sessionManager.setSessionIdUrlRewritingEnabled(false);
|
||||
return sessionManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* rememberMe管理器
|
||||
*/
|
||||
@Bean
|
||||
public CookieRememberMeManager rememberMeManager(SimpleCookie rememberMeCookie) {
|
||||
RememberMeManager manager = new RememberMeManager();
|
||||
manager.setCipherKey(Base64.decode("WcfHGU25gNnTxTlmJMeSpw=="));
|
||||
manager.setCookie(rememberMeCookie);
|
||||
return manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建一个简单的Cookie对象
|
||||
*/
|
||||
@Bean
|
||||
public SimpleCookie rememberMeCookie(ShiroProjectProperties properties) {
|
||||
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
|
||||
simpleCookie.setHttpOnly(true);
|
||||
// cookie记住登录信息时间,默认7天
|
||||
simpleCookie.setMaxAge(properties.getRememberMeTimeout() * 24 * 60 * 60);
|
||||
return simpleCookie;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用shrio授权注解拦截方式,AOP式方法级权限检查
|
||||
*/
|
||||
@Bean
|
||||
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
|
||||
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor =
|
||||
new AuthorizationAttributeSourceAdvisor();
|
||||
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
|
||||
return authorizationAttributeSourceAdvisor;
|
||||
}
|
||||
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package com.linln.component.shiro.config.properties;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 项目-shiro会话配置项
|
||||
* @author gion
|
||||
* @date 2018/11/6
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "project.shiro")
|
||||
public class ShiroProjectProperties {
|
||||
|
||||
/** cookie记住登录信息时间,默认7天 */
|
||||
private Integer rememberMeTimeout = 7;
|
||||
|
||||
/** Session会话超时时间,默认30分钟 */
|
||||
private Integer globalSessionTimeout = 1800;
|
||||
|
||||
/** Session会话检测间隔时间,默认15分钟 */
|
||||
private Integer sessionValidationInterval = 900;
|
||||
|
||||
/** 忽略的路径规则,多个规则使用","逗号隔开 */
|
||||
private String excludes = "";
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package com.linln.component.shiro.exception;
|
||||
|
||||
import com.linln.common.enums.ResultEnum;
|
||||
import com.linln.common.utils.ResultVoUtil;
|
||||
import com.linln.common.utils.SpringContextUtil;
|
||||
import com.linln.common.vo.ResultVo;
|
||||
import org.apache.shiro.authz.AuthorizationException;
|
||||
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 拦截访问权限异常处理
|
||||
* @author gion
|
||||
* @date 2019/4/26
|
||||
*/
|
||||
@ControllerAdvice
|
||||
@Order(-1)
|
||||
public class AuthorizationExceptionHandler {
|
||||
|
||||
/**
|
||||
* 拦截访问权限异常
|
||||
*/
|
||||
@ExceptionHandler(AuthorizationException.class)
|
||||
@ResponseBody
|
||||
public ResultVo authorizationException(AuthorizationException e, HttpServletRequest request,
|
||||
HttpServletResponse response){
|
||||
Integer code = ResultEnum.NO_PERMISSIONS.getCode();
|
||||
String msg = ResultEnum.NO_PERMISSIONS.getMessage();
|
||||
|
||||
// 获取异常信息
|
||||
Throwable cause = e.getCause();
|
||||
String message = cause.getMessage();
|
||||
Class<ResultVo> resultVoClass = ResultVo.class;
|
||||
|
||||
// 判断无权限访问的方法返回对象是否为ResultVo
|
||||
if(!message.contains(resultVoClass.getName())){
|
||||
try {
|
||||
// 重定向到无权限页面
|
||||
String contextPath = request.getContextPath();
|
||||
ShiroFilterFactoryBean shiroFilter = SpringContextUtil.getBean(ShiroFilterFactoryBean.class);
|
||||
response.sendRedirect(contextPath+shiroFilter.getUnauthorizedUrl());
|
||||
} catch (IOException e1) {
|
||||
return ResultVoUtil.error(code, msg);
|
||||
}
|
||||
}
|
||||
return ResultVoUtil.error(code, msg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package com.linln.component.shiro.remember;
|
||||
|
||||
import com.linln.modules.system.domain.Dept;
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.LazyInitializationException;
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.proxy.HibernateProxy;
|
||||
import org.hibernate.proxy.LazyInitializer;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 记住我部门适配器(用于判断延迟加载超时)
|
||||
*
|
||||
* @author gion
|
||||
* @date 2019/10/30
|
||||
*/
|
||||
public class RememberMeDept extends Dept implements HibernateProxy {
|
||||
|
||||
@Override
|
||||
public Object writeReplace() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LazyInitializer getHibernateLazyInitializer() {
|
||||
|
||||
return new LazyInitializer() {
|
||||
@Override
|
||||
public void initialize() throws HibernateException {
|
||||
throw new LazyInitializationException(RememberMeDept.class.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Serializable getIdentifier() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setIdentifier(Serializable id) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEntityName() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class getPersistentClass() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUninitialized() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getImplementation() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getImplementation(SharedSessionContractImplementor session) throws HibernateException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImplementation(Object target) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReadOnlySettingAvailable() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReadOnly() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReadOnly(boolean readOnly) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public SharedSessionContractImplementor getSession() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSession(SharedSessionContractImplementor session) throws HibernateException {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsetSession() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUnwrap(boolean unwrap) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUnwrap() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
package com.linln.component.shiro.remember;
|
||||
|
||||
import com.linln.common.utils.EntityBeanUtil;
|
||||
import com.linln.component.shiro.AuthRealm;
|
||||
import com.linln.component.shiro.ShiroUtil;
|
||||
import com.linln.modules.system.domain.User;
|
||||
import org.apache.shiro.authc.AuthenticationException;
|
||||
import org.apache.shiro.subject.PrincipalCollection;
|
||||
import org.apache.shiro.subject.SimplePrincipalCollection;
|
||||
import org.apache.shiro.web.mgt.CookieRememberMeManager;
|
||||
import org.hibernate.collection.internal.PersistentSet;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 记住我管理器
|
||||
*
|
||||
* @author gion
|
||||
* @date 2019/10/28
|
||||
*/
|
||||
public class RememberMeManager extends CookieRememberMeManager {
|
||||
|
||||
/**
|
||||
* “记住我”二次加密索引,无需修改
|
||||
*/
|
||||
private final String CIPHER_KEY = "(~ ̄▽ ̄)~";
|
||||
/**
|
||||
* 二次加密密码盐长度
|
||||
*/
|
||||
private final int ENCRYPT_LENGTH = 64;
|
||||
|
||||
/**
|
||||
* 重写“记住我”实体类【系列化】数据
|
||||
*
|
||||
* @param principals the principals to remember for retrieval later.
|
||||
*/
|
||||
@Override
|
||||
protected byte[] serialize(PrincipalCollection principals) {
|
||||
|
||||
// 获取用户信息
|
||||
User user = (User) principals.getPrimaryPrincipal();
|
||||
|
||||
// 克隆一个Principal对象,隐藏用户密码及密码盐,消除部门及角色数据
|
||||
String[] ignores = {"password", "salt", "dept", "roles"};
|
||||
User principal = (User) EntityBeanUtil.cloneBean(user, ignores);
|
||||
|
||||
// 二次加密用户密码
|
||||
String password = ShiroUtil.encrypt(user.getPassword(), user.getSalt());
|
||||
principal.setPassword(password);
|
||||
|
||||
// 定义简单的SimplePrincipal对象
|
||||
SimplePrincipalCollection collection = new SimplePrincipalCollection(principal, AuthRealm.class.getName());
|
||||
return confusion(super.serialize(collection), password);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重写“记住我”实体类【反系列化】数据
|
||||
*
|
||||
* @param serializedIdentity the previously serialized {@code PrincipalCollection} as a byte array
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected PrincipalCollection deserialize(byte[] serializedIdentity) {
|
||||
|
||||
// 获取“记住我”缓存中的用户对象
|
||||
PrincipalCollection collection = super.deserialize(extSerializeData(serializedIdentity));
|
||||
User principal = (User) collection.getPrimaryPrincipal();
|
||||
|
||||
// 提取二次加密密码盐数据
|
||||
byte[] encrypt = new byte[ENCRYPT_LENGTH];
|
||||
System.arraycopy(serializedIdentity, 0, encrypt, 0, encrypt.length);
|
||||
|
||||
// 判断二次加密密码盐是否正确
|
||||
String password = principal.getPassword();
|
||||
byte[] verifyEncrypt = ShiroUtil.encrypt(password, CIPHER_KEY).getBytes();
|
||||
if (!Arrays.equals(encrypt, verifyEncrypt)) {
|
||||
throw new AuthenticationException();
|
||||
}
|
||||
|
||||
// 更新“记住我”用户数据,使部门及角色超时
|
||||
principal.setDept(new RememberMeDept());
|
||||
principal.setRoles(new PersistentSet());
|
||||
return collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* 混淆系列化数据
|
||||
* {格式:二次加密密码盐(64位)+系列化数据}
|
||||
*/
|
||||
private byte[] confusion(byte[] serializeData, String password) {
|
||||
byte[] encrypt = ShiroUtil.encrypt(password, CIPHER_KEY).getBytes();
|
||||
// 合并两个数组
|
||||
byte[] confusionData = new byte[ENCRYPT_LENGTH + serializeData.length];
|
||||
System.arraycopy(encrypt, 0, confusionData, 0, ENCRYPT_LENGTH);
|
||||
System.arraycopy(serializeData, 0, confusionData, ENCRYPT_LENGTH, serializeData.length);
|
||||
|
||||
return confusionData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取系列化数据
|
||||
* {格式:二次加密密码盐(64位)+系列化数据}
|
||||
*/
|
||||
private byte[] extSerializeData(byte[] serializedIdentity) {
|
||||
if (serializedIdentity.length > ENCRYPT_LENGTH) {
|
||||
// 提取系列化数据
|
||||
byte[] serializeData = new byte[serializedIdentity.length - ENCRYPT_LENGTH];
|
||||
System.arraycopy(serializedIdentity, ENCRYPT_LENGTH, serializeData, 0, serializeData.length);
|
||||
|
||||
return serializeData;
|
||||
}
|
||||
return serializedIdentity;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
com.linln.component.shiro.config.ShiroAutoConfig
|
||||
Reference in New Issue
Block a user