();
资源抽象对象,目前暂未使用:
/**
* 资源抽象类
* @author Administrator
*
*/
public interface Resource {
// 得到资源编码
public String getResCode();
}
最后,贴上hbm文件。
< class name ="com.my.model.User" table ="t_student" >
< id name ="id" >
< column name ="pk_id" />
< generator class ="identity" > generator >
id >
< property name ="name" type ="java.lang.String" >
< column name ="f_name" />
property >
< property name ="password" type ="java.lang.String" >
< column name ="f_password" />
property >
< property name ="birthday" >
< column name ="f_birthday" />
property >
< set name ="roles" table ="t_student_role" >
< key column ="fk_student_id" > key >
< many-to-many class ="com.my.model.Role" column ="fk_role_id" >
many-to-many >
set >
class >
< class name ="com.my.model.Model" table ="t_model" >
< id name ="id" >
< column name ="pk_id" />
< generator class ="identity" > generator >
id >
< property name ="modelName" type ="java.lang.String" >
< column name ="f_model_name" />
property >
< property name ="displayOrder" column ="f_display_order" >
property >
< set name ="menus" table ="t_menu" order-by ="f_display_order" >
< key column ="fk_model_id" > key >
< one-to-many class ="com.my.model.Menu" />
set >
class >
< class name ="com.my.model.Menu" table ="t_menu" >
< id name ="id" >
< column name ="pk_id" />
< generator class ="identity" > generator >
id >
< property name ="code" type ="java.lang.String" >
< column name ="f_code" />
property >
< property name ="name" type ="java.lang.String" >
< column name ="f_name" />
property >
< property name ="displayOrder" column ="f_display_order" >
property >
< property name ="url" type ="java.lang.String" >
< column name ="f_url" />
property >
< many-to-one name ="model" class ="com.my.model.Model" column ="fk_model_id" >
many-to-one >
class >
< class name ="com.my.model.Operation" table ="t_operation" >
< id name ="id" >
< column name ="pk_id" />
< generator class ="identity" > generator >
id >
< property name ="code" type ="java.lang.String" >
< column name ="f_code" />
property >
< property name ="name" type ="java.lang.String" >
< column name ="f_name" />
property >
< property name ="displayOrder" column ="f_display_order" >
property >
< property name ="actionFlag" column ="f_action_flag" >
property >
< property name ="url" type ="java.lang.String" >
< column name ="f_url" />
property >
< many-to-one name ="menu" class ="com.my.model.Menu" column ="fk_menu_id" >
many-to-one >
class >
< class name ="com.my.model.Role" table ="t_role" >
< id name ="id" >
< column name ="pk_id" />
< generator class ="identity" > generator >
id >
< property name ="displayName" type ="java.lang.String" >
< column name ="f_displayName" />
property >
< property name ="roleCode" type ="java.lang.String" >
< column name ="f_roleCode" />
property >
< set name ="menus" table ="t_role_menu" >
< key column ="fk_role_id" > key >
< many-to-many class ="com.my.model.Menu" column ="fk_menu_id" order-by ="f_display_order" >
many-to-many >
set >
< set name ="operations" table ="t_role_operation" >
< key column ="fk_role_id" > key >
< many-to-many class ="com.my.model.Operation" column ="fk_operation_id" order-by ="f_display_order" >
many-to-many >
set >
class >
基础的代码完成了,其实,user中已经有部分和权限验证相关的代码了,后面我详细讲解。
在项目中集成 shiro权限框架(3)
Shiro与spring已经有完整的整合方法,所以,我们先在web.xml中添对过滤器,将需要验证的请求,拦截到shiro中。
shiro 过滤器 -->
< filter >
< filter-name > shiroFilterfilter-name >
< filter-class > org.springframework.web.filter.DelegatingFilterProxyfilter-class >
< init-param >
< param-name > targetFilterLifecycleparam-name >
< param-value > trueparam-value >
init-param >
filter >
< filter-mapping >
< filter-name > shiroFilterfilter-name >
< url-pattern > /*url-pattern >
filter-mapping >
在spring配置中添加下面的支持bean.
< bean id ="roleOrFilter" class ="com.my.commons.RolesOrFilter" >
bean >
< bean id ="myRealm" class ="com.my.service.impl.DbAuthRealm" >
bean >
< bean id ="securityManager" class ="org.apache.shiro.web.mgt.DefaultWebSecurityManager" >
< property name ="realms" >
< list >
< ref bean ="myRealm" />
list >
property >
bean >
< bean id ="shiroFilter" class ="org.apache.shiro.spring.web.ShiroFilterFactoryBean" depends-on ="roleOrFilter" >
< property name ="securityManager" ref ="securityManager" />
< property name ="loginUrl" value ="/login.jsp" />
< property name ="successUrl" value ="/main" />
< property name ="unauthorizedUrl" value ="/commons/unauth.jsp" />
< property name ="filterChainDefinitions" value ="#{authService.loadFilterChainDefinitions()}" />
< property name ="filters" >
< map >
< entry key ="roleOrFilter" value-ref ="roleOrFilter" >
entry >
map >
property >
bean >
< bean id ="lifecycleBeanPostProcessor" class ="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
其中filterChainDefinitions 部分,简单的项目可以直接写死,我们这里采用的是启动时读取生成,如果写死,将会出现类似下面的代码:
/ js /** = anon
/ css /** = anon
/login.jsp = anon
/login = anon
/ docs /doc1. jsp = authc ,roleOrFilter[ admin , dev ]
/ admin /manager.jsp = authc , roleOrFilter[ admin ]
/ admin /user/* = authc , rest[/ admin /user/*]
/ userinfo /myinfo.jsp = authc ,roleOrFilter[test, dev , admin ]
/** = authc
这段静态权限代码中用到了过滤器,我得给大家说一下:
anon部分是不需要进行验证的,即这部分是要放行的(注意**与*是有区别的,**表示该目录下的所有,当然也包括子目录下的东西,而*仅指当前目录下的东西)。
Authc,这个是需要登录的人才可以访问的。
roleOrFile,这个是我定义的,只要这个过滤器中的一个角色满足,即可访问,而shiro提供的role过滤器,是当所有角色都满足时才可访问。
Rest是restful过滤器,会将get,post,put,delete转换为对资源的read.create,modify,delete操作。
当然,shiro还提供了其它的过滤器,大家可以自己去看看,比如:permission过滤器,shiro对权限的描述采用wildcard字串,功能强大且可读性强。
另外一点要接出,shiro在里使用最先匹配规则,一旦匹配成功,将不再进行后续的过滤规则检查,因此,在书写时一定要注意顺序,比如,你把/** = anon写到第一行,那么后面的一切都将不会再检测。
好了,下面来讲如何动态生成这个规则,大家看我的配置中有value="#{authService.loadFilterChainDefinitions()}",这个是spring el表达式语言,表示调用容器中的另一个bean的方法,把这个方法的返回结果,赋值给filterChainDefinitions属性。
在些再补充一些权限字串的知识:
shiro所支持的广义权限字串表达式,共有三种:
1、简单方式
比如: subject.isPermitted("editNews"),表示判断某操作者是否有【编辑新闻】的权限。
2、细粒度方式
比如: subject.isPermitted("News:create"),表示判断某操作者是否有【新建新闻】的权限。
3、实例级访问方式
比如: subject.isPermitted("News:edit:10"),表示判断某操作者是否有【编辑id号是10新闻】的权限。
上面 3种方式中,可以用*表示所有,例如:"News:*"为对所有新闻的操作,"*:create"对所有事务都可以新增。还可以用 逗号 表示或都,"News:edit:10,11"表示可对10,11号新闻进行编辑。
如果要写页面权限,可参照如下配置:
/index.jsp = anon /admin/** = authc, roles[admin] /docs/** = authc, perms[document:read] /** = authc
我定义了一个权限相关的接口,如下:
/**
* 权限管理相关方法
* @author ljh
*
*/
public interface IAuthService {
/**
* 加载过滤配置信息
* @return
*/
public String loadFilterChainDefinitions();
/**
* 重新构建权限过滤器
* 一般在修改了用户角色、用户等信息时,需要再次调用该方法
*/
public void reCreateFilterChains();
}
其中一个方法用于加载生成权限规则字串,另一个,用于用户在系统中更改了角色-菜单,角色-功能关系时,动态重新生效的方法,实现类如下:
@Service (value="authService" )
public class AuthServiceImpl implements IAuthService {
private static final Logger log = Logger.getLogger (AuthServiceImpl .class );
// 注意 /r/n 前不能有空格
private static final String CRLF = "\r\n" ;
private static final String LAST_AUTH_STR = "/** =authc\r\n" ;
@Resource
private ShiroFilterFactoryBean shiroFilterFactoryBean ;
@Resource
private IBaseDao dao ;
@Override
public String loadFilterChainDefinitions() {
StringBuffer sb = new StringBuffer("" );
sb.append(getFixedAuthRule())
.append(getDynaAuthRule())
.append(getRestfulOperationAuthRule())
.append(LAST_AUTH_STR );
return sb.toString();
}
// 生成 restful 风格功能权限规则
private String getRestfulOperationAuthRule() {
List operations = dao .queryEntitys("from Operation o" , new Object[]{}) ;
Set restfulUrls = new HashSet();
for (Operation op : operations) {
restfulUrls.add(op.getUrl());
}
StringBuffer sb = new StringBuffer("" );
for (Iterator urls = restfulUrls.iterator(); urls.hasNext(); ) {
String url = urls.next();
if (! url.startsWith("/" )) {
url = "/" + url ;
}
sb.append(url).append("=" ).append("authc, rest[" ).append(url).append("]" ).append(CRLF );
}
return sb.toString();
}
// 根据角色,得到动态权限规则
private String getDynaAuthRule() {
StringBuffer sb = new StringBuffer("" );
Map> rules = new HashMap>();
List roles = dao .queryEntitys("from Role r left join fetch r.menus" , new Object[]{}) ;
for (Role role: roles) {
for (Iterator
menus =role.getMenus().iterator(); menus.hasNext();) {
String url = menus.next().getUrl();
if (!url.startsWith("/" )) {
url = "/" + url;
}
if (!rules.containsKey(url)) {
rules.put(url, new HashSet());
}
rules.get(url).add((role.getRoleCode()));
}
}
for (Map.Entry> entry :rules.entrySet()) {
sb.append(entry.getKey()).append(" = " ).append("authc,roleOrFilter" ).append(entry.getValue()).append(CRLF );
}
return sb.toString();
}
// 得到固定权限验证规则串
private String getFixedAuthRule() {
StringBuffer sb = new StringBuffer("" );
ClassPathResource cp = new ClassPathResource("fixed_auth_res.properties" );
Properties properties = new OrderedProperties();
try {
properties.load(cp.getInputStream());
} catch (IOException e) {
log .error("loadfixed_auth_res.properties error!" , e);
throw new RuntimeException("load fixed_auth_res.properties error!" );
}
for (Iterator its = properties.keySet().iterator();its.hasNext();) {
String key = (String)its.next();
sb.append(key).append(" = " ).append(properties.getProperty(key).trim()).append(CRLF );
}
return sb.toString();
}
@Override
// 此方法加同步锁
public synchronized void reCreateFilterChains() {
AbstractShiroFilter shiroFilter = null ;
try {
shiroFilter = (AbstractShiroFilter)shiroFilterFactoryBean .getObject();
} catch (Exception e) {
log .error("getShiroFilter from shiroFilterFactoryBean error!" , e);
throw new RuntimeException("get ShiroFilter from shiroFilterFactoryBean error!" );
}
PathMatchingFilterChainResolver filterChainResolver =(PathMatchingFilterChainResolver)shiroFilter.getFilterChainResolver();
DefaultFilterChainManager manager =(DefaultFilterChainManager)filterChainResolver.getFilterChainManager();
// 清空老的权限控制
manager.getFilterChains().clear();
shiroFilterFactoryBean .getFilterChainDefinitionMap().clear();
shiroFilterFactoryBean .setFilterChainDefinitions(loadFilterChainDefinitions());
// 重新构建生成
Map chains = shiroFilterFactoryBean .getFilterChainDefinitionMap();
for (Map.Entry entry :chains.entrySet()) {
String url = entry.getKey();
String chainDefinition =entry.getValue().trim().replace(" " , "" );
manager.createChain(url,chainDefinition);
}
}
}
在项目中集成 shiro权限框架(4)
在实现loadFilterChainDefinitions方法时,我把权限规则分成了三个部分,一个是固定规则,比如哪些不需要过渡等,另一部分是菜单级动态规则,最后一部分是基于restful的功能规则(当然,如果系统不是基于restful的,需要根据实际情况修改)。
固定规则保存在一个叫fixed_auth_res.properties的文件中,内容大致是这样的:
#fixed auth rules
/js /**= anon
/css /**= anon
/login.jsp = anon
/login = anon
下面是DbAuthRealm类,按照我们的规则,从数据库中取出信息,对用户进行验证,密码的加密算法也是在些检测。
/**
* 数据库存储认证类信息
* @author ljh
*
*/
public class DbAuthRealm extends AuthorizingRealm {
@Resource (name = "studentService" )
private IStudentService studentService ;
public DbAuthRealm() {
super ();
// 设置认证 token 的实现类为用户名密码模式
this .setAuthenticationTokenClass(UsernamePasswordToken.class );
// 设置验证方式,用户自行设定密码加密方式
this .setCredentialsMatcher(new CredentialsMatcher() {
@Override
public boolean doCredentialsMatch(AuthenticationToken token,AuthenticationInfo info) {
UsernamePasswordToken upToken = (UsernamePasswordToken)token;
String pwd = new String(upToken.getPassword());
User student = studentService .getStudentById(Long.parseLong (upToken.getUsername()));
if (student.getPassword().equals(DigestUtils.md5Hex (pwd))){
// 用户名及密码验证通过
return true ;
}
// 用户名或密码不正确
return false ;
}
});
}
// 认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
// 调用业务方法
User student = studentService .getStudentById(Long.parseLong (upToken.getUsername()));
if (student != null ) {
// 要放在作用域中的东西,请在这里进行操作
SecurityUtils.getSubject ().getSession().setAttribute("c_user" , student);
return new SimpleAuthenticationInfo(student.getId(),student.getPassword(), this .getName());
}
// 认证没有通过
return null ;
}
// 授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
PrincipalCollection principalCollection) {
Long loginId = (Long)principalCollection.fromRealm(getName()).iterator().next();
// 取当前用户
User student = studentService .getStudentById(loginId);
// 添加角色及权限信息
SimpleAuthorizationInfo sazi = new SimpleAuthorizationInfo();
sazi.addRoles(student.getRolesAsString());
sazi.addStringPermissions(student.getPermissionsAsString());
return sazi;
}
@Override
protected void clearCachedAuthorizationInfo(PrincipalCollection pc) {
SimplePrincipalCollection principals = new SimplePrincipalCollection(pc, getName());
super .clearCachedAuthorizationInfo(pc);
}
最后,来看看入口控制器关于登录、退出的代码:
@Controller
public class MyAction {
@Resource
private ShiroFilterFactoryBean shiroFilterFactoryBean ;
@Resource
private IAuthService authService ;
@RequestMapping (value="/login" )
public String login(
@RequestParam (value="username" , defaultValue="" ) String username,
@RequestParam (value="password" , defaultValue="" ) String password,
HttpServletRequest req) {
Subject currentUser = SecurityUtils.getSubject ();
boolean loginSuccess = false ;
// 用户已经登录过了,直接进主页面
if (currentUser.isAuthenticated()) {
loginSuccess = true ;
}
try {
currentUser.login(new UsernamePasswordToken(username, password));
loginSuccess = true ;
} catch (Exception e) {
e.printStackTrace();
req.setAttribute("error_info" , " 用户名或密码错,请核对! " );
return "login" ;
}
if (loginSuccess) {
// 生成用户菜单 ( 当然,也可以在 main 中完成,但在重定向的情况下会有 hibernatesession 延迟问题 )
User s = (User)currentUser.getSession().getAttribute("c_user" );
//currentUser.getSession().setAttribute("menus",s.getMenus());
currentUser.getSession().setAttribute("models" , s.getModels());
return "redirect:main" ;
}
return "login" ;
}
@RequestMapping (value="/logout" )
public String logout() {
Subject currentUser = SecurityUtils.getSubject ();
currentUser.logout();
return "login" ;
}
@RequestMapping (value="/main" )
public String index() {
User s = (User)SecurityUtils.getSubject ().getSession().getAttribute("c_user" );
System.out .println(s.getName() + " 进入主页面! " );
return "main_page" ;
}
最后,RolesOrFilter代码贴出:
/**
* 在进行角色检测时,只要有一个满足即通
* @author ljh
*
*/
public class RolesOrFilter extends AuthorizationFilter {
@SuppressWarnings ({"unchecked" })
public boolean isAccessAllowed(ServletRequestrequest, 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 ;
}
for (String role : rolesArray) {
if (subject.hasRole(role)) {
return true ;
}
}
return false ;
}
}
最后一个辅助类,在读取properties时,按key/val在源文件中的顺序读出:
/**
* 有序 properties
* @author ljh
*/
public class OrderedProperties extends Properties {
private static final long serialVersionUID = -4827607240846121968L;
private final LinkedHashSet keys = new LinkedHashSet();
public Enumerationkeys() {
return Collections. enumeration (keys );
}
public Object put(Object key, Objectvalue) {
keys .add(key);
return super .put(key, value);
}
public Set keySet() {
return keys ;
}
public SetstringPropertyNames() {
Set set = new LinkedHashSet();
for (Object key : this .keys ) {
set.add((String) key);
}
return set;
}
}
好了,shiro的简单的权限整合到此结束,上面的代码实现了用户功能菜单根据权限动态生成,基于url的访问控制,基于restful的访问控制,权限粒度到了功能按钮级别。
另外,如果你要实现基于方法级的权限,shiro也是支持的,它提供了相应的注解【当然你也可以用AOP来自己写,需要用到threadlocal对象传递一些信息哦】。如果你想实现基于数据级的权限,我想最好的办法还是自己在业务实现中进行处理吧。
如果你想学习Java工程化、高性能及分布式、高性能、深入浅出。性能调优、Spring,MyBatis,Netty源码分析和大数据等知识点可以来找我。 而现在我就有一个平台可以提供给你们学习,让你在实践中积累经验掌握原理。主要方向是JAVA架构师。如果你想拿高薪,想突破瓶颈,想跟别人竞争能取得优势的,想进BAT但是有担心面试不过的,可以加我的Java架构进阶群:554355695 注:加群要求 1、具有2-5工作经验的,面对目前流行的技术不知从何下手,需要突破技术瓶颈的可以加。 2、在公司待久了,过得很安逸,但跳槽时面试碰壁。需要在短时间内进修、跳槽拿高薪的可以加。 3、如果没有工作经验,但基础非常扎实,对java工作机制,常用设计思想,常用java开发框架掌握熟练的,可以加。 4、觉得自己很牛B,一般需求都能搞定。但是所学的知识点没有系统化,很难在技术领域继续突破的可以加。 5.阿里Java高级大牛直播讲解知识点,分享知识,多年工作经验的梳理和总结,带着大家全面、科学地建立自己的技术体系和技术认知! 6.小号加群一律不给过,谢谢。