SpringBoot集成shiro
数据库设计
- sh_user:用户表,一个用户可以有多个角色
- sh_role: 角色表,一个角色可以有多个资源
- sh_resource:资源表
- sh_user_role:用户角色中间表
- sh_role_resource:角色资源中间表
首先自定义realm抽象类并实现
java
package com.itheima.shiro.core;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import javax.annotation.PostConstruct;
/**
* @Description:自定义realm的抽象类
*/
public abstract class ShiroDbRealm extends AuthorizingRealm {
/**
* @Description 认证方法
* @param token token对象
* @return 认证信息
*/
@Override
protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException ;
/**
* @Description 授权方法
* @param principals 令牌对象
* @return 授权信息
*/
@Override
protected abstract AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals);
/**
* @Description 自定义密码比较器
* @param
* @return
*/
@PostConstruct
public abstract void initCredentialsMatcher();
}
实现这个抽象类
java
package com.itheima.shiro.core.impl;
import com.itheima.shiro.constant.SuperConstant;
import com.itheima.shiro.core.ShiroDbRealm;
import com.itheima.shiro.core.base.ShiroUser;
import com.itheima.shiro.core.base.SimpleToken;
import com.itheima.shiro.core.bridge.UserBridgeService;
import com.itheima.shiro.pojo.User;
import com.itheima.shiro.utils.BeanConv;
import com.itheima.shiro.utils.EmptyUtil;
import org.apache.shiro.authc.*;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
/**
* @Description:自定义realm的抽象类实现
*/
public class ShiroDbRealmImpl extends ShiroDbRealm {
@Autowired
UserBridgeService userBridgeService;
/**
* @Description 认证方法
* @param token token对象
* @return 认证信息
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//token令牌信息,这里的SimpleToken继承了UsernamePasswordToken
SimpleToken simpleToken = (SimpleToken) token;
//根据登录名查询user对象
User user = userBridgeService.findUserByLoginName(simpleToken.getUsername());
if (EmptyUtil.isNullOrEmpty(user)){
throw new UnknownAccountException("账号不存在!");
}
//构建认证令牌对象(将User对象转换成ShiroUser对象,ShiroUser类比Users实体类多了资源列表等属性)
ShiroUser shiroUser = BeanConv.toBean(user, ShiroUser.class);
// 根据用户id获取资源列表,并设置到shiroUser对象
shiroUser.setResourceIds(userBridgeService.findResourcesIds(shiroUser.getId()));
String slat = shiroUser.getSalt();
String password = shiroUser.getPassWord();
//构建认证信息对象:1、令牌对象 2、密文密码 3、加密因子 4、当前realm的名称
return new SimpleAuthenticationInfo(shiroUser, password, ByteSource.Util.bytes(slat), getName());
}
/**
* @Description 授权方法
* @param principals 令牌对象
* @return 授权信息
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 获得令牌对象
ShiroUser shiroUser = (ShiroUser) principals.getPrimaryPrincipal();
return userBridgeService.getAuthorizationInfo(shiroUser);
}
/**
* @Description 自定义密码比较器
* @param
* @return
*/
@Override
public void initCredentialsMatcher() {
//指定密码算法
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(SuperConstant.HASH_ALGORITHM);
//指定迭代次数
hashedCredentialsMatcher.setHashIterations(SuperConstant.HASH_INTERATIONS);
//生效密码比较器
setCredentialsMatcher(hashedCredentialsMatcher);
}
}
SimpleToken类(说明)
java
package com.itheima.shiro.core.base;
import org.apache.shiro.authc.UsernamePasswordToken;
/**
* @Description 自定义tooken
*/
public class SimpleToken extends UsernamePasswordToken {
/** serialVersionUID */
private static final long serialVersionUID = -4849823851197352099L;
private String tokenType;
private String quickPassword;
/**
* Constructor for SimpleToken
* @param tokenType
*/
public SimpleToken(String tokenType, String username,String password) {
super(username,password);
this.tokenType = tokenType;
}
public SimpleToken(String tokenType, String username,String password,String quickPassword) {
super(username,password);
this.tokenType = tokenType;
this.quickPassword = quickPassword;
}
public String getTokenType() {
return tokenType;
}
public void setTokenType(String tokenType) {
this.tokenType = tokenType;
}
public String getQuickPassword() {
return quickPassword;
}
public void setQuickPassword(String quickPassword) {
this.quickPassword = quickPassword;
}
}
userBridgeService接口(说明)
java
package com.itheima.shiro.core.bridge;
import com.itheima.shiro.core.base.ShiroUser;
import com.itheima.shiro.pojo.User;
import org.apache.shiro.authz.AuthorizationInfo;
import java.util.List;
/**
* @Description:用户信息桥接(后期会做缓存)
*/
public interface UserBridgeService {
/**
* @Description 查找用户信息
* @param loginName 用户名称
* @return user对象
*/
User findUserByLoginName(String loginName);
/**
* @Description 查询资源ids
* @param userId 用户id
* @return 资源id集合
*/
List<String> findResourcesIds(String userId);
/**
* @Description 鉴权方法
* @param shiroUser 令牌对象
* @return 鉴权信息
*/
AuthorizationInfo getAuthorizationInfo(ShiroUser shiroUser);
/**
* @Description 查询用户对应角色标识list
* @param userId 用户id
* @return 角色标识集合
*/
List<String> findRoleList(String userId);
/**
* @Description 查询用户对应资源标识list
* @param userId 用户id
* @return 资源标识集合
*/
List<String> findResourcesList(String userId);
}
ShiroUser类(说明)
java
package com.itheima.shiro.core.base;
import com.itheima.shiro.utils.ToString;
import lombok.Data;
import java.util.List;
/**
* @Description 自定义Authentication对象,使得Subject除了携带用户的登录名外还可以携带更多信息
*/
@Data
public class ShiroUser extends ToString {
/** serialVersionUID */
private static final long serialVersionUID = -5024855628064590607L;
/**
* 主键
*/
private String id;
/**
* 登录名称
*/
private String loginName;
/**
* 真实姓名
*/
private String realName;
/**
* 昵称
*/
private String nickName;
/**
* 密码
*/
private String passWord;
/**
* 加密因子
*/
private String salt;
/**
* 性别
*/
private Integer sex;
/**
* 邮箱
*/
private String zipcode;
/**
* 地址
*/
private String address;
/**
* 固定电话
*/
private String tel;
/**
* 电话
*/
private String mobil;
/**
* 邮箱
*/
private String email;
/**
* 职务
*/
private String duties;
/**
* 排序
*/
private Integer sortNo;
/**
* 是否有效
*/
private String enableFlag;
/**
* @Description 资源列表
*/
private List<String> resourceIds;
public ShiroUser() {
super();
}
public ShiroUser(String id, String loginName) {
super();
this.id = id;
this.loginName = loginName;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((email == null) ? 0 : email.hashCode());
result = prime * result + ((id == null) ? 0 : id.hashCode());
result = prime * result
+ ((loginName == null) ? 0 : loginName.hashCode());
result = prime * result + ((mobil == null) ? 0 : mobil.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
ShiroUser other = (ShiroUser) obj;
if (email == null) {
if (other.email != null)
return false;
} else if (!email.equals(other.email))
return false;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
if (loginName == null) {
if (other.loginName != null)
return false;
} else if (!loginName.equals(other.loginName))
return false;
if (mobil == null) {
if (other.mobil != null)
return false;
} else if (!mobil.equals(other.mobil))
return false;
return true;
}
}
SuperConstant类(说明)
java
package com.itheima.shiro.constant;
/**
*
* @Description 静态变量
*/
public class SuperConstant {
/**
* 常量是
*/
public static final String YES = "YES";
/**
* 常量否
*/
public static final String NO = "NO";
/**
* 匿名用户ID
*/
public static final String ANON_ID = "-1";
/**
* 树形根节点父Id
*/
public static final String ROOT_PARENT_ID = "-1";
/**
* 树形根节点Id
*/
public static final String ROOT_ID = "1";
/**
* 匿名用户登录名
*/
public static final String ANON_LOGIN_NAME = "ANONYMITY";
/**
* 匿名用户真实名
*/
public static final String ANON_REAL_NAME = "匿名";
/**
* hash算法
*/
public static final String HASH_ALGORITHM = "SHA-1";
/**
* 计算次数
*/
public static final int HASH_INTERATIONS = 1024;
}
ShiroConfig配置
java
package com.itheima.shiro.config;
import com.itheima.shiro.core.ShiroDbRealm;
import com.itheima.shiro.core.filter.RolesOrAuthorizationFilter;
import com.itheima.shiro.core.impl.ShiroDbRealmImpl;
import com.itheima.shiro.properties.PropertiesUtil;
import lombok.extern.log4j.Log4j2;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
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.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* @Description:权限管理配置类
*/
@Configuration
// 不配置扫描也可以
@ComponentScan(basePackages = "com.itheima.shiro.core")
@Log4j2
public class ShiroConfig {
//创建cookie对象
@Bean(name = "simpleCookie")
public SimpleCookie simpleCookie(){
SimpleCookie simpleCookie = new SimpleCookie();
simpleCookie.setName("ShiroSession");
return simpleCookie;
}
//创建权限管理器
@Bean("securityManager")
public DefaultWebSecurityManager defaultWebSecurityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//管理realm
securityManager.setRealm(shiroDbRealm());
//管理会话
securityManager.setSessionManager(sessionManager());
return securityManager;
}
//自定义realm
@Bean("shiroDbRealm")
public ShiroDbRealm shiroDbRealm(){
return new ShiroDbRealmImpl();
}
//会话管理器
@Bean("sessionManager")
public DefaultWebSessionManager sessionManager(){
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
//关闭会话更新
sessionManager.setSessionValidationSchedulerEnabled(false);
//生效cookie
sessionManager.setSessionIdCookieEnabled(true);
//指定cookie的生成策略
sessionManager.setSessionIdCookie(simpleCookie());
//指定全局会话超时时间
sessionManager.setGlobalSessionTimeout(3600000);
return sessionManager;
}
//创建生命周期的管理
@Bean("lifecycleBeanPostProcessor")
public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
return new LifecycleBeanPostProcessor();
}
//aop增强(使用注解鉴权方式)
/**
* @Description AOP式方法级权限检查
*/
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
return defaultAdvisorAutoProxyCreator;
}
/**
* @Description 配合DefaultAdvisorAutoProxyCreator事项开启注解权限校验
*/
@Bean
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
aasa.setSecurityManager(defaultWebSecurityManager());
return aasa;
}
/**
* @Description 过滤器链定义
*/
private Map<String,String> filterChainDefinitionMap(){
// 读取authentication.properties配置文件中的过滤器链定义
// PropertiesUtil是读取配置文件的工具类
List<Object> list = PropertiesUtil.propertiesShiro.getKeyList();
// 必须创建一个有序的map
Map<String,String> map = new LinkedHashMap<>();
for (Object o : list) {
String key = o.toString();
String val = PropertiesUtil.getShiroValue(key);
map.put(key, val);
}
return map;
}
/**
* @Description 加载自定义过滤器
*/
private Map<String, Filter> filters(){
Map<String,Filter> map = new HashMap<>();
// 这里的key表示过滤器的名称
map.put("roles-or", new RolesOrAuthorizationFilter());
return map;
}
//shiro过滤器管理
@Bean("shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean(){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager());
//过滤器,指定自己的过滤器
shiroFilterFactoryBean.setFilters(filters());
//过滤器链
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap());
//登录页面
shiroFilterFactoryBean.setLoginUrl("/login");
//未授权页面
shiroFilterFactoryBean.setUnauthorizedUrl("/login");
return shiroFilterFactoryBean;
}
}
PropertiesUtil类(说明)
java
package com.itheima.shiro.properties;
import com.itheima.shiro.utils.EmptyUtil;
import lombok.extern.log4j.Log4j2;
/**
* @Description 读取Properties的工具类
*/
@Log4j2
public class PropertiesUtil {
public static LinkProperties propertiesShiro = new LinkProperties();
/**
* 读取properties配置文件信息
*/
static {
String sysName = System.getProperty("sys.name");
if (EmptyUtil.isNullOrEmpty(sysName)) {
sysName = "application.properties";
} else {
sysName += ".properties";
}
try {
propertiesShiro.load(PropertiesUtil.class.getClassLoader()
.getResourceAsStream("authentication.properties"));
} catch (Exception e) {
log.warn("资源路径中不存在authentication.properties权限文件,忽略读取!");
}
}
/**
* 根据key得到value的值
*/
public static String getShiroValue(String key) {
return propertiesShiro.getProperty(key);
}
}
authentication.properties配置文件
properties
/static/**=anon
#登录链接不拦截
/login/**=anon
#访问/resource/**需要有admin的角色
/resource/**=roles[dev,SuperAdmin]
#其他链接是需要登录的
/**=authc ```
说明:过滤器链由路径+过滤器组成,它是自上而下有序的,一个一个去匹配的。如果上面匹配失败,则不会匹配下面的内容。
自定义过滤器
java
package com.itheima.shiro.core.filter;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.CollectionUtils;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.util.Set;
/**
* @Description:角色or判断过滤器,只要当前用户包含传入的角色集合中的一个角色,就判断通过
*/
public class RolesOrAuthorizationFilter extends AuthorizationFilter {
//TODO - complete JavaDoc
@SuppressWarnings({"unchecked"})
public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {
Subject subject = getSubject(request, response);
String[] rolesArray = (String[]) mappedValue;
if (rolesArray == null || rolesArray.length == 0) {
//no roles specified, so nothing to check - allow access.
return true;
}
Set<String> roles = CollectionUtils.asSet(rolesArray);
for (String role : roles) {
boolean flag = subject.hasRole(role);
if (flag){
return flag;
}
}
return false;
}
}
加载自定义过滤器
在 ShiroConfig类中配置如下:
java
/**
* @Description 加载自定义过滤器
*/
private Map<String, Filter> filters(){
Map<String,Filter> map = new HashMap<>();
// 这里的key表示过滤器的名称
map.put("roles-or", new RolesOrAuthorizationFilter());
return map;
}
//shiro过滤器管理
@Bean("shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean(){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager());
//过滤器,指定自定义的过滤器
shiroFilterFactoryBean.setFilters(filters());
//过滤器链
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap());
//登录页面
shiroFilterFactoryBean.setLoginUrl("/login");
//未授权页面
shiroFilterFactoryBean.setUnauthorizedUrl("/login");
return shiroFilterFactoryBean;
}
测试自定义过滤器
在 authentication.properties配置文件中修改内容如下:
properties
#访问/resource/**需要有SuperAdmin或者dev的角色
/resource/**=roles-or[dev,SuperAdmin]