Shiro安全框架的入门
Shiro的认证方式
1. 认证:现在使用咱们的系统的认证方式就是指通过用户名和密码登录该系统,然后可以使用该系统提供的模块功能。
* 在生活中其实有很多的认证手动,例如指纹识别,人脸识别、短信验证等。
* 使用shiro框架的认证,指的是登录功能!!
2. 咱们系统中采用的就是用户名和密码的验证的方式
* 传统的验证方式:客户端发送请求 --> Action接收请求参数(调用service和dao) --> 封装到HttpSession对象 --> 转发到成功JSP页面 --> 响应给客户端
* 咱们项目中使用的是Shiro的验证方式
3. Shiro的安全框架
* Shiro是一个安全框架,是Apache开源组织提供的,用于解决系统的认证和授权的问题,同时也提供了会话管理和数据加密等功能。
* Shiro的验证方式
* 客户端 --> Action接收请求参数 --> Action调用Shiro的安全框架
* 调用service,通过用户名查询出该用户,再根据客户的传递密码和数据库密码进行比较,需要单独编写密码比较器,如果密码比较成功后通过认证,授权
* 登录成功存入到HttpSession --> 转发到成功页面 --> 响应给客户端
Shiro框架的概述
1. 查看提供的文档
* Apache Shiro是Java的一个安全框架。功能强大,使用简单的Java安全框架
* 它为开发人员提供一个直观而全面的认证,授权,加密及会话管理的解决方案
2.Shiro核心的功能
* Authentication:身份认证/登录,验证用户是不是拥有相应的身份
* Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限
* Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中
* Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储
3. 从应用程序角度来观看Shiro框架
Shiro认证的入门
1. 因为现在没有提供Realm域,所以先自己手动提供配置文件
* 创建shiro.ini的配置文件,写入如下代码
[users]
meimei=1234
2. 编写入门程序
@Test
public void demo1(){
// 通过工厂,加载配置文件
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
// 通过工厂获取到安全管理器
SecurityManager securityManager = factory.getInstance();
// 获取到subject对象,但是需要先注册工具
SecurityUtils.setSecurityManager(securityManager);
// 获取到subject对象
Subject subject = SecurityUtils.getSubject();
// 就可以认证了
AuthenticationToken token = new UsernamePasswordToken("meimei", "12345");
// 这就是认证
subject.login(token);
System.out.println("认证通过...");
}
Spring整合Shiro安全框架
Shiro框架的使用
1. 导入jar包,如果使用了Maven,引入坐标即可(项目配置文件中已经引入)
2. 配置Spring整合Shiro的核心过滤器,核心filter,一个filter相当于10个filter
* 注意:shiro的filter必须在struts2的filter之前,否则action无法创建
* 代码如下
<!-- Shiro Security filter filter-name这个名字的值将来还会在spring中用到 -->
<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. Spring整合Shiro框架,需要配置
* 创建applicationContext-shiro.xml的配置文件,引入约束
* 在applicationContext配置文件引入<import resource="classpath:spring/applicationContext-shiro.xml"></import>
4. 配置DelegatingFilterProxy过滤器,通过shiroFilter名称去IOC的容器中查找对象,配置该对象(配置具体的规则)
<!-- 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/loginAction_logout* = anon
/login* = anon
/logout* = anon
/components/** = anon
/css/** = anon
/img/** = anon
/js/** = anon
/plugins/** = anon
/images/** = anon
/js/** = anon
/make/** = anon
/skin/** = anon
/stat/** = anon
/ufiles/** = anon
/validator/** = anon
/resource/** = anon
/** = authc
/*.* = authc
</value>
</property>
</bean>
5. 配置安全管理器,需要配置
* 配置安全管理器
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!-- 必须要注入自定义Realm域,至少提供一个 -->
<property name="realm" ref="authRealm"/>
</bean>
* 配置自定义Realm域,准备提供数据
<!-- 配置自定义Realm域 -->
<bean id="authRealm" class="cn.itcast.jk.shiro.AuthRealm">
<!-- 注入密码比较器 -->
<property name="credentialsMatcher" ref="passwordMatcher"/>
</bean>
* 注意:该Realm域还没有创建,需要自己手动创建,Realm类必须要继承AuthorizingRealm类,这是编写Realm域的规范。就相当于过滤器要实现Filter接口一个道理。
* 有2个抽象方法必须添加实现,具体的代码如下
/**
* 自定义Realm域
* @author Administrator
*/
public class AuthRealm extends AuthorizingRealm{
@Resource(name="userService")
private UserService userService;
/**
* 授权
*/
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
return null;
}
/**
* 认证
*/
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken arg0) throws AuthenticationException {
return null;
}
}
* 配置密码比较器的类,密码比较器的类也需要自己手动创建,需要继承SimpleCredentialsMatcher类,重写doCredentialsMatch方法进行密码比较
* 具体的代码如下
/**
* 密码比较器
* @author Administrator
*/
public class CustomCredentialsMatcher extends SimpleCredentialsMatcher{
/**
* 用于进行密码比较的
*/
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
return super.doCredentialsMatch(token, info);
}
}
* 配置如下
<bean id="passwordMatcher" class="cn.itcast.jk.shiro.CustomCredentialsMatcher"/>
6. 测试shiro的配置文件
* 首先修改LoginAction类的login的方法,添加如下代码
// 判断用户名是否为空,如果为空,跳转到登录页面
if(UtilFuns.isEmpty(username)){
return LOGIN;
}
Md5hash加密算法
1. md5的加密
* 比较常用的加密算法,相对来说容易被破解
* 算法比较固定,密码是123,使用md5加密,得到的密文是固定的(202cb962ac59075b964b07152d234b70)!!
2. 采用shiro的Md5Hash算法进行加密
* 使用工具类Encrypt类
登录认证的代码编写
1. 打开AuthRealm类,准备完成认证代码的编写。认证就是登录功能,最疼密码需要一致,先编写密码比较器
2. 打开CustomCredentialsMatcher类,准备编写代码
/**
* 密码比较器
* @author Administrator
*/
public class CustomCredentialsMatcher extends SimpleCredentialsMatcher{
/**
* 用于进行密码比较的
* token -- 表示从页面传过来的用户名和密码
* info -- 表示从数据库中查询到的密码
*/
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
// 把token向下强转成实现类
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
// 获取密码
String inputPwd = new String(upToken.getPassword());
// 对密码加密
String md5Pwd = Encrypt.md5(inputPwd, upToken.getUsername());
// 获取到数据库中的密码
String dbPwd = info.getCredentials().toString();
return super.equals(md5Pwd, dbPwd);
}
}
3. 打开AuthRealm类,完成认证的方法编写,具体的代码如下
/**
* 认证
* AuthenticationToken token 表示页面传过来的用户名和密码
* 逻辑:通过用户名查询数据库,获取到该用户的密码,封装到AuthenticationInfo对象中,返回。
*/
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
// 获取到用户名
final String username = upToken.getUsername();
// 创建条件对象
Specification<User> spec = new Specification<User>() {
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
return cb.equal(root.get("userName").as(String.class), username);
}
};
// 通过用户名查询数据库,查询数据库,查询数据
List<User> list = userService.find(spec);
// 判断
if(list != null && list.size() > 0){
// 获取到user对象
User user = list.get(0);
// 创建AuthenticationInfo对象
/**
* principal user对象
* credentials 密码
* realmName 字符串,自定义的,realm的名称
*/
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), "abc");
return info;
}
return null;
}
编写登录的方法
1. 搭建LoginAction,编写login的登录方法,具体代码如下
public String login() throws Exception {
// 判断
if(UtilFuns.isEmpty(username)){
return LOGIN;
}
try {
// 先获取到Subject对象
Subject subject = SecurityUtils.getSubject();
// 创建UsernamePasswordToken对象,封装用户名和密码
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
// 使用shiro框架进行校验
subject.login(token);
// 获取返回的结果
User user = (User) subject.getPrincipal();
// 存入到session中
ServletActionContext.getRequest().getSession().setAttribute(SysConstant.CURRENT_USER_INFO, user);
return SUCCESS;
} catch (Exception e) {
e.printStackTrace();
super.put("errorInfo", "用户名或者密码错误!!");
return "login";
}
}
编写退出的方法
1. 具体的代码如下
//退出
public String logout(){
//删除session
session.remove(SysConstant.CURRENT_USER_INFO);
SecurityUtils.getSubject().logout();
return "logout";
}
用户授权的代码编写
1. 授权功能:认证通过后的用户,所拥有的权限。
2. shiro框架提供和权限有关的标签库
* <%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro" %>
* <shiro:hasPermission name="系统首页">,判断认证通过后的用户,是否拥有系统首页的权限?如果该用户拥有系统首页权限,把标签中间的内容显示到页面上。
3. 打开home文件夹下的title.jsp,把动态获取菜单代码打开
4. 打开AuthRealm类,完成授权的方法编写,具体的代码如下
/**
* 授权
*/
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) {
// 从AuthenticationInfo中获取到用户对象
User user = (User) pc.fromRealm(this.getName()).iterator().next();
List<String> list = new ArrayList<String>();
// 继续操作,通过对象导航的方式,获取到该用户下的角色,具有哪些权限
Set<Role> roles = user.getRoles();
// 遍历,获取到每一个角色对象
for (Role role : roles) {
// 通过角色对象获取到该角色具有的权限
Set<Module> modules = role.getModules();
for (Module module : modules) {
list.add(module.getName());
}
}
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermissions(list);
return info;
}
5. 问题
* 如果访问系统管理下部门Action中的所有的方法,必须有部门管理的权限
* /sysadmin/deptAction_* = perms["部门管理"]