首先创建 用户-角色-权限 三个实体类 和 用户与角色关系表和 角色与权限关系表
关联表:用户-角色管理表SysRoleUser(用户在系统中都有什么角色,比如admin,manage等),角色-权限关联表SysRolePermission(每个角色对应什么权限可以进行操作)。
用户实体
@TableName("user_info")
public class UserInfo extends BaseEntity<UserInfo> {
private static final long serialVersionUID = 1L;
private Long id;//用户id;
private String name;//名称(昵称或者真实姓名,不同系统不同定义)
private String password; //密码;
private String salt;//加密密码的盐
private Integer state;//用户状态,0:创建未认证(比如没有激活,没有输入验证码等等)--等待验证的用户 , 1:正常状态,2:用户被锁定.
@TableField("user_name")
private String userName; //账号.
@TableField(exist=false)
private List roleList;// 一个用户具有多个角色
}
角色实体
@TableName("sys_role")
public class SysRole extends BaseEntity<SysRole> {
private static final long serialVersionUID = 1L;
private String available;// 是否可用,如果不可用将不会添加给用户
private String description; // 角色描述,UI界面显示使用
private Long id;// 编号
private String role; // 角色标识程序中判断使用,如"admin",这个是唯一的:
@TableField(exist = false)
private List permissions;// 角色 - 权限关系定义;
}
权限实体
@TableName("sys_permission")
public class SysPermission extends BaseEntity<SysPermission> {
private static final long serialVersionUID = 1L;
private Long id;
private String name;
@TableField("parent_id")
private Integer parentId;
@TableField("parent_ids")
private String parentIds;
private String permission;
@TableField("resource_type")
private String resourceType;
private String url;
}
对应关系实体(mybatisPlus生成)
@TableName("sys_role_user")
public class SysRoleUser extends Model<SysRoleUser> {
private static final long serialVersionUID = 1L;
private Long id;
private Long roleId;
private Long uid;
}
@TableName("sys_role_permission")
public class SysRolePermission extends Model<SysRolePermission> {
private static final long serialVersionUID = 1L;
private Integer id;
private Long permissionId;
private Long roleId;
}
部分接口mapper.XML不展示
<resultMap id="userInfoWithRole" type="com.core.shiro.entity.UserInfo" extends="BaseResultMap">
<collection property="roleList" ofType="com.core.shiro.entity.SysRole" >
<id column="rid" property="id" />
<result column="create_time" property="createTime" />
<result column="creator" property="creator" />
<result column="edit_time" property="editTime" />
<result column="editor" property="editor" />
<result column="is_del" property="isDel" />
<result column="available" property="available" />
<result column="description" property="description" />
<result column="role" property="role" />
collection>
<select id="selectUserByUserNameWithRole" resultMap="userInfoWithRole" parameterType="java.lang.String">
SELECT
u.*,
r.id as rid,
r.ROLE,
r.description,
r.available
FROM user_info u,sys_role r,sys_role_user ru
<where>
u.user_name=#{userName}
and u.id=ru.uid
and r.id=ru.role_id
and r.is_del='f'
where>
select>
<resultMap id="RoleWithPermission" type="com.core.shiro.entity.SysRole" extends="BaseResultMap">
<collection property="permissions" ofType="com.core.shiro.entity.SysPermission">
<id column="pid" property="id" />
<result column="name" property="name" />
<result column="parent_id" property="parentId" />
<result column="parent_ids" property="parentIds" />
<result column="permission" property="permission" />
<result column="resource_type" property="resourceType" />
<result column="url" property="url" />
collection>
resultMap>
<select id="selectRoleByIdWithPermission" resultMap="RoleWithPermission" parameterType="java.lang.Long">
SELECT
r.*,
p.id as pid,
P . NAME,
P .parent_id,
P .parent_ids,
P .permission,
P .resource_type,
P .url
from sys_permission p,sys_role r,sys_role_permission rp
<where>
r.id=#{id} AND
r.id=rp.role_id AND
p.id=rp.permission_id AND
p.is_del='f'
where>
select>
使用shiro框架 大概需要三个步骤。
注入ShiroFilterFactoryBean
注入securityManager
@Bean
public DefaultWebSecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//设置realm.
securityManager.setRealm(myShiroRealm());
//注入缓存管理器;
securityManager.setCacheManager(ehCacheManager());//这个如果执行多次,也是同样的一个对象;
//注入记住我管理器;
securityManager.setRememberMeManager(rememberMeManager());
return securityManager;
}
3.实现Realm继承AuthorizingRealm然后重写两个方法
ShiroConfiguration.java
@Configuration
public class ShiroConfiguration {
/**
* ShiroFilterFactoryBean 处理拦截资源文件问题。
* 注意:单独一个ShiroFilterFactoryBean配置是或报错的,以为在
* 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager
*
Filter Chain定义说明
1、一个URL可以配置多个Filter,使用逗号分隔
2、当设置多个过滤器时,全部验证通过,才视为通过
3、部分过滤器可指定参数,如perms,roles
*
*/
@Bean
public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
System.out.println("ShiroConfiguration.shirFilter()");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
Map filters = shiroFilterFactoryBean.getFilters();//获取filters
filters.put("authc", new CustomFormAuthenticationFilter());//将自定义 的FormAuthenticationFilter注入shiroFilter中
// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
//拦截器.
Map filterChainDefinitionMap = new LinkedHashMap();
//配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了
filterChainDefinitionMap.put("/logout", "logout");
//配置记住我或认证通过可以访问的地址
filterChainDefinitionMap.put("/index", "user");
filterChainDefinitionMap.put("/", "user");
//验证码可以匿名访问
filterChainDefinitionMap.put("/getValidateCode", "anon");
filterChainDefinitionMap.put("/static/**", "anon");//解决静态资源文件
//:这是一个坑呢,一不小心代码就不好使了;
//
filterChainDefinitionMap.put("/**", "authc");
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
shiroFilterFactoryBean.setLoginUrl("/login");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/index");
//未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public DefaultWebSecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//设置realm.
securityManager.setRealm(myShiroRealm());
//注入缓存管理器;
securityManager.setCacheManager(ehCacheManager());//这个如果执行多次,也是同样的一个对象;
//注入记住我管理器;
securityManager.setRememberMeManager(rememberMeManager());
return securityManager;
}
/**
* 身份认证realm;
* (这个需要自己写,账号密码校验;权限等)
* @return
*/
@Bean
public MyShiroRealm myShiroRealm(){
MyShiroRealm myShiroRealm = new MyShiroRealm();
myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());;
return myShiroRealm;
}
/**
* 凭证匹配器
* (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
* 所以我们需要修改下doGetAuthenticationInfo中的代码;
* )
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
// HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
HashedCredentialsMatcher hashedCredentialsMatcher = new RetryLimitHashedCredentialsMatcher(ehCacheManager());
hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));
return hashedCredentialsMatcher;
}
/**
* 开启shiro aop注解支持.
* 使用代理方式;所以需要开启代码支持;
* @param securityManager
* @return
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
/**
* shiro缓存管理器;
* 需要注入对应的其它的实体类中:
* 1、安全管理器:securityManager
* 可见securityManager是整个shiro的核心;
* @return
*/
@Bean
public EhCacheManager ehCacheManager(){
System.out.println("ShiroConfiguration.getEhCacheManager()");
EhCacheManager cacheManager = new EhCacheManager();
cacheManager.setCacheManagerConfigFile("classpath:config/ehcache-shiro.xml");
return cacheManager;
}
/**
* cookie对象;
* @return
*/
@Bean
public SimpleCookie rememberMeCookie(){
System.out.println("ShiroConfiguration.rememberMeCookie()");
//这个参数是cookie的名称,对应前端的checkbox的name = rememberMe
SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
//
simpleCookie.setMaxAge(259200);
return simpleCookie;
}
/**
* cookie管理对象;
* @return
*/
@Bean
public CookieRememberMeManager rememberMeManager(){
System.out.println("ShiroConfiguration.rememberMeManager()");
CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
cookieRememberMeManager.setCookie(rememberMeCookie());
return cookieRememberMeManager;
}
}
常用的权限Filter
anon:所有url都都可以匿名访问;
authc: 需要认证才能进行访问;
user:配置记住我或认证通过可以访问;
MyShiroRealm.java
public class MyShiroRealm extends AuthorizingRealm {
@Autowired
private IUserInfoService iUserInfoService;
@Autowired
private ISysRoleService sysRoleService;
/**
* 认证信息.(身份验证)
* :
* Authentication 是用来验证用户身份
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("MyShiroRealm.doGetAuthenticationInfo()");
//获取用户的输入的账号.
String username = (String)token.getPrincipal();
System.out.println(token.getCredentials());
//通过username从数据库中查找 User对象,如果找到,没找到.
//实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
UserInfo userInfo= iUserInfoService.selectUserByUserNameWithRole(username);
System.out.println("----->>userInfo="+userInfo);
if(userInfo == null){
return null;
}
//账号判断;
//加密方式;
//交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配,如果觉得人家的不好可以自定义实现
// SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
// userInfo, //用户名
// userInfo.getPassword(), //密码
// ByteSource.Util.bytes(userInfo.getCredentialsSalt()),//salt=username+salt
// getName() //realm name
// );
//明文: 若存在,将此用户存放到登录认证info中,无需自己做密码对比,Shiro会为我们进行密码对比校验
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
userInfo, //用户名
userInfo.getPassword(), //密码
getName() //realm name
);
return authenticationInfo;
}
/**
* 此方法调用 hasRole,hasPermission的时候才会进行回调.
*
* 权限信息.(授权):
* 1、如果用户正常退出,缓存自动清空;
* 2、如果用户非正常退出,缓存自动清空;
* 3、如果我们修改了用户的权限,而用户不退出系统,修改的权限无法立即生效。
* (需要手动编程进行实现;放在service进行调用)
* 在权限修改后调用realm中的方法,realm已经由spring管理,所以从spring中获取realm实例,
* 调用clearCached方法;
* :Authorization 是授权访问控制,用于对用户进行的操作授权,证明该用户是否允许进行当前操作,如访问某个链接,某个资源文件等。
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
/*
* 当没有使用缓存的时候,不断刷新页面的话,这个代码会不断执行,
* 当其实没有必要每次都重新设置权限信息,所以我们需要放到缓存中进行管理;
* 当放到缓存中时,这样的话,doGetAuthorizationInfo就只会执行一次了,
* 缓存过期之后会再次执行。
*/
System.out.println("权限配置-->MyShiroRealm.doGetAuthorizationInfo()");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
UserInfo userInfo = (UserInfo)principals.getPrimaryPrincipal();
//实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
// UserInfo userInfo = userInfoService.findByUsername(username)
//权限单个添加;
// 或者按下面这样添加
//添加一个角色,不是配置意义上的添加,而是证明该用户拥有admin角色
// authorizationInfo.addRole("admin");
//添加权限
// authorizationInfo.addStringPermission("userInfo:query");
///在认证成功之后返回.
//设置角色信息.
//支持 Set集合
for(SysRole role:userInfo.getRoleList()){
authorizationInfo.addRole(role.getRole());
SysRole sysRole = sysRoleService.selectRoleByIdWithPermission(role.getId());//获取角色
for(SysPermission p:sysRole.getPermissions()){
authorizationInfo.addStringPermission(p.getPermission());
}
}
//设置权限信息.
// authorizationInfo.setStringPermissions(getStringPermissions(userInfo.getRoleList()));
return authorizationInfo;
}
}
在认证、授权内部实现机制中都有提到,最终处理都将交给Realm进行处理。因为在Shiro中,是通过Realm来获取应用程序中的用户、角色及权限信息的
Shiro的认证过程最终会交由Realm执行,这时会调用Realm的getAuthenticationInfo(token)方法。
该方法主要执行以下操作:
1、检查提交的进行认证的令牌信息
2、根据令牌信息从数据源(通常为数据库)中获取用户信息
3、对用户信息进行匹配验证。
4、验证通过将返回一个封装了用户信息的AuthenticationInfo实例。
5、验证失败则抛出AuthenticationException异常信息。
而在我们的应用程序中要做的就是自定义一个Realm类,继承AuthorizingRealm抽象类,重载doGetAuthenticationInfo (),重写获取用户信息的方法。