项目地址:https://github.com/fanta04/PermissionExample
一、表结构
四张表就可以表示每个用户拥有的角色,以及每个角色对应的资源权限。通过资源权限表可以对管理模块进行增删改查级别(也就是按钮级别)的权限划分,具体可以看资源权限表的内容:
二、Shiro配置
/**
* shiro配置类
* */
@Configuration
public class ShiroConfiguration {
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//将自定义的拦截器覆盖掉shiro自带的拦截器
Map<String, Filter> filterMap = new LinkedHashMap<>();
filterMap.put("authc",new AjaxPermissionAuthorizationFilter());
shiroFilterFactoryBean.setFilters(filterMap);
/**
* 定义shiro的过滤链
* authc:该url需要通过验证才能访问,它是Shiro内置的一个拦截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter
* anon:该url可以匿名访问
* */
Map<String,String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/","anon");
filterChainDefinitionMap.put("/static/**","anon");
filterChainDefinitionMap.put("/login/auth","anon");
filterChainDefinitionMap.put("/login/logout","anon");
filterChainDefinitionMap.put("/error","anon");
filterChainDefinitionMap.put("/**","authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
defaultWebSecurityManager.setRealm(userRealm());
return defaultWebSecurityManager;
}
@Bean
public UserRealm userRealm(){
UserRealm userRealm = new UserRealm();
return userRealm;
}
/**
* Shiro生命周期处理器
*/
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
/**
* 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证
* 配置以下两个bean(DefaultAdvisorAutoProxyCreator(可选)和AuthorizationAttributeSourceAdvisor)即可实现此功能
*/
@Bean
@DependsOn({"lifecycleBeanPostProcessor"})
public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
advisorAutoProxyCreator.setProxyTargetClass(true);
return advisorAutoProxyCreator;
}
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
return authorizationAttributeSourceAdvisor;
}
}
自定义Realm
public class UserRealm extends AuthorizingRealm {
@Autowired
private LoginService loginService;
@Override
/**
* loginService(getInfo方法:将用户的基本信息和权限信息放入session)<---
* permissionService(getUserPermission)<---permissionMapper.xml拿到用户的权限列表
*
* 当碰到注解@RequiresPermission时调用这个授权方法
* */
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
Session session = SecurityUtils.getSubject().getSession();
//查询用户的权限
JSONObject permission = (JSONObject) session.getAttribute(Constants.SESSION_USER_PERMISSION);
//为当前用户设置角色和权限
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
authorizationInfo.addStringPermissions((Collection<String>) permission.get("permissionList"));
return authorizationInfo;
}
/**
* subject.login(token)最终会调用到doGetAuthenticationInfo这个认证方法
* */
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//获取输入的用户名和密码
String loginName = (String) authenticationToken.getPrincipal();
String passowrd = new String((char[]) authenticationToken.getCredentials());
//从数据库中查询用户名和密码
JSONObject user = loginService.getUser(loginName, passowrd);
if(user == null){
throw new UnknownAccountException();
}
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
user.getString("username"),
user.getString("password"),
this.getName()
);
user.remove("password");
//当认证通过之后将用户的基本信息放入session里
SecurityUtils.getSubject().getSession().setAttribute(Constants.SESSION_USER_INFO,user);
return simpleAuthenticationInfo;
}
}
Shiro最主要的功能就是认证和授权。
认证比较好理解,就是判断用户的登录名和密码是否正确。
其步骤为:
- 通过SecurityManager拿到认证主体
subject
:SecurityManager.getSubject()
- 生成用户名密码凭证
token
:new UsernamePasswordToken(user.getUsername(),user.getPassword())
- 登录认证:
subject.login(token)
然后shiro源码会指引我们到自定义Realm中的认证方法doGetAuthenticationInfo()
,完成认证。
授权,当访问的链接配置在shiro的过滤器链中,Controller方法上有shiro权限注解时,或者前端页面标签中含有shiro权限标签时,以上情况都需要进行。
比较常用的是在Controller方法上加shiro注解,如@RequiresPermissions("article:add")
当访问这类有注解的方法时,shiro会调用自定义Realm中的doGetAuthorizationInfo()
授权方法。
在登录认证的过程中,会把用户的基本信息和权限信息查询出来放到session中(本文中没写出来,代码里面有),所以授权方法要做的就是从session中取出权限信息,放入到SimpleAuthorizationInfo
中。