1.知识回顾
1)搞清hibernate配置对象的关系
绝招:拿范例来背,权限架构,部门+用户+用户扩展信息+角色+模块
2)部门的自关联
public class Dept implements Serializable {
private String id;
private Set<User> users = new HashSet<User>(0);//部门与用户 一对多
private String deptName;//部门名称
private Dept parent; //父部门 自关联 子部门与父部门 多对一
private Integer state;//状态 1代表启用 0代表停用
<!-- 自关联 子部门与父部门 多对一 -->
<many-to-one name="parent" class="Dept" column="PARENT_ID"></many-to-one>
name实体中的配置的对象属性
class对应实体对象,写全路径cn.itcast.jk.domain.Dept有时简写Dept,必须配置package
column对于数据库表中的外键
3)部门和用户,一对多
<!-- private Set<User> users = new HashSet<User>(0);//部门与用户 一对多 -->
<set name="users">
<key column="DEPT_ID"></key>
<one-to-many class="User"/>
</set>
Set标签,它是个集合,在实体对象中配置Set users
name属性在实体中配置的集合的名称
casecade 级联,级联保存,删除(save-update,delete,delete-orphan,all,all- delete-orphan)
inverse交给另外一方来维护
key外键
one-to-many.class —对多具体调用的实体
面试:cascade 与 inverse 区别?
Cascade主要用于级联操作(如:级联添加,删除)
Inverse主要用于控制权是否要反转,一般将控制权放在多方
True代表控制权要反转,就交给多方维护,效率会提高
当删除部门时,级联删除部门下的所有用户。
lnverse="false”它是默认值可以不配
Update user set deptld=null where deptld=l;〃解决父子关系 Delete from user where deptld is null;〃先干掉孩子
Delete from dept where id=l;〃自杀
如果inverse="true”代表多方维护关系
Delete from user where deptld=l;〃 自杀
Delete from dept where id=l;〃自杀
False代表一方维护,它是默认值
4) 用户和部门,多对一,双向关联
public class User extends BaseEntity{
private String id;
private Dept dept;//用户与部门 多对一
private Userinfo userinfo ; //用户与用户扩展信息 一对一
private Set<Role> roles = new HashSet<Role>(0);//用户与角色 多对多
private String userName;//用户名
private String password;//密码 要加密
private Integer state;//状态
<!-- private Dept dept;//用户与部门 多对一 -->
<many-to-one name="dept" class="Dept" column="DEPT_ID"></many-to-one>
Name配置关联的对象
Class调用的实体对象
Column夕卜键
用户和用户扩展信息,一对一,单向关联
<!-- private Userinfo userinfo ; //用户与用户扩展信息 一对一 -->
<one-to-one name="userinfo" class="Userinfo" cascade="all"></one-to-one>
Name配置实体中声明的对象
Class调用的实体对象
Cascade 级联
将用户,用户扩展信息表中的主键生成策略:assigned
在程序代码中,将用户,用户扩展信息的id设置为同一个取值。
5) 用户和角色,多对多
<!-- private Set<Role> roles = new HashSet<Role>(0);//用户与角色 多对多 -->
<set name="roles" table="ROLE_USER_P">
<key column="USER_ID"></key>
<many-to-many class="Role" column="ROLE_ID"></many-to-many>
</set>
Name在实体配置关联对象
Table多对多,配置中间表
Key外键
Many-to-many多对多,class配置关联对象,column外键,order-by排序,表中有这个排序
java学院宋江
字段
没有配置级联,没有配置inverse;(双向关联,例如:删除角色和不影响用户)
修改了用户的映射文件:
6)角色和用户,多对多
<!-- private Set<User> users = new HashSet<User>(0);//角色与用户 多对多 -->
<set name="users" table="ROLE_USER_P">
<key column="ROLE_ID"></key>
<many-to-many class="User" column="USER_ID"></many-to-many>
</set>
7)角色和模块,多对多,双向关联,同上
[面试:byte/short/int/char/varchar 执行效率]
Byte>short>lnt>cha r>varchar
为什么你的id还使用varchar呢?(你想分布式)
Mysql数据库设计表的主键时,直接使用int并带上mysql数据库自增(autojncrement)
面试:请说出char与varchar的区别?
Char长度固定,执行效率更高,可能造成空间浪费
Varchar长度不固定剩余的空间会进行回收,运行效率低
补充角色模块:
public class Role extends BaseEntity{
private String id;
private Set<User> users = new HashSet<User>(0);//角色与用户 多对多
private Set<Module> modules = new HashSet<Module>(0);//角色与模块 多对多
private String name;//角色名
private String remark;//备注
private String orderNo;//排序号
<!-- private Set<Module> modules = new HashSet<Module>(0);//角色与模块 多对多 -->
<set name="modules" table="ROLE_MODULE_P">
<key column="ROLE_ID"></key>
<many-to-many class="Module" column="MODULE_ID" order-by="ORDER_NO"></many-to-many>
</set>
8)模块和角色,多对多,双向关联,同上.
<!-- private Set<Role> roles = new HashSet<Role>(0);//模块与角色 多对多 -->
<set name="roles" table="ROLE_MODULE_P">
<key column="MODULE_ID"></key>
<many-to-many class="Role" column="ROLE_ID" order-by="ORDER_NO"></many-to-many>
</set>
public class Module extends BaseEntity {
private String id;
private Set<Role> roles = new HashSet<Role>(0);//模块与角色 多对多
private String parentId; //父模块的编号
private String parentName;//父模块的名称 冗余 用空间换时间
private String name; //模块名
private Integer layerNum;//层数
private Integer isLeaf;//叶子
private String ico; //图片
private String cpermission;//权限
private String curl;//路径
private Integer ctype;//菜单的类型:主菜单,左侧菜单 ,按钮
private Integer state;//状态
private String belong;//从属于
private String cwhich;//
private Integer quoteNum;//引用次数
private String remark;//备注
private Integer orderNo;//排序号
9)Hibernate.cfg.xml
<!-- 加载映射文件-->
<mapping resource="cn/itcast/jk/domain/Dept.hbm.xml"></mapping>
<mapping resource="cn/itcast/jk/domain/User.hbm.xml"></mapping>
<mapping resource="cn/itcast/jk/domain/Userinfo.hbm.xml"></mapping>
<mapping resource="cn/itcast/jk/domain/Role.hbm.xml"></mapping>
<mapping resource="cn/itcast/jk/domain/Module.hbm.xml"></mapping>
Shiro安全框架( 认证、授权)
2.传统登录方式
3. Shiro安全框架实现登录
4.什么是Shiro ?
它是一个安全框架,用于解决系统的认证和授权问题,同时提供了会话管理,数据加密 机制。
5.Shiro的内部组织结构?
加密
加密分类:
(1)、对称加密
双方使用的同一个密匙,既可以加密又可以解密,这种加密方法称为对称加密,也称单密匙加密。
(2)、非对称加密
一对密匙由公钥和私钥组成(可以使用很多对密匙)。私钥解密公钥加密数据,公钥解密私钥加密数据(私钥公钥可以互相加密解密)。
加密算法分类
(1)、单项加密
单项加密是不可逆的,也就是只能加密,不能解密。通常用来传输类型用户名和密码,直接将加密后的数据提交到后台,因为后台不需要知道用户名和密码,可以直接将接收到的加密后的数据存储到数据库
(2)、双向加密
通常分为对称性加密算法和非对称性加密算法,对于对称性加密算法,信息接收双方都需事先知道密匙和加解密算法且其密匙是相同的,之后便是对数据进行 加解密了。非对称算法与之不同,发送双方A,B事先均生成一堆密匙,然后A将自己的公有密匙发送给B,B将自己的公有密匙发送给A,如果A要给B发送消 息,则先需要用B的公有密匙进行消息加密,然后发送给B端,此时B端再用自己的私有密匙进行消息解密,B向A发送消息时为同样的道理。
常见的算法
6.应用程序如何使用Shiro框架?
Realm:域,Realm 充当了 Shiro 与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro 会从应用配置的 Realm 中查找用户及其权限信息。从这个意义上讲,Realm 实质上是一个安全相关的 DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给 Shiro 。当配置 Shiro时,你必须至少指定一个 Realm ,用于认证和(或)授权。配置多个 Realm 是可以的,但是至少需要一个。
Shiro 内置了可以连接大量安全数据源(又名目录)的 Realm,如 LDAP、关系数据库(JDBC)、类似 INI 的文本配置资源以及属性文件等。如果缺省的 Realm 不能满足需求,你还可以插入代表自定义数据源的自己的 Realm 实现。功能
Realm能做的工作主要有以下几个方面:
身份验证(getAuthenticationInfo 方法)验证账户和密码,并返回相关信息
权限获取(getAuthorizationInfo 方法) 获取指定身份的权限,并返回相关信息
令牌支持(supports方法)判断该令牌(Token)是否被支持
令牌有很多种类型,例如:HostAuthenticationToken(主机验证令牌),UsernamePasswordToken(账户密码验证令牌)
这里主来说明一下关于前两点验证方面的逻辑,因为令牌一般用的都是 UsernamePasswordToken,哪怕用 HostAuthenticationToken,也没必要细讲,这个函数很少用到。
7.Shiro使用时要配置相关过滤器
<!-- Shiro Security filter filter-name这个名字的值将来还会在spring中用到 -->
<!-- 注意:shiro的filter必须在struts2的filter之前,否则action无法创建 -->
<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>
8.菜单的管理如何实现?
/WEB-INF/pages/home/title.jsp
<div id="menuContent">
<!-- <span id="topmenu" onclick="toModule('home');">系统首页</span><span id="tm_separator"></span>
<span id="topmenu" onclick="toModule('cargo');">货运管理</span><span id="tm_separator"></span>
<span id="topmenu" onclick="toModule('stat');">统计分析</span><span id="tm_separator"></span>
<span id="topmenu" onclick="toModule('baseinfo');">基础信息</span><span id="tm_separator"></span>
<span id="topmenu" onclick="toModule('sysadmin');">系统管理</span>
<span id="topmenu" onclick="toModule('sysadmin');">班级管理</span> -->
<!-- 当jsp页面碰到shiro标签时就执行AuthRealm中授权方法 -->
<shiro:hasPermission name="系统首页">
<span id="topmenu" onclick="toModule('home');">系统首页</span><span id="tm_separator"></span>
</shiro:hasPermission>
<shiro:hasPermission name="货运管理">
<span id="topmenu" onclick="toModule('cargo');">货运管理</span><span id="tm_separator"></span>
</shiro:hasPermission>
<shiro:hasPermission name="统计分析">
<span id="topmenu" onclick="toModule('stat');">统计分析</span><span id="tm_separator"></span>
</shiro:hasPermission>
<shiro:hasPermission name="基础信息">
<span id="topmenu" onclick="toModule('baseinfo');">基础信息</span><span id="tm_separator"></span>
</shiro:hasPermission>
<shiro:hasPermission name="系统管理">
<span id="topmenu" onclick="toModule('sysadmin');">系统管理</span>
</shiro:hasPermission>
<shiro:hasPermission name="流程管理">
<span id="topmenu" onclick="toModule('activiti');">流程管理</span>
</shiro:hasPermission>
</div>
/WEB-INF/pages/leftmenu.jsp
<ul>
<c:set var="aaa" value=""/>
<!-- 遍历当前登录用户的角色列表 -->
<c:forEach items="${_CURRENT_USER.roles }" var="role">
<!-- 遍历每个角色下的模块 -->
<c:forEach items="${role.modules }" var="module">
<!-- 如果该模块没有输出过,则要进行输出,否则这个模块就不输出 -->
<c:if test="${(moduleName eq module.remark) and module.ctype==1 }">
<c:if test="${fn:contains(aaa,module.cpermission) eq false }">
<c:set var="aaa" value="${aaa},${module.cpermission }"/>
<li><a href="${ctx}/${module.curl}" onclick="linkHighlighted(this)" target="main" id="aa_1">${module.cpermission }</a></li>
</c:if>
</c:if>
</c:forEach>
</c:forEach>
</ul>
9. Shiro具体使用步骤
1)导入jar包
2)过滤器的配置
web.xml shiroFilter
<!-- Shiro Security filter filter-name这个名字的值将来还会在spring中用到 -->
<!-- 注意:shiro的filter必须在struts2的filter之前,否则action无法创建 -->
<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>
3)产生代理类的方式
applicationContext.xml
<!--Shiro安全框架产生代理子类的方式: 使用cglib方式-->
<aop:aspectj-autoproxy proxy-target-class="true" />
applicationContext-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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<description>Shiro的配置</description>
<!-- SecurityManager配置 -->
<!-- 配置Realm域 -->
<!-- 密码比较器 -->
<!-- 代理如何生成? 用工厂来生成Shiro的相关过滤器-->
<!-- 配置缓存:ehcache缓存 -->
<!-- 安全管理 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- Single realm app. If you have multiple realms, use the 'realms' property instead. -->
<property name="realm" ref="authRealm"/><!-- 引用自定义的realm -->
<!-- 缓存 -->
<property name="cacheManager" ref="shiroEhcacheManager"/>
</bean>
<!-- 自定义权限认证 -->
<bean id="authRealm" class="cn.itcast.jk.shiro.AuthRealm">
<property name="userService" ref="userService"/>
<!-- 自定义密码加密算法 -->
<property name="credentialsMatcher" ref="passwordMatcher"/>
</bean>
<!-- 设置密码加密策略 md5hash -->
<bean id="passwordMatcher" class="cn.itcast.jk.shiro.CustomCredentialsMatcher"/>
<!-- filter-name这个名字的值来自于web.xml中filter的名字 -->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<!--登录页面 -->
<property name="loginUrl" value="/index.jsp"></property>
<!-- 登录成功后 -->
<!-- <property name="successUrl" value="/home.action"></property> -->
<property name="filterChainDefinitions">
<!-- /**代表下面的多级目录也过滤 -->
<value>
/index.jsp* = anon
/home* = anon
/sysadmin/login/login.jsp* = anon
/sysadmin/login/logout.jsp* = anon
/login* = anon
/logout* = anon
/components/** = anon
/css/** = anon
/images/** = anon
/js/** = anon
/make/** = anon
/skin/** = anon
/stat/** = anon
/ufiles/** = anon
/validator/** = anon
/resource/** = anon
/** = authc
/*.* = authc
</value>
</property>
</bean>
<!-- 用户授权/认证信息Cache, 采用EhCache 缓存 -->
<bean id="shiroEhcacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
<property name="cacheManagerConfigFile" value="classpath:ehcache-shiro.xml"/>
</bean>
<!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!-- 生成代理,通过代理进行控制 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor">
<property name="proxyTargetClass" value="true"/>
</bean>
<!-- 安全管理器 -->
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
</beans>
ehcache-shiro.xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache updateCheck="false" name="shiroCache">
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="false"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
/>
</ehcache>
4)在applicationContext.xml文件中加载shiro配置文件
<import resource="classpath:spring/applicationContext-shiro.xml"></import>
5)编写密码比较器 CustomCredentialsMatcher.java
<-(UsernamePasswordToken)AuthenticationToken
<-AuthenticationInfo
public class CustomCredentialsMatcher extends SimpleCredentialsMatcher {
//密码比较的方法 token代表用户在界面输入的用户名和密码 info代表从数据库中得到加密数据
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
//1.向下转型
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
//2.将用户在界面输入的原始密码加密
Object pwd = Encrypt.md5(new String(upToken.getPassword()), upToken.getUsername());
//3.取出数据库中加密的密码
Object dbPwd = info.getCredentials();
return this.equals(pwd, dbPwd);
}
}
Encrypt.java String md5(String password, String salt)
public class Encrypt {
/*
* 散列算法一般用于生成数据的摘要信息,是一种不可逆的算法,一般适合存储密码之类的数据,
* 常见的散列算法如MD5、SHA等。一般进行散列时最好提供一个salt(盐),比如加密密码“admin”,
* 产生的散列值是“21232f297a57a5a743894a0e4a801fc3”,
* 可以到一些md5解密网站很容易的通过散列值得到密码“admin”,
* 即如果直接对密码进行散列相对来说破解更容易,此时我们可以加一些只有系统知道的干扰数据,
* 如用户名和ID(即盐);这样散列的对象是“密码+用户名+ID”,这样生成的散列值相对来说更难破解。
*/
//高强度加密算法,不可逆
public static String md5(String password, String salt){
return new Md5Hash(password,salt,2).toString();
}
public static void main(String[] args) {
System.out.println(new Md5Hash("123456","zhangsan"+"cgx",5).toString());
}
}
6)编写自定义Realm域 AuthRealm.java
->AuthorizationInfo
->AuthenticationInfo(SimpleAuthenticationInfo)(用户界面输入的用户名,密码)
public class AuthRealm extends AuthorizingRealm {
private UserService userService;
public void setUserService(UserService userService) {
this.userService = userService;
}
//授权 当jsp页面出现Shiro标签时,就会执行授权方法
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) {
System.out.println("授权");
User user = (User) pc.fromRealm(this.getName()).iterator().next();//根据realm的名字去找对应的realm
Set<Role > roles = user.getRoles();//对象导航
List<String> permissions = new ArrayList<String>();
for(Role role :roles){
//遍历每个角色
Set<Module> modules = role.getModules();//得到每个角色下的模块列表
for(Module m :modules){
permissions.add(m.getName());
}
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermissions(permissions);//添加用户的模块(权限)
return info;
}
//认证 token 代表用户在界面输入的用户名和密码
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("认证");
//1.向下转型
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
//2.调用业务方法,实现根据用户名查询
String hql = "from User where userName=?";
List<User> list = userService.find(hql, User.class, new String[]{upToken.getUsername()});
if(list!=null && list.size()>0){
User user = list.get(0);
AuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getPassword(),this.getName());
return info; //此处如果返回,就会立即进入到密码比较器
}
return null;//就会出现异常
}
}
7)将编写的AuthRealm域配置好
8)登录操作
LoginAction ->login()
private String username;
private String password;
public String login() throws Exception {
// if(true){
// String msg = "登录错误,请重新填写用户名密码!";
// this.addActionError(msg);
// throw new Exception(msg);
// }
// User user = new User(username, password);
// User login = userService.login(user);
// if (login != null) {
// ActionContext.getContext().getValueStack().push(user);
// session.put(SysConstant.CURRENT_USER_INFO, login); //记录session
// return SUCCESS;
// }
// return "login";
if(UtilFuns.isEmpty(username)){
return "login";
}
try {
//1.得到Subject
Subject subject = SecurityUtils.getSubject();
//2.调用登录方法
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
subject.login(token);//当这一代码执行时,就会自动跳入到AuthRealm中认证方法
//3.登录成功时,就从Shiro中取出用户的登录信息
User user = (User) subject.getPrincipal();
//4.将用户放入session域中
session.put(SysConstant.CURRENT_USER_INFO, user);
} catch (Exception e) {
e.printStackTrace();
request.put("errorInfo", "对不起,用户名或密码错误!");
return "login";
}
return SUCCESS;
}
9)实现测试,发现存在一个bug
10)测试认证过程成功,进入后台操作主页面。
11)测试授权过程
<!-- 当jsp页面碰到shiro标签时就执行AuthRealm中授权方法 -->
<shiro:hasPermission name="系统首页">
<span id="topmenu" onclick="toModule('home');">系统首页</span><span id="tm_separator"></span>
</shiro:hasPermission>
12)添加用户时的bug修正
早期实现添加用户时,没有给密码,所以要补充,这样才可以使用添加的新用户实现登录操
作。
//新增
String id = UUID.randomUUID().toString();
entity.setId(id);
entity.getUserinfo().setId(id);
//补充Shiro添加后的bug
entity.setPassword(Encrypt.md5(SysConstant.DEFAULT_PASS, entity.getUserName()));
baseDao.saveOrUpdate(entity);//记录保存