@@ -39,6 +39,10 @@ | |||
</properties> | |||
<dependencies> | |||
<dependency> | |||
<groupId>org.springframework.boot</groupId> | |||
<artifactId>spring-boot-starter-thymeleaf</artifactId> | |||
</dependency> | |||
<!--mybatis--> | |||
<dependency> | |||
<groupId>org.mybatis.spring.boot</groupId> | |||
@@ -4,14 +4,22 @@ import lombok.extern.slf4j.Slf4j; | |||
import org.mybatis.spring.annotation.MapperScan; | |||
import org.springframework.boot.SpringApplication; | |||
import org.springframework.boot.autoconfigure.SpringBootApplication; | |||
import org.springframework.stereotype.Controller; | |||
import org.springframework.web.bind.annotation.GetMapping; | |||
@SpringBootApplication | |||
@MapperScan(basePackages = {"com.xkcoding.springbootdemorabcshiromybatis.dao"}) | |||
@Slf4j | |||
@Controller | |||
public class SpringBootDemoRabcShiroMybatisApplication { | |||
public static void main(String[] args) { | |||
SpringApplication.run(SpringBootDemoRabcShiroMybatisApplication.class, args); | |||
log.info("SpringBootDemoRabcShiroMybatisApplication 启动成功。。。。"); | |||
} | |||
@GetMapping("/login.page") | |||
public String index() { | |||
return "index"; | |||
} | |||
} |
@@ -1,30 +0,0 @@ | |||
package com.xkcoding.springbootdemorabcshiromybatis.config; | |||
import lombok.extern.slf4j.Slf4j; | |||
import org.springframework.boot.CommandLineRunner; | |||
import org.springframework.stereotype.Component; | |||
/** | |||
* <p> | |||
* 数据初始化,实现 CommandLineRunner 接口,启动 springboot 后自动执行,如果有多个这个类可以根据 @Order 来指定执行顺序 | |||
* </p> | |||
* | |||
* @package: com.xkcoding.springbootdemorabcshiromybatis | |||
* @description: 数据初始化 | |||
* @author: yangkai.shen | |||
* @date: Created in 2017/11/29 下午4:32 | |||
* @copyright: Copyright (c) 2017 | |||
* @version: 0.0.1 | |||
* @modified: yangkai.shen | |||
*/ | |||
@Component | |||
@Slf4j | |||
public class DBInitConfig implements CommandLineRunner { | |||
@Override | |||
public void run(String... strings) throws Exception { | |||
log.info("正在初始化数据。。。"); | |||
log.info("数据初始化完成。。。"); | |||
} | |||
} |
@@ -1,78 +0,0 @@ | |||
package com.xkcoding.springbootdemorabcshiromybatis.config; | |||
import com.google.common.collect.Maps; | |||
import com.xkcoding.springbootdemorabcshiromybatis.shiro.MyShiroRealm; | |||
import lombok.extern.slf4j.Slf4j; | |||
import org.apache.shiro.mgt.DefaultSecurityManager; | |||
import org.apache.shiro.mgt.SecurityManager; | |||
import org.apache.shiro.spring.web.ShiroFilterFactoryBean; | |||
import org.apache.shiro.web.mgt.DefaultWebSecurityManager; | |||
import org.springframework.context.annotation.Bean; | |||
import org.springframework.context.annotation.Configuration; | |||
import java.util.Map; | |||
/** | |||
* Shiro 配置 | |||
* | |||
* @package: com.xkcoding.springbootdemorabcshiromybatis.config | |||
* @description: Shiro 配置 | |||
* @author: yangkai.shen | |||
* @date: Created in 2017/11/29 下午3:24 | |||
* @copyright: Copyright (c) 2017 | |||
* @version: 0.0.1 | |||
* @modified: yangkai.shen | |||
*/ | |||
@Configuration | |||
@Slf4j | |||
public class ShiroConfig { | |||
@Bean | |||
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { | |||
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); | |||
// 必须设置 securityManager | |||
shiroFilterFactoryBean.setSecurityManager(securityManager); | |||
// 设置登录页面,默认是 Web 工程目录下的"/login.jsp" | |||
shiroFilterFactoryBean.setLoginUrl("/login"); | |||
// 设置登陆成功后的页面 | |||
shiroFilterFactoryBean.setSuccessUrl("/index"); | |||
// 设置未授权页面 | |||
shiroFilterFactoryBean.setUnauthorizedUrl("/403"); | |||
// 配置拦截器链,注意配置的顺序,很关键 | |||
// 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 | |||
// authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问 | |||
Map<String, String> filterChainDefinitionMap = Maps.newLinkedHashMap(); | |||
// 配置可以被匿名访问的地址 | |||
filterChainDefinitionMap.put("/static/**", "anon"); | |||
filterChainDefinitionMap.put("/ajaxLogin", "anon"); | |||
// 配置 logout,登出部分逻辑由 shiro 为我们实现 | |||
filterChainDefinitionMap.put("/logout", "logout"); | |||
// 配置自定义权限 | |||
filterChainDefinitionMap.put("/add", "perms[权限添加]"); | |||
filterChainDefinitionMap.put("/**", "authc"); | |||
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); | |||
log.info("ShiroFilterFactoryBean 注入成功"); | |||
return shiroFilterFactoryBean; | |||
} | |||
@Bean | |||
public SecurityManager securityManager(MyShiroRealm myShiroRealm) { | |||
DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager(); | |||
// 设置 Realm | |||
// 在 shiro 中,最终是通过 Realm 来获取应用程序中的用户、角色及权限信息的。 | |||
// 通常情况下,在 Realm 中会直接从我们的数据源中获取 shiro 需要的验证信息。 | |||
// 可以说,Realm 是专用于安全框架的 DAO。 | |||
defaultSecurityManager.setRealm(myShiroRealm); | |||
return defaultSecurityManager; | |||
} | |||
@Bean | |||
public MyShiroRealm myShiroRealm() { | |||
return new MyShiroRealm(); | |||
} | |||
} |
@@ -1,124 +0,0 @@ | |||
package com.xkcoding.springbootdemorabcshiromybatis.constrant.factory; | |||
import java.util.List; | |||
/** | |||
* <p> | |||
* 常量生产工厂的接口 | |||
* </p> | |||
* | |||
* @package: com.xkcoding.springbootdemorabcshiromybatis.constrant.factory | |||
* @description: 常量生产工厂的接口 | |||
* @author: yangkai.shen | |||
* @date: Created in 2017/12/6 下午3:51 | |||
* @copyright: Copyright (c) 2017 | |||
* @version: 0.0.1 | |||
* @modified: yangkai.shen | |||
*/ | |||
public interface Constant { | |||
String ADMIN_NAME = "超级管理员"; | |||
String COMMA = ","; | |||
String SEMI = ";"; | |||
String SEPARATE = "|"; | |||
String PATH_SEPARATE = "/"; | |||
String PERCENT = "%"; | |||
/** | |||
* 根据用户id获取用户名称 | |||
* | |||
* @param userId 用户id | |||
* @return 用户名称 | |||
*/ | |||
String getRealNameById(Integer userId); | |||
/** | |||
* 根据用户id获取用户账号 | |||
* | |||
* @param userId 用户id | |||
* @return 用户账号 | |||
*/ | |||
String getUsernameById(Integer userId); | |||
/** | |||
* 根据角色id获取角色名称 | |||
* | |||
* @param roleId 角色id | |||
* @return 角色名称 | |||
*/ | |||
String getRoleName(Integer roleId); | |||
/** | |||
* 根据角色id列表获取角色名称列表 | |||
* | |||
* @param roleIds 角色id列表 | |||
* @return 角色名称列表 | |||
*/ | |||
List<String> getRoleNames(List<Integer> roleIds); | |||
/** | |||
* 根据用户id获取角色id列表 | |||
* | |||
* @param userId 用户id | |||
* @return 角色id列表 | |||
*/ | |||
List<Integer> getRoleIds(Integer userId); | |||
/** | |||
* 根据部门id获取部门名称 | |||
* | |||
* @param deptId 部门id | |||
* @return 部门名称 | |||
*/ | |||
String getDeptName(Integer deptId); | |||
/** | |||
* 根据权限id获取权限名称 | |||
* | |||
* @param aclId 权限id | |||
* @return 权限名称 | |||
*/ | |||
String getAclName(Integer aclId); | |||
/** | |||
* 根据权限编号获取权限名称 | |||
* | |||
* @param code 权限编号 | |||
* @return 权限名称 | |||
*/ | |||
String getAclNameByCode(String code); | |||
/** | |||
* 根据状态码获取用户登录状态 | |||
* | |||
* @param code 状态码 | |||
* @return 状态 | |||
*/ | |||
String getUserStatusName(Integer code); | |||
/** | |||
* 根据状态码获取权限状态 | |||
* | |||
* @param code 状态码 | |||
* @return 状态 | |||
*/ | |||
String getAclStatusName(Integer code); | |||
/** | |||
* 获取子部门id | |||
* | |||
* @param deptId 当前部门id | |||
* @return 所有子部门id | |||
*/ | |||
List<Integer> getSubDeptId(Integer deptId); | |||
/** | |||
* 获取所有父部门id | |||
* | |||
* @param deptId 当前部门id | |||
* @return 所有父部门id | |||
*/ | |||
List<Integer> getParentDeptIds(Integer deptId); | |||
} |
@@ -1,157 +0,0 @@ | |||
package com.xkcoding.springbootdemorabcshiromybatis.constrant.factory; | |||
import com.google.common.collect.Lists; | |||
import com.xiaoleilu.hutool.util.StrUtil; | |||
import com.xkcoding.springbootdemorabcshiromybatis.dao.*; | |||
import com.xkcoding.springbootdemorabcshiromybatis.enums.AclStatusEnum; | |||
import com.xkcoding.springbootdemorabcshiromybatis.enums.UserStatusEnum; | |||
import com.xkcoding.springbootdemorabcshiromybatis.model.*; | |||
import com.xkcoding.springbootdemorabcshiromybatis.util.SpringContextHolder; | |||
import org.springframework.context.annotation.DependsOn; | |||
import org.springframework.stereotype.Component; | |||
import tk.mybatis.mapper.entity.Example; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import java.util.stream.Collectors; | |||
/** | |||
* <p> | |||
* 常量的生产工厂 | |||
* </p> | |||
* | |||
* @package: com.xkcoding.springbootdemorabcshiromybatis.constrant.factory | |||
* @description: 常量的生产工厂 | |||
* @author: yangkai.shen | |||
* @date: Created in 2017/12/6 下午4:01 | |||
* @copyright: Copyright (c) 2017 | |||
* @version: 0.0.1 | |||
* @modified: yangkai.shen | |||
*/ | |||
@Component | |||
@DependsOn("springContextHolder") | |||
public class ConstantFactory implements Constant { | |||
private MybatisShiroAclMapper aclMapper = SpringContextHolder.getBean(MybatisShiroAclMapper.class); | |||
private MybatisShiroDeptMapper deptMapper = SpringContextHolder.getBean(MybatisShiroDeptMapper.class); | |||
private MybatisShiroUserMapper userMapper = SpringContextHolder.getBean(MybatisShiroUserMapper.class); | |||
private MybatisShiroRoleMapper roleMapper = SpringContextHolder.getBean(MybatisShiroRoleMapper.class); | |||
private MybatisShiroRoleUserMapper roleUserMapper = SpringContextHolder.getBean(MybatisShiroRoleUserMapper.class); | |||
public static Constant me() { | |||
return SpringContextHolder.getBean("constantFactory"); | |||
} | |||
@Override | |||
public String getRealNameById(Integer userId) { | |||
MybatisShiroUser user = userMapper.selectByPrimaryKey(userId); | |||
if (user != null) { | |||
return user.getRealname(); | |||
} | |||
return null; | |||
} | |||
@Override | |||
public String getUsernameById(Integer userId) { | |||
MybatisShiroUser user = userMapper.selectByPrimaryKey(userId); | |||
if (user != null && StrUtil.isNotEmpty(user.getUsername())) { | |||
return user.getUsername(); | |||
} | |||
return null; | |||
} | |||
@Override | |||
public String getRoleName(Integer roleId) { | |||
MybatisShiroRole role = roleMapper.selectByPrimaryKey(roleId); | |||
if (role != null && StrUtil.isNotEmpty(role.getName())) { | |||
return role.getName(); | |||
} | |||
return null; | |||
} | |||
@Override | |||
public List<String> getRoleNames(List<Integer> roleIds) { | |||
return roleIds.stream().map(id -> getRoleName(id)).collect(Collectors.toList()); | |||
} | |||
@Override | |||
public List<Integer> getRoleIds(Integer userId) { | |||
MybatisShiroRoleUser param = new MybatisShiroRoleUser(); | |||
param.setUserId(userId); | |||
List<MybatisShiroRoleUser> roleUser = roleUserMapper.select(param); | |||
return roleUser.stream().map(v -> v.getRoleId()).collect(Collectors.toList()); | |||
} | |||
@Override | |||
public String getDeptName(Integer deptId) { | |||
MybatisShiroDept dept = deptMapper.selectByPrimaryKey(deptId); | |||
if (dept != null && StrUtil.isNotEmpty(dept.getName())) { | |||
return dept.getName(); | |||
} | |||
return null; | |||
} | |||
@Override | |||
public String getAclName(Integer aclId) { | |||
MybatisShiroAcl acl = aclMapper.selectByPrimaryKey(aclId); | |||
if (acl != null && StrUtil.isNotEmpty(acl.getName())) { | |||
return acl.getName(); | |||
} | |||
return null; | |||
} | |||
@Override | |||
public String getAclNameByCode(String code) { | |||
if (StrUtil.isNotBlank(code)) { | |||
return null; | |||
} else { | |||
MybatisShiroAcl param = new MybatisShiroAcl(); | |||
param.setCode(code); | |||
MybatisShiroAcl acl = aclMapper.selectOne(param); | |||
if (acl != null && StrUtil.isNotEmpty(acl.getName())) { | |||
return acl.getName(); | |||
} | |||
return null; | |||
} | |||
} | |||
@Override | |||
public String getUserStatusName(Integer code) { | |||
return UserStatusEnum.valueOf(code); | |||
} | |||
@Override | |||
public String getAclStatusName(Integer code) { | |||
return AclStatusEnum.valueOf(code); | |||
} | |||
@Override | |||
public List<Integer> getSubDeptId(Integer deptId) { | |||
Example example = new Example(MybatisShiroDept.class); | |||
example.createCriteria().andLike("level", "%" + deptId + "%"); | |||
List<MybatisShiroDept> deptList = deptMapper.selectByExample(example); | |||
ArrayList<Integer> deptIds = Lists.newArrayList(); | |||
if (deptList != null || deptList.size() > 0) { | |||
for (MybatisShiroDept dept : deptList) { | |||
deptIds.add(dept.getId()); | |||
} | |||
} | |||
return deptIds; | |||
} | |||
@Override | |||
public List<Integer> getParentDeptIds(Integer deptId) { | |||
MybatisShiroDept dept = deptMapper.selectByPrimaryKey(deptId); | |||
String level = dept.getLevel(); | |||
ArrayList<Integer> parentIds = Lists.newArrayList(); | |||
for (String parentId : level.split(COMMA)) { | |||
parentIds.add(Integer.valueOf(parentId)); | |||
} | |||
return parentIds; | |||
} | |||
} |
@@ -0,0 +1,24 @@ | |||
package com.xkcoding.springbootdemorabcshiromybatis.domain; | |||
import lombok.Data; | |||
/** | |||
* <p> | |||
* 登录参数 | |||
* </p> | |||
* | |||
* @package: com.xkcoding.springbootdemorabcshiromybatis.domain | |||
* @description: 登录参数 | |||
* @author: yangkai.shen | |||
* @date: Created in 2017/12/7 下午3:45 | |||
* @copyright: Copyright (c) 2017 | |||
* @version: 0.0.1 | |||
* @modified: yangkai.shen | |||
*/ | |||
@Data | |||
public class LoginParam { | |||
private String username; | |||
private String password; | |||
private String kaptcha; | |||
private Boolean rememberMe; | |||
} |
@@ -1,48 +0,0 @@ | |||
package com.xkcoding.springbootdemorabcshiromybatis.enums; | |||
import lombok.Getter; | |||
/** | |||
* <p> | |||
* 权限状态的枚举类 | |||
* </p> | |||
* | |||
* @package: com.xkcoding.springbootdemorabcshiromybatis.enums | |||
* @description: 权限状态的枚举类 | |||
* @author: yangkai.shen | |||
* @date: Created in 2017/12/6 下午3:38 | |||
* @copyright: Copyright (c) 2017 | |||
* @version: 0.0.1 | |||
* @modified: yangkai.shen | |||
*/ | |||
@Getter | |||
public enum AclStatusEnum { | |||
DISABLE(0, "禁用"), ENABLE(1, "启用"); | |||
private Integer code; | |||
private String message; | |||
AclStatusEnum(Integer code, String message) { | |||
this.code = code; | |||
this.message = message; | |||
} | |||
/** | |||
* 根据状态码返回状态 | |||
* | |||
* @param code 状态码 | |||
* @return 状态 | |||
*/ | |||
public static String valueOf(Integer code) { | |||
if (code == null) { | |||
return null; | |||
} else { | |||
for (AclStatusEnum statusEnum : AclStatusEnum.values()) { | |||
if (statusEnum.getCode().equals(code)) { | |||
return statusEnum.getMessage(); | |||
} | |||
} | |||
return null; | |||
} | |||
} | |||
} |
@@ -1,48 +0,0 @@ | |||
package com.xkcoding.springbootdemorabcshiromybatis.enums; | |||
import lombok.Getter; | |||
/** | |||
* <p> | |||
* 用户状态的枚举类 | |||
* </p> | |||
* | |||
* @package: com.xkcoding.springbootdemorabcshiromybatis.enums | |||
* @description: 用户状态的枚举类 | |||
* @author: yangkai.shen | |||
* @date: Created in 2017/12/6 下午3:38 | |||
* @copyright: Copyright (c) 2017 | |||
* @version: 0.0.1 | |||
* @modified: yangkai.shen | |||
*/ | |||
@Getter | |||
public enum UserStatusEnum { | |||
DELETED(-1, "已删除"), DISABLE(0, "禁用"), ENABLE(1, "启用"); | |||
private Integer code; | |||
private String message; | |||
UserStatusEnum(Integer code, String message) { | |||
this.code = code; | |||
this.message = message; | |||
} | |||
/** | |||
* 根据状态码返回状态 | |||
* | |||
* @param code 状态码 | |||
* @return 状态 | |||
*/ | |||
public static String valueOf(Integer code) { | |||
if (code == null) { | |||
return null; | |||
} else { | |||
for (UserStatusEnum statusEnum : UserStatusEnum.values()) { | |||
if (statusEnum.getCode().equals(code)) { | |||
return statusEnum.getMessage(); | |||
} | |||
} | |||
return null; | |||
} | |||
} | |||
} |
@@ -1,96 +0,0 @@ | |||
package com.xkcoding.springbootdemorabcshiromybatis.shiro; | |||
import com.google.common.collect.Sets; | |||
import com.xiaoleilu.hutool.util.StrUtil; | |||
import com.xkcoding.springbootdemorabcshiromybatis.dao.MybatisShiroUserMapper; | |||
import com.xkcoding.springbootdemorabcshiromybatis.model.MybatisShiroUser; | |||
import com.xkcoding.springbootdemorabcshiromybatis.shiro.factory.Shiro; | |||
import com.xkcoding.springbootdemorabcshiromybatis.shiro.factory.ShiroFactroy; | |||
import lombok.extern.slf4j.Slf4j; | |||
import org.apache.shiro.authc.*; | |||
import org.apache.shiro.authc.credential.CredentialsMatcher; | |||
import org.apache.shiro.authc.credential.HashedCredentialsMatcher; | |||
import org.apache.shiro.authz.AuthorizationInfo; | |||
import org.apache.shiro.authz.SimpleAuthorizationInfo; | |||
import org.apache.shiro.realm.AuthorizingRealm; | |||
import org.apache.shiro.subject.PrincipalCollection; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import java.util.List; | |||
import java.util.Set; | |||
/** | |||
* shiro 身份校验 | |||
* | |||
* @package: com.xkcoding.springbootdemorabcshiromybatis.shiro | |||
* @description: shiro 身份校验 | |||
* @author: yangkai.shen | |||
* @date: Created in 2017/11/29 下午3:39 | |||
* @copyright: Copyright (c) 2017 | |||
* @version: 0.0.1 | |||
* @modified: yangkai.shen | |||
*/ | |||
@Slf4j | |||
public class MyShiroRealm extends AuthorizingRealm { | |||
@Autowired | |||
private MybatisShiroUserMapper mybatisShiroUserMapper; | |||
/** | |||
* 身份认证: Authentication 用来验证用户信息 | |||
* | |||
* @param authenticationToken | |||
* @return | |||
* @throws AuthenticationException | |||
*/ | |||
@Override | |||
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { | |||
log.info("【身份认证】:进入doGetAuthenticationInfo()"); | |||
Shiro shiroFactory = ShiroFactroy.me(); | |||
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; | |||
MybatisShiroUser user = shiroFactory.user(token.getUsername()); | |||
ShiroUser shiroUser = shiroFactory.shiroUser(user); | |||
SimpleAuthenticationInfo info = shiroFactory.info(shiroUser, user, super.getName()); | |||
return info; | |||
} | |||
/** | |||
* 授权验证 | |||
* | |||
* @param principalCollection | |||
* @return | |||
*/ | |||
@Override | |||
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { | |||
log.info("【授权验证】:进入doGetAuthorizationInfo()"); | |||
Shiro shiroFactory = ShiroFactroy.me(); | |||
ShiroUser shiroUser = (ShiroUser) principalCollection.getPrimaryPrincipal(); | |||
List<Integer> roleList = shiroUser.getRoleList(); | |||
Set<String> aclSet = Sets.newHashSet(); | |||
Set<String> roleNameSet = Sets.newHashSet(shiroUser.getRoleNames()); | |||
for (Integer roleId : roleList) { | |||
List<String> acls = shiroFactory.findAclsByRoleId(roleId); | |||
for (String acl : acls) { | |||
if (StrUtil.isNotEmpty(acl)) { | |||
aclSet.add(acl); | |||
} | |||
} | |||
} | |||
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); | |||
info.addStringPermissions(aclSet); | |||
info.addRoles(roleNameSet); | |||
return info; | |||
} | |||
@Override | |||
public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) { | |||
HashedCredentialsMatcher md5CredentialsMatcher = new HashedCredentialsMatcher(); | |||
md5CredentialsMatcher.setHashAlgorithmName(ShiroUtil.HASH_ALGORITHM_NAME); | |||
md5CredentialsMatcher.setHashIterations(ShiroUtil.HASH_ITERATIONS); | |||
super.setCredentialsMatcher(md5CredentialsMatcher); | |||
} | |||
} |
@@ -1,34 +0,0 @@ | |||
package com.xkcoding.springbootdemorabcshiromybatis.shiro; | |||
import lombok.Data; | |||
import java.io.Serializable; | |||
import java.util.List; | |||
/** | |||
* <p> | |||
* 自定义Authentication对象,使得Subject除了携带用户的登录名外还可以携带更多信息 | |||
* </p> | |||
* | |||
* @package: com.xkcoding.springbootdemorabcshiromybatis.shiro | |||
* @description: 自定义Authentication对象,使得Subject除了携带用户的登录名外还可以携带更多信息 | |||
* @author: yangkai.shen | |||
* @date: Created in 2017/12/6 下午3:26 | |||
* @copyright: Copyright (c) 2017 | |||
* @version: 0.0.1 | |||
* @modified: yangkai.shen | |||
*/ | |||
@Data | |||
public class ShiroUser implements Serializable { | |||
private static final long serialVersionUID = 1L; | |||
private Integer id; | |||
private String username; | |||
private String realname; | |||
private Integer deptId; | |||
private String deptName; | |||
private List<Integer> roleList; | |||
private List<String> roleNames; | |||
} |
@@ -1,270 +0,0 @@ | |||
package com.xkcoding.springbootdemorabcshiromybatis.shiro; | |||
import com.xkcoding.springbootdemorabcshiromybatis.constrant.factory.Constant; | |||
import com.xkcoding.springbootdemorabcshiromybatis.constrant.factory.ConstantFactory; | |||
import com.xkcoding.util.T; | |||
import org.apache.shiro.SecurityUtils; | |||
import org.apache.shiro.crypto.hash.Md5Hash; | |||
import org.apache.shiro.crypto.hash.SimpleHash; | |||
import org.apache.shiro.session.Session; | |||
import org.apache.shiro.subject.Subject; | |||
import org.apache.shiro.util.ByteSource; | |||
import java.util.List; | |||
/** | |||
* <p> | |||
* shiro 工具类 | |||
* </p> | |||
* | |||
* @package: com.xkcoding.springbootdemorabcshiromybatis.shiro | |||
* @description: shiro 工具类 | |||
* @author: yangkai.shen | |||
* @date: Created in 2017/12/1 下午6:02 | |||
* @copyright: Copyright (c) 2017 | |||
* @version: 0.0.1 | |||
* @modified: yangkai.shen | |||
*/ | |||
public class ShiroUtil { | |||
/** | |||
* 加盐参数 | |||
*/ | |||
public final static String HASH_ALGORITHM_NAME = "MD5"; | |||
/** | |||
* 循环次数 | |||
*/ | |||
public final static int HASH_ITERATIONS = 1; | |||
/** | |||
* shiro密码加密工具类 | |||
* | |||
* @param credentials 密码 | |||
* @param saltSource 密码盐 | |||
* @return 加密后的字符串 | |||
*/ | |||
public static String md5(String credentials, String saltSource) { | |||
ByteSource salt = new Md5Hash(saltSource); | |||
return new SimpleHash(HASH_ALGORITHM_NAME, credentials, salt, HASH_ITERATIONS).toString(); | |||
} | |||
/** | |||
* 获取随机盐值 | |||
* | |||
* @return 获取随机盐值 | |||
*/ | |||
public static String getRandomSalt() { | |||
return T.UUID(); | |||
} | |||
/** | |||
* 获取当前 Subject | |||
* | |||
* @return Subject | |||
*/ | |||
public static Subject getSubject() { | |||
return SecurityUtils.getSubject(); | |||
} | |||
/** | |||
* 获取封装的 ShiroUser | |||
* | |||
* @return ShiroUser | |||
*/ | |||
public static ShiroUser getUser() { | |||
if (isGuest()) { | |||
return null; | |||
} else { | |||
return (ShiroUser) getSubject().getPrincipals().getPrimaryPrincipal(); | |||
} | |||
} | |||
/** | |||
* 从shiro获取session | |||
*/ | |||
public static Session getSession() { | |||
return getSubject().getSession(); | |||
} | |||
/** | |||
* 获取shiro指定的sessionKey | |||
*/ | |||
@SuppressWarnings("unchecked") | |||
public static <T> T getSessionAttr(String key) { | |||
Session session = getSession(); | |||
return session != null ? (T) session.getAttribute(key) : null; | |||
} | |||
/** | |||
* 设置shiro指定的sessionKey | |||
*/ | |||
public static void setSessionAttr(String key, Object value) { | |||
Session session = getSession(); | |||
session.setAttribute(key, value); | |||
} | |||
/** | |||
* 移除shiro指定的sessionKey | |||
*/ | |||
public static void removeSessionAttr(String key) { | |||
Session session = getSession(); | |||
if (session != null) { | |||
session.removeAttribute(key); | |||
} | |||
} | |||
/** | |||
* 验证当前用户是否属于该角色?,使用时与lacksRole 搭配使用 | |||
* | |||
* @param roleName 角色名 | |||
* @return 属于该角色:true,否则false | |||
*/ | |||
public static boolean hasRole(String roleName) { | |||
return getSubject() != null && roleName != null && roleName.length() > 0 && getSubject().hasRole(roleName); | |||
} | |||
/** | |||
* 与hasRole标签逻辑相反,当用户不属于该角色时验证通过。 | |||
* | |||
* @param roleName 角色名 | |||
* @return 不属于该角色:true,否则false | |||
*/ | |||
public static boolean lacksRole(String roleName) { | |||
return !hasRole(roleName); | |||
} | |||
/** | |||
* 验证当前用户是否属于以下任意一个角色。 | |||
* | |||
* @param roleNames 角色列表 | |||
* @return 属于:true,否则false | |||
*/ | |||
public static boolean hasAnyRoles(String roleNames) { | |||
boolean hasAnyRole = false; | |||
Subject subject = getSubject(); | |||
if (subject != null && roleNames != null && roleNames.length() > 0) { | |||
for (String role : roleNames.split(Constant.COMMA)) { | |||
if (subject.hasRole(role.trim())) { | |||
hasAnyRole = true; | |||
break; | |||
} | |||
} | |||
} | |||
return hasAnyRole; | |||
} | |||
/** | |||
* 验证当前用户是否属于以下所有角色。 | |||
* | |||
* @param roleNames 角色列表 | |||
* @return 属于:true,否则false | |||
*/ | |||
public static boolean hasAllRoles(String roleNames) { | |||
boolean hasAllRole = true; | |||
Subject subject = getSubject(); | |||
if (subject != null && roleNames != null && roleNames.length() > 0) { | |||
for (String role : roleNames.split(Constant.COMMA)) { | |||
if (!subject.hasRole(role.trim())) { | |||
hasAllRole = false; | |||
break; | |||
} | |||
} | |||
} | |||
return hasAllRole; | |||
} | |||
/** | |||
* 验证当前用户是否拥有指定权限,使用时与lacksPermission 搭配使用 | |||
* | |||
* @param permission 权限名 | |||
* @return 拥有权限:true,否则false | |||
*/ | |||
public static boolean hasPermission(String permission) { | |||
return getSubject() != null && permission != null && permission.length() > 0 && getSubject().isPermitted(permission); | |||
} | |||
/** | |||
* 与hasPermission标签逻辑相反,当前用户没有制定权限时,验证通过。 | |||
* | |||
* @param permission 权限名 | |||
* @return 拥有权限:true,否则false | |||
*/ | |||
public static boolean lacksPermission(String permission) { | |||
return !hasPermission(permission); | |||
} | |||
/** | |||
* 已认证通过的用户。不包含已记住的用户,这是与user标签的区别所在。与notAuthenticated搭配使用 | |||
* | |||
* @return 通过身份验证:true,否则false | |||
*/ | |||
public static boolean isAuthenticated() { | |||
return getSubject() != null && getSubject().isAuthenticated(); | |||
} | |||
/** | |||
* 未认证通过用户,与authenticated标签相对应。与guest标签的区别是,该标签包含已记住用户。。 | |||
* | |||
* @return 没有通过身份验证:true,否则false | |||
*/ | |||
public static boolean notAuthenticated() { | |||
return !isAuthenticated(); | |||
} | |||
/** | |||
* 认证通过或已记住的用户。与guset搭配使用。 | |||
* | |||
* @return 用户:true,否则 false | |||
*/ | |||
public static boolean isUser() { | |||
return getSubject() != null && getSubject().getPrincipal() != null; | |||
} | |||
/** | |||
* 验证当前用户是否为“访客”,即未认证(包含未记住)的用户。用user搭配使用 | |||
* | |||
* @return 访客:true,否则false | |||
*/ | |||
public static boolean isGuest() { | |||
return !isUser(); | |||
} | |||
/** | |||
* 输出当前用户信息,通常为登录帐号信息。 | |||
* | |||
* @return 当前用户信息 | |||
*/ | |||
public static String principal() { | |||
if (getSubject() != null) { | |||
Object principal = getSubject().getPrincipal(); | |||
return principal.toString(); | |||
} | |||
return ""; | |||
} | |||
/** | |||
* 获取当前用户的部门数据范围的集合 | |||
*/ | |||
public static List<Integer> getDeptDataScope() { | |||
Integer deptId = getUser().getDeptId(); | |||
List<Integer> subDeptIds = ConstantFactory.me().getSubDeptId(deptId); | |||
subDeptIds.add(deptId); | |||
return subDeptIds; | |||
} | |||
/** | |||
* 判断当前用户是否是超级管理员 | |||
*/ | |||
public static boolean isAdmin() { | |||
List<String> roleNameList = getUser().getRoleNames(); | |||
for (String roleName : roleNameList) { | |||
if (roleName.equals(Constant.ADMIN_NAME)) { | |||
return true; | |||
} | |||
} | |||
return false; | |||
} | |||
} | |||
@@ -1,66 +0,0 @@ | |||
package com.xkcoding.springbootdemorabcshiromybatis.shiro.factory; | |||
import com.xkcoding.springbootdemorabcshiromybatis.model.MybatisShiroUser; | |||
import com.xkcoding.springbootdemorabcshiromybatis.shiro.ShiroUser; | |||
import org.apache.shiro.authc.SimpleAuthenticationInfo; | |||
import java.util.List; | |||
/** | |||
* <p> | |||
* 定义 shiro realm 所需数据的接口 | |||
* </p> | |||
* | |||
* @package: com.xkcoding.springbootdemorabcshiromybatis.shiro.factory | |||
* @description: 定义 shiro realm 所需数据的接口 | |||
* @author: yangkai.shen | |||
* @date: Created in 2017/12/6 下午3:24 | |||
* @copyright: Copyright (c) 2017 | |||
* @version: 0.0.1 | |||
* @modified: yangkai.shen | |||
*/ | |||
public interface Shiro { | |||
/** | |||
* 根据用户名数据库存储的用户信息 | |||
* | |||
* @param username 用户名 | |||
* @return 数据库存储的用户信息 | |||
*/ | |||
MybatisShiroUser user(String username); | |||
/** | |||
* 根据系统用户获取 shiro 的用户 | |||
* | |||
* @param user 数据库保存的用户 | |||
* @return 自定义的用户对象 | |||
*/ | |||
ShiroUser shiroUser(MybatisShiroUser user); | |||
/** | |||
* 根据角色id获取权限列表 | |||
* | |||
* @param roleId 角色id | |||
* @return 权限列表 | |||
*/ | |||
List<String> findAclsByRoleId(Integer roleId); | |||
/** | |||
* 根据角色id获取角色名称 | |||
* | |||
* @param roleId 角色id | |||
* @return 角色名称 | |||
*/ | |||
String findRoleNameByRoleId(Integer roleId); | |||
/** | |||
* 获取 shiro 的认证信息 | |||
* | |||
* @param shiroUser 自定义返回的 user 对象 | |||
* @param user 数据库保存的 user 对象 | |||
* @param realmName 真实姓名 | |||
* @return shiro的认证信息 | |||
*/ | |||
SimpleAuthenticationInfo info(ShiroUser shiroUser, MybatisShiroUser user, String realmName); | |||
} |
@@ -1,95 +0,0 @@ | |||
package com.xkcoding.springbootdemorabcshiromybatis.shiro.factory; | |||
import com.xkcoding.springbootdemorabcshiromybatis.constrant.factory.ConstantFactory; | |||
import com.xkcoding.springbootdemorabcshiromybatis.dao.MybatisShiroAclMapper; | |||
import com.xkcoding.springbootdemorabcshiromybatis.dao.MybatisShiroUserMapper; | |||
import com.xkcoding.springbootdemorabcshiromybatis.enums.UserStatusEnum; | |||
import com.xkcoding.springbootdemorabcshiromybatis.model.MybatisShiroUser; | |||
import com.xkcoding.springbootdemorabcshiromybatis.shiro.ShiroUser; | |||
import com.xkcoding.springbootdemorabcshiromybatis.util.SpringContextHolder; | |||
import lombok.extern.slf4j.Slf4j; | |||
import org.apache.shiro.authc.CredentialsException; | |||
import org.apache.shiro.authc.DisabledAccountException; | |||
import org.apache.shiro.authc.SimpleAuthenticationInfo; | |||
import org.apache.shiro.crypto.hash.Md5Hash; | |||
import org.apache.shiro.util.ByteSource; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import org.springframework.context.annotation.DependsOn; | |||
import org.springframework.stereotype.Service; | |||
import java.util.List; | |||
@Service | |||
@DependsOn("springContextHolder") | |||
@Slf4j | |||
public class ShiroFactroy implements Shiro { | |||
@Autowired | |||
private MybatisShiroUserMapper userMapper; | |||
@Autowired | |||
private MybatisShiroAclMapper aclMapper; | |||
public static Shiro me() { | |||
return SpringContextHolder.getBean(Shiro.class); | |||
} | |||
@Override | |||
public MybatisShiroUser user(String username) { | |||
MybatisShiroUser user = userMapper.findByUsername(username); | |||
// 账号不存在 | |||
if (null == user) { | |||
log.error("【登录失败】:账号不存在"); | |||
throw new CredentialsException("账号不存在"); | |||
} | |||
// 账号被冻结 | |||
if (user.getStatus().equals(UserStatusEnum.DISABLE.getCode())) { | |||
log.error("【登录失败】:用户已被冻结"); | |||
throw new DisabledAccountException("登录失败,用户已被冻结"); | |||
} else if (user.getStatus().equals(UserStatusEnum.DELETED.getCode())) { | |||
log.error("【登录失败】:没有该用户"); | |||
throw new DisabledAccountException("登录失败,没有该用户"); | |||
} | |||
return user; | |||
} | |||
@Override | |||
public ShiroUser shiroUser(MybatisShiroUser user) { | |||
ShiroUser shiroUser = new ShiroUser(); | |||
shiroUser.setId(user.getId()); | |||
shiroUser.setUsername(user.getUsername()); | |||
shiroUser.setDeptId(user.getDeptId()); | |||
shiroUser.setDeptName(ConstantFactory.me().getDeptName(user.getDeptId())); | |||
shiroUser.setRealname(user.getRealname()); | |||
List<Integer> roleList = ConstantFactory.me().getRoleIds(user.getId()); | |||
List<String> roleNameList = ConstantFactory.me().getRoleNames(roleList); | |||
shiroUser.setRoleList(roleList); | |||
shiroUser.setRoleNames(roleNameList); | |||
return shiroUser; | |||
} | |||
@Override | |||
public List<String> findAclsByRoleId(Integer roleId) { | |||
return aclMapper.getResUrlsByRoleId(roleId); | |||
} | |||
@Override | |||
public String findRoleNameByRoleId(Integer roleId) { | |||
return ConstantFactory.me().getRoleName(roleId); | |||
} | |||
@Override | |||
public SimpleAuthenticationInfo info(ShiroUser shiroUser, MybatisShiroUser user, String realmName) { | |||
String credentials = user.getPassword(); | |||
// 密码加盐处理 | |||
String source = user.getSalt(); | |||
ByteSource credentialsSalt = new Md5Hash(source); | |||
return new SimpleAuthenticationInfo(shiroUser, credentials, credentialsSalt, realmName); | |||
} | |||
} |
@@ -0,0 +1,147 @@ | |||
package com.xkcoding.springbootdemorabcshiromybatis.util; | |||
import lombok.extern.slf4j.Slf4j; | |||
import org.apache.commons.lang3.StringUtils; | |||
import javax.servlet.http.HttpServletRequest; | |||
import java.net.InetAddress; | |||
import java.net.UnknownHostException; | |||
import java.util.regex.Matcher; | |||
import java.util.regex.Pattern; | |||
/** | |||
* <p> | |||
* 获取 IP 工具类 | |||
* </p> | |||
* | |||
* @package: com.xkcoding.springbootdemorabcshiromybatis.util | |||
* @description: 获取 IP 工具类 | |||
* @author: yangkai.shen | |||
* @date: Created in 2017/12/7 上午11:13 | |||
* @copyright: Copyright (c) 2017 | |||
* @version: 0.0.1 | |||
* @modified: yangkai.shen | |||
*/ | |||
@Slf4j | |||
public class IpUtil { | |||
public final static String ERROR_IP = "127.0.0.1"; | |||
public final static Pattern pattern = Pattern. | |||
compile("(2[5][0-5]|2[0-4]\\d|1\\d{2}|\\d{1,2})\\.(25[0-5]|2[0-4]\\d|1\\d{2}|\\d{1,2})\\.(25[0-5]|2[0-4]\\d|1\\d{2}|\\d{1,2})\\.(25[0-5]|2[0-4]\\d|1\\d{2}|\\d{1,2})"); | |||
/** | |||
* 取外网IP | |||
* | |||
* @param request | |||
* @return | |||
*/ | |||
public static String getRemoteIp(HttpServletRequest request) { | |||
String ip = request.getHeader("x-real-ip"); | |||
if (ip == null) { | |||
ip = request.getRemoteAddr(); | |||
} | |||
//过滤反向代理的ip | |||
String[] stemps = ip.split(","); | |||
if (stemps != null && stemps.length >= 1) { | |||
//得到第一个IP,即客户端真实IP | |||
ip = stemps[0]; | |||
} | |||
ip = ip.trim(); | |||
if (ip.length() > 23) { | |||
ip = ip.substring(0, 23); | |||
} | |||
return ip; | |||
} | |||
/** | |||
* 获取用户的真实ip | |||
* | |||
* @param request | |||
* @return | |||
*/ | |||
public static String getUserIP(HttpServletRequest request) { | |||
// 优先取X-Real-IP | |||
String ip = request.getHeader("X-Real-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.getRemoteAddr(); | |||
if ("0:0:0:0:0:0:0:1".equals(ip)) { | |||
ip = ERROR_IP; | |||
} | |||
} | |||
if ("unknown".equalsIgnoreCase(ip)) { | |||
ip = ERROR_IP; | |||
return ip; | |||
} | |||
int pos = ip.indexOf(','); | |||
if (pos >= 0) { | |||
ip = ip.substring(0, pos); | |||
} | |||
return ip; | |||
} | |||
public static String getLastIpSegment(HttpServletRequest request) { | |||
String ip = getUserIP(request); | |||
if (ip != null) { | |||
ip = ip.substring(ip.lastIndexOf('.') + 1); | |||
} else { | |||
ip = "0"; | |||
} | |||
return ip; | |||
} | |||
public static boolean isValidIP(HttpServletRequest request) { | |||
String ip = getUserIP(request); | |||
return isValidIP(ip); | |||
} | |||
/** | |||
* 判断我们获取的ip是否是一个符合规则ip | |||
* | |||
* @param ip | |||
* @return | |||
*/ | |||
public static boolean isValidIP(String ip) { | |||
if (StringUtils.isEmpty(ip)) { | |||
log.debug("ip is null. valid result is false"); | |||
return false; | |||
} | |||
Matcher matcher = pattern.matcher(ip); | |||
boolean isValid = matcher.matches(); | |||
log.debug("valid ip:" + ip + " result is: " + isValid); | |||
return isValid; | |||
} | |||
public static String getLastServerIpSegment() { | |||
String ip = getServerIP(); | |||
if (ip != null) { | |||
ip = ip.substring(ip.lastIndexOf('.') + 1); | |||
} else { | |||
ip = "0"; | |||
} | |||
return ip; | |||
} | |||
public static String getServerIP() { | |||
InetAddress inet; | |||
try { | |||
inet = InetAddress.getLocalHost(); | |||
String hostAddress = inet.getHostAddress(); | |||
return hostAddress; | |||
} catch (UnknownHostException e) { | |||
e.printStackTrace(); | |||
} | |||
return "127.0.0.1"; | |||
} | |||
} |
@@ -0,0 +1,62 @@ | |||
package com.xkcoding.springbootdemorabcshiromybatis.util; | |||
import com.fasterxml.jackson.core.type.TypeReference; | |||
import com.fasterxml.jackson.databind.ObjectMapper; | |||
import lombok.extern.slf4j.Slf4j; | |||
import java.io.IOException; | |||
/** | |||
* Json 转化工具类 | |||
* | |||
* @package: com.xkcoding.springbootdemoaoplog.util | |||
* @description:Json 转化工具类 | |||
* @author: yangkai.shen | |||
* @date: Created in 2017/11/24 上午9:36 | |||
* @copyright: Copyright (c) 2017 | |||
* @version: 0.0.1 | |||
* @modified: yangkai.shen | |||
*/ | |||
@Slf4j | |||
public class JsonMapper { | |||
private static ObjectMapper objectMapper = new ObjectMapper(); | |||
/** | |||
* 对象转 json 字符串 | |||
* | |||
* @param src 元对象 | |||
* @param <T> 类型 | |||
* @return json 字符串 | |||
*/ | |||
public static <T> String obj2Str(T src) { | |||
if (src == null) { | |||
return null; | |||
} | |||
try { | |||
return src instanceof String ? (String) src : objectMapper.writeValueAsString(src); | |||
} catch (IOException e) { | |||
log.error("【JSON 转换:对象 --> 字符串】,异常堆栈:{}", e); | |||
return null; | |||
} | |||
} | |||
/** | |||
* json 字符串转化为对象 | |||
* | |||
* @param src 源 json 字符串 | |||
* @param typeReference 转化后的类型 | |||
* @param <T> 类型 | |||
* @return 返回转化后的对象 | |||
*/ | |||
public static <T> T str2Obj(String src, TypeReference<T> typeReference) { | |||
if (src == null || typeReference == null) { | |||
return null; | |||
} | |||
try { | |||
return (T) (typeReference.getType().equals(String.class) ? src : objectMapper.readValue(src, typeReference)); | |||
} catch (Exception e) { | |||
log.error("【JSON 转换:字符串 --> 对象】,异常堆栈:{}", e); | |||
return null; | |||
} | |||
} | |||
} |
@@ -2,6 +2,11 @@ server: | |||
port: 8080 | |||
context-path: /demo | |||
spring: | |||
thymeleaf: | |||
mode: HTML5 | |||
encoding: UTF-8 | |||
content-type: text/html | |||
cache: false | |||
jackson: | |||
time-zone: GMT+8 | |||
date-format: yyyy-MM-dd HH:mm:ss | |||
@@ -20,6 +20,8 @@ INSERT INTO `mybatis_shiro_dept` VALUES (3, '运营部', 1, '0,1', 1, '运营部 | |||
INSERT INTO `mybatis_shiro_dept` VALUES (4, '战略部', 1, '0,1', 2, '战略部', '系统', '2017-12-01 00:00:00', '127.0.0.1'); | |||
INSERT INTO `mybatis_shiro_dept` VALUES (5, '软件部', 2, '0,1,2', 0, '软件部', '系统', '2017-12-01 00:00:00', '127.0.0.1'); | |||
INSERT INTO `mybatis_shiro_dept` VALUES (6, '硬件部', 2, '0,1,2', 1, '硬件部', '系统', '2017-12-01 00:00:00', '127.0.0.1'); | |||
INSERT INTO `mybatis_shiro_dept` VALUES (7, '采购部', 6, '0,1,2,6', 0, '采购部', '系统', '2017-12-01 00:00:00', '127.0.0.1'); | |||
INSERT INTO `mybatis_shiro_dept` VALUES (8, '维修部', 6, '0,1,2,6', 1, '维修部', '系统', '2017-12-01 00:00:00', '127.0.0.1'); | |||
-- 用户表 -- | |||
DROP TABLE IF EXISTS `mybatis_shiro_user`; | |||
@@ -0,0 +1,31 @@ | |||
<!doctype html> | |||
<html lang="en"> | |||
<head> | |||
<meta charset="UTF-8"/> | |||
<meta name="viewport" | |||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"/> | |||
<meta http-equiv="X-UA-Compatible" content="ie=edge"/> | |||
<title>Document</title> | |||
<script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script> | |||
<script src="https://cdn.bootcss.com/axios/0.17.1/axios.min.js"></script> | |||
</head> | |||
<body> | |||
用户名:<input type="text" id="ipt_username" name="username"/> | |||
密码:<input type="password" id="ipt_password" name="password"/> | |||
<button id="btn_login"> 登录</button> | |||
<script> | |||
$(function () { | |||
$("#btn_login").click(function () { | |||
var username = $("#ipt_username").val(); | |||
var password = $("#ipt_password").val(); | |||
axios.post('/demo/ajaxLogin', {username, password}).then((res) => { | |||
console.log(res); | |||
}).catch((err) => { | |||
console.log(err); | |||
}) | |||
}); | |||
}); | |||
</script> | |||
</body> | |||
</html> |