本记录旨在将shiro框架应用到javaee中,且与spring整合(其他方面也不会啊呵)
第一步:导入架包,官方网址为:
http://shiro.apache.org/download.html
一般在javaee项目中只需要以下三个架包就可以了:
shiro-spring-1.2.2.jar; shiro-web-1.2.2.jar; shiro-core-1.2.2.jar
第二步:编写配置文件
1、在web.xml中进行修改
1>、要在web.xml中添加shiro的过滤器,添加方法如下:
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<!--
<init-param>
<param-name>targetFilterLifecycle</param-name>
<param-value>true</param-value>
</init-param>
-->
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
注意:该过滤器要放在struts2过滤器的前面
2>、同时shiro会有一个自己的配置文件(是不是必须的不清楚,现在只知道它必须有),也须要在web.xml中告知系统其位置,写法如下(举个例子,名字不一定,可以自己起;前一个是spring的配置文件):
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml,classpath:spring-shiro.xml</param-value>
</context-param>
2、在shiro自己的配置文件(在这里是叫spring-shiro.xml)中应该怎么写,这是一个写好的简单例子,详细的说明在其中所注释说明:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-3.0.xsd">
<description>Shiro 配置</description>
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager" />
<property name="loginUrl" value="/login.jsp" />
<property name="successUrl" value="/index.jsp" />
<property name="unauthorizedUrl" value="/noLogin.jsp" />
<property name="filterChainDefinitions">
<value>
<!-- 下面的语句是对资源及页面等进行不对处理的设置,一般anon就是不做限制,随意都可以请求到,authc说明请求该资源需要验证权限 -->
/**/*.png = anon
/**/*.jpg = anon
/**/*.gif = anon
/**/*.js = anon
/**/*.css = anon
/logout.jsp = logoutFilter
/** = authc
</value>
</property>
</bean>
<!--自定义Realm 继承自AuthorizingRealm,很重要,在此类中指明了请求不同用户各自数据源的不同方法,这个东西shiro实在是自己办不到了,哈哈,还得咱还给它指定。 -->
<bean id="myRealm" class="com.security.MyRealm"></bean>
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!--设置自定义realm -->
<property name="realm" ref="myRealm" />
</bean>
<!-- securityManager
<bean
class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="staticMethod"
value="org.apache.shiro.SecurityUtils.setSecurityManager" />
<property name="arguments" ref="securityManager" />
</bean>
-->
<!-- Enable Shiro Annotations for Spring-configured beans. Only run after -->
<!-- the lifecycleBeanProcessor has run: -->
<!-- 打开shiro注解加权限的功能 ,除了用标签对页面元素进行权限控制之外,还可以用注解对action类及方法进行权限的控制-->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
<bean
class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor" />
<bean
class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager" />
</bean>
</beans>
第三步:编写一个顶顶重要的类Realm,此类用来向shiro指明访问数据源(数据存放的位置)中存放的权限信息的方法;一般继承AuthorizingRealm类,此类为抽象类,有两个方法必须来实现,一个是doGetAuthorizationInfo,另一个是doGetAuthenticationInfo,下面对此两个方法进行详细说明:
1、protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken autheToken) throws AuthenticationException
此方法用于对请求登录的用户进行身份验证,就是在用户向系统提交登录凭证(一般就是用户名与密码)时,该方法负责将用户提交的凭证(autheToken)与数据库中存储的进行比对,返回给shiro是否有该用户(AuthenticationInfo )的信息,shiro凭借此判断是否让其登录,例子在下面的myRealm.java中有(且配有较详细的说明哦!)。
本方法的调用时机:在系统发送登录(身份认证)请求时shiro会调用此方法来得到用户信息。
2、protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection ps)
此方法用于在对用户的某一操作进行权限检测时向shiro权限控制系统提供当前登录用户是否有此权限的信息,在此方法中要完成查询(到你的数据库或任何你存放权限信息的数据源中去查)你的权限信息并返回给shiro足够信息的功能。
本方法的调用时机:在用户进行每一项请求时都会去查查看他有没有这个权限,此时,shiro就会去调此方法来得到当前登录用户的确切的权限信息。(例子在下面myRealm.java中)
3、 本类(myRealm.java)在shrio的配置文件(此例中是spring-shiro.xml)中已经给securityManager指明了。
myRealm.java
package com.security;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationException;
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 com.pojo.security.Permission;
import com.pojo.security.Role;
import com.pojo.security.User;
import com.service.IUserService;
public class MyRealm extends AuthorizingRealm{
@Autowired
private IUserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection ps) {
Set<String> permissions = new HashSet<String>();
Set<String> roles = new HashSet<String>();//保持为空,直接使用权限
Collection<User> principalsList = ps.byType(User.class);
if (principalsList.isEmpty()) {
throw new AuthorizationException("Empty principals list!");
}
for(User u : principalsList) {
User oneUser = userService.findById(u.getUsid());
permissions.addAll(getAllPermissions(oneUser));
}
//角色好像就是一个中间的符号而已,或者说像堆权限集合的统一名称一样,在此处没什么用,给它个空的就好了,只要保证把所有的权限返回就ok了应该
SimpleAuthorizationInfo anthInfo = new SimpleAuthorizationInfo(roles);
//将Set<String>类型的权限信息封装后然后返回给shiro以供其使用,当然也可以返回Permission对象集合的权限信息好像,反正原则就是你得告诉shiro到底有些什么权限
anthInfo.setStringPermissions(permissions);
return anthInfo;
}
/**
* 传入一个User对象,查出它的全部权限,以{@code Set<String>}形式返回
*/
public Set<String> getAllPermissions(User user) {
Set<String> permissions = new HashSet<String>();
Role role = user.getRole();
if(role == null) {
return new HashSet<String>();
}
Set<Permission> pers = role.getPers();
for(Permission p : pers) {
permissions.add(p.getPerName());
}
return permissions;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken autheToken) throws AuthenticationException {
//UsernamePasswordToken是AuthenticationToken的子类,此例中传入的就是用户名/密码,所以可以转换成功
UsernamePasswordToken token = (UsernamePasswordToken)autheToken;
//用请求登录的用户名/密码在数据库中查找,看是否有该用户存在(注:若密码是用MD5之类加密过的,则在此处也就用相同的方法对密码进行处理)
String hql = "from User t where t.username=? and t.password=?";
User queryUser = userService.findFirstByHql(hql, new Object[]{token.getUsername(),String.valueOf(token.getPassword())});
//返回携带有登录用户信息的认证信息(AuthenticationInfo),simpleAuthenticationInfo中第一个参数是一个用户的任何标识符,可以是id,用户名等,它是Object的,在此是用的User对象
//,第二个参数是凭证、证书之类的,是秘密的东西,一般就指密码了,第三个是该Realm的“名字”,因在一个认证体系中,可以有多个Realm,返回“名字”可能是要对多个Realm进行验证选择之类的用途吧!
return new SimpleAuthenticationInfo(queryUser, queryUser.getPassword(), getName());
}
}
第四步:构建用户/权限认证存储体系(呵,我自己起的,其实就是设计存储用户/权限信息的数据库表结构了)
在前面的myRealm类的两个方法中对User对象的操作已经有了,但这个User怎么设计呢,有没有什么特殊的呢,现在就来说一说,其实一点特殊也没有,普普通通的一个类而已,我在此写的是一个最简单的吧;
一般就是User-Role-Permission(用户-角色-权限),三个表的关系是User-Role:多对一(想着以后搞成一对多);Role-Permission:多对多;Permission-Permission:一对多(多对一???);第三种权限对权限的关联我也说不清了,反正意思就是说一个权限可能是另一个权限的子权限,每个权限都还有自己的子权限,意会就好了。
三个简单的例子如下:
Usr.java :
package com.pojo.security;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import org.hibernate.annotations.GenericGenerator;
@Entity @Table(name="sys_user")
public class User {
@Id
@GeneratedValue(generator = "system-uuid")
@GenericGenerator(name = "system-uuid", strategy = "uuid")
private String usid;
private String username;
private String password;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="rid")
private Role role;
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
public String getUsid() {
return usid;
}
public void setUsid(String usid) {
this.usid = usid;
}
}
Role.java:
package com.pojo.security;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import org.hibernate.annotations.GenericGenerator;
@Entity @Table(name="sys_role")
public class Role {
@Id
@GeneratedValue(generator = "system-uuid")
@GenericGenerator(name = "system-uuid", strategy = "uuid")
private String rid;
private String roleName;
//与权限关联
@ManyToMany(cascade = CascadeType.PERSIST)
@JoinTable(name = "sys_role_permission", joinColumns = { @JoinColumn(name = "rid") }, inverseJoinColumns = { @JoinColumn(name = "pid") })
private Set<Permission> pers;
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
public String getRid() {
return rid;
}
public void setRid(String rid) {
this.rid = rid;
}
public Set<Permission> getPers() {
return pers;
}
public void setPers(Set<Permission> pers) {
this.pers = pers;
}
}
Permission.java:
package com.pojo.security;
import java.util.List;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import org.hibernate.annotations.GenericGenerator;
@Entity @Table(name="sys_permission")
public class Permission {
@Id
@GeneratedValue(generator = "system-uuid")
@GenericGenerator(name = "system-uuid", strategy = "uuid")
private String pid;
//权限名称
private String perName;
//权限描述
private String perDec;
//权限状态(现在好像没什么用)
private String perStatus;
//与角色关联
@ManyToMany(mappedBy = "pers", cascade = CascadeType.MERGE)
private Set<Role> roles;
//上级节点
@ManyToOne(fetch=FetchType.LAZY)
private Permission parent;
//子节点
@OneToMany(mappedBy="parent", fetch=FetchType.LAZY, cascade = CascadeType.ALL)
private List<Permission> children;
public String getPid() {
return pid;
}
public void setPid(String pid) {
this.pid = pid;
}
public String getPerName() {
return perName;
}
public void setPerName(String perName) {
this.perName = perName;
}
public String getPerDec() {
return perDec;
}
public void setPerDec(String perDec) {
this.perDec = perDec;
}
public Permission getParent() {
return parent;
}
public void setParent(Permission parent) {
this.parent = parent;
}
public List<Permission> getChildren() {
return children;
}
public void setChildren(List<Permission> children) {
this.children = children;
}
public Set<Role> getRoles() {
return roles;
}
public void setRoles(Set<Role> roles) {
this.roles = roles;
}
public String getPerStatus() {
return perStatus;
}
public void setPerStatus(String perStatus) {
this.perStatus = perStatus;
}
}
第五步:准备了这么多,最后当然是怎么用啦。
使用当前来说主要分两种情境,一种是登录时的身份验证,另一种则是对用户的某一动作进行权限的认证
1、登录验证
以最常用的用户名/密码登录为例(其实我就只会这一种),不管你用什么方法(表单最常用了吧)将用户填写的用户名/密码信息传到后台java类(比如一个Action类)中,然后就开始写java代码就行了,写法大致如下示:
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
token.setRememberMe(true);//可有可无,看你需要不需要把用户记住,至于什么是记住网上有解释
Subject currentUser = SecurityUtils.getSubject();//用SecurityUtils得到当前Subject
try {
currentUser.login(token);
} catch ( UnknownAccountException uae ) { ...
} catch ( IncorrectCredentialsException ice ) { ...
} catch ( LockedAccountException lae ) { ...
} catch ( ExcessiveAttemptsException eae ) { ...
} catch ( AuthenticationException ae ) {
}
如上写你的类,用当前的Subject调用login方法就相当于是触发了shiro去进行登录身份认证,它会一路走到咱在myRealm中的doGetAuthenticationInfo方法中去,用咱提供的方法去验证该用户名/密码是否是合法的用户凭证,如果存在,则login方法会顺利执行完成(不会抛异常),若一旦有任何问题,shiro会抛出划分明细的各个异常,以提醒你是哪里出错了。
特别说明: 网上好多教程写到这里就不再写了,这给我初学此框架时带来了很大困扰,因为我参考的一个工程中用到了shiro,但是我死活也找不到Subjec.login()方法的调用,这不科学啊!最后在我硬着头皮读了源码之后才发,原来...shiro是可以默认进行验证的,前提是你的登录表单得按照一定的格式来写,这个格式就是:<1>.表单中的用户名文本框的name值应该是username,密码文本框的name值应该是password;<2>.表单必须提交到login.jsp(即表单的action属性值为必须为“/login.jsp”)。你的登录表单只要如此写,一旦提交,shiro源码中会调用Subjec.login()的方法来进行登录验证的。
2、 权限验证
权限验证就是说登录用户可以看见什么,不可以看见什么,可以操作什么、不可以操作什么;现在我知道的是三个方面,一个是页面,用shiro标签将对应的页面元素“包”起来,如果有查看此元素的权限则正常显示,反之,则你什么也不会看到;二是在action中使用注解来对某一请求的类或方法添加权限验证;三是在代码中实现。
<1>. 页面的写法看起来像下面这个样子:
<shiro:hasPermission name="权限1">
<a href="javascript:MenuSwitch('test1')"><font size="4"> 权限1 下的一个超连接</font></a><br />
</shiro:hasPermission>
(说明:当当前登录用户确实有“权限1”这一权限时,则用户可以看到这个超连接,反之则看不到;shiro标签应该可以用到所有的页面元素中,而且它并不只是有hasPermission,还有其他的标签用法与此同)
在使用标签前当然得先导入标签了,它看起来应该是类似这样的:
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags"/>
<2>. 用注解对某一方添加权限验证如这个样子:
@RequiresPermissions("account:create") //冒号前面是操作的实体,后面是动作
还有一些@RequiresRoles、@ RequiresUser、@ RequiresAuthentication、@ RequiresGuest之类的, 具体的我也不是很清楚了,用的时候再研究吧!
<3>. 在代码中直接进行“手动”验证:
//基于字符串的
if (currentUser.isPermitted("printer:print:laserjet4400n")) {
//show the Print button
} else {
//don't show the button? Grey it out?
}
//断言的方式(即如果有这个权限,此句会平稳的执行,不会有任何反映,若没权限,不知道会怎么.....,没试过!)
currentUser.checkPermission("account:open");
补充说明:
1.shiro“号称”四大功能,上面只说了两项,另两项是会话管理(session)与加密,我都没用过,在此mark一下;
2.通过简单的扩展,shiro验证可以支持验证码,也还未实施,mark;
3.更多详尽的说明可以在《Apache_Shiro参考手册中文版.pdf》中找到;