Ⅰ、简述
1)概述
轻量级灵活的安全框架,用于身份认证、授权、企业会话管理和加密
2)执行原理
更正:realm是抽象类(画图时想当然了,忘记还有抽象类这个东西)
3)shiro认证、授权
Ⅱ、登录示例
这里使用 spring管理的web项目 来举例
一、准备
1)引入jar包
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-all</artifactId>
<version>1.2.2</version>
</dependency>
2)web.xml中配置shiro过滤器
<!--shiro过滤器-->
<filter>
<filter-name>shiroFilter</filter-name>
<!--spring代理过滤器,拦截所有请求,通过代理过滤器找到shiro过滤器-->
<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的xml中初始化过滤器、securityMAnager、realm
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--初始化shiro过滤器-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<!--登录-->
<property name="loginUrl" value="/index.html"></property>
<!--登录成功-->
<property name="successUrl" value="/main.jsp"></property>
<!--Unauthorized未经授权(当前用户不能访问某些页面时,跳转到指定页面)-->
<property name="unauthorizedUrl" value="/index.html"></property>
<property name="filterChainDefinitions">
<value>
<!--authc认证、perms授权、roles角色-->
<!--BOSS这个角色可以访问的页面-->
/jurisdiction.html = authc, roles[BOSS]
<!--编号为022的功能可读-->
<!--/jurisdiction.html = authc, perms[022:read]-->
<!--退出:清除session、改变cookie中的登录状态-->
/employee/loginOut = logout
<!--释放:这些资源、链接、路径可以被公众访问(就是没登录,也可以访问的……)-->
/index.html = anon
/employee/login = anon
/css/** = anon
/js/** = anon
<!--所有路径都需要认证,除了释放的-->
/** = authc
</value>
</property>
</bean>
<!--初始化securityMAnager-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="myRealm"></property>
</bean>
<!--管理shiro生命周期的对象。可以对类重构(重新构建)-->
<bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!--初始化realm-->
<bean id="myRealm" class="com.hbw.realm.MyRealm">
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<!--指定加密算法-->
<property name="hashAlgorithmName" value="MD5"></property>
<!--迭代次数(随机数:盐,采用MD5算法迭代了几次)-->
<property name="hashIterations" value="3"></property>
</bean>
</property>
</bean>
</beans>
二、使用
1)前端
index.html登录页面发送请求
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登录页面</title>
<link rel="stylesheet" type="text/css" href="/css/themes/default/easyui.css">
<link rel="stylesheet" type="text/css" href="/css/themes/icon.css">
<link rel="stylesheet" type="text/css" href="/css/demo/demo.css">
<script type="text/javascript" src="/js/jquery.min.js"></script>
<script type="text/javascript" src="/js/jquery.easyui.min.js"></script>
</head>
<body>
<div style="margin:20px 0;"></div>
<div style="margin: auto;width: 400px;">
<div class="easyui-panel" title="登录页面" style="width:400px">
<div style="padding:10px 60px 20px 60px">
<form id="ff" method="post">
<table cellpadding="5">
<tr>
<td>用户名:</td>
<td><input class="easyui-textbox" type="text" name="ename" data-options="required:true"></input></td>
</tr>
<tr>
<td>密码:</td>
<td><input class="easyui-textbox" type="password" name="epwd" data-options="required:true"></input></td>
</tr>
</table>
</form>
<div style="text-align:center;padding:5px">
<a href="javascript:void(0)" class="easyui-linkbutton" onclick="submitForm()">登录</a>
<a href="javascript:void(0)" class="easyui-linkbutton" onclick="clearForm()">取消</a>
</div>
</div>
</div>
</div>
<script>
function submitForm(){
$('#ff').form('submit', {
url:"/login",
success:function(data){
var obj = eval('('+data+')');
alert(obj.message);
if(obj.result){
if(obj.login){
window.location.href="/main.html";
}else{
window.location.href ="/index.html";
}
}else{
window.location.href = "/index.html";
}
}
});
}
function clearForm(){
$('#ff').form('clear');
}
</script>
</body>
</html>
2)controller控制层
前端发送请求给控制层,控制层调用servie层
/*登录*/
@RequestMapping("login")
@ResponseBody
public EmployeeResult login(String ename, String epwd){
//使用shiro登录
return employeeService.shiroLogin(ename,epwd);
}
3)service层进行处理
service进行逻辑处理,login方法自动调用MyRealm.java
public EmployeeResult shiroLogin(String ename, String epwd) {
EmployeeResult result = new EmployeeResult();
//通过核心管理器,拿到登录对象
Subject subject = SecurityUtils.getSubject();
try{//校验成功
//登录校验(把用户名和密码封装到token中)
// 调用login实际上是交给realm做的操作
subject.login(new UsernamePasswordToken(ename,epwd));
//利用shiro也可以讲数据封装在session中
Session session = subject.getSession();
//shiro讲登录成功的数据封装在subject的principal中
session.setAttribute("emplyoee",subject.getPrincipal());
result.setLogin(true);
result.setResult(true);
result.setMessage("登录成功");
}catch (AuthenticationException ex){//校验失败
ex.printStackTrace();
result.setLogin(false);
result.setMessage("登录失败");
} catch (Exception e) {
e.printStackTrace();
result.setResult(false);
}
return result;
}
4)MyRealm.java
登录调用认证方法
定义realm进行认证、授权,realm调用mapper.xml与数据库打交道,根据获取的数据进行校验,如果校验没有通过,则会抛出异常,在Srvice中进行逻辑判断即可。
package com.hbw.realm;
import com.hbw.bean.Efunction;
import com.hbw.bean.Employee;
import com.hbw.bean.EmployeeExample;
import com.hbw.bean.Erole;
import com.hbw.dao.EfunctionMapper;
import com.hbw.dao.EmployeeMapper;
import com.hbw.dao.EroleMapper;
import org.apache.shiro.authc.*;
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.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.List;
public class MyRealm extends AuthorizingRealm {
@Autowired
private EmployeeMapper employeeMapper;
@Autowired
private EroleMapper eroleMapper;
@Autowired
private EfunctionMapper efunctionMapper;
//授权(就是授予权力,你有没有权力某些链接、路径的权力,防止非法访问)
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
AuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
Employee employee = (Employee) principalCollection.getPrimaryPrincipal();
//1)获取当前用户的角色信息
//每个员工可能有多个角色
List<Erole> eroles = eroleMapper.getRoleByEid(employee.getEid());
//将角色信息封装到AuthorizationInfo中
for(Erole erole : eroles){
((SimpleAuthorizationInfo) authorizationInfo).addRole(erole.getRname());
}
//2)获取当前登录客户的功能信息
List<Efunction> efunctions = efunctionMapper.getCurrentEfunction(employee.getEid());
//将功能信息封装到AuthorizationInfo中
for(Efunction efunction : efunctions){
((SimpleAuthorizationInfo) authorizationInfo).addStringPermission(efunction.getFcode());
}
return authorizationInfo;
}
//认证(就是登录校验)
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
//如何认证的
//先通过用户名查询用户信息,没有就是null,抛出异常
EmployeeExample example = new EmployeeExample();
EmployeeExample.Criteria criteria = example.createCriteria();
criteria.andEnameEqualTo(token.getUsername());
List<Employee> employees = employeeMapper.selectByExample(example);
if(employees!=null && employees.size()>0){
//使用返回的用户信息中的用户名和密码与token中的用户名和密码进行校验(查询的信息、与前端的信息来比较)。认证失败就抛出异常
//三个参数:查询出的对象、查询对象中的密码、token中的用户名
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(employees.get(0),employees.get(0).getEpwd(),token.getUsername());
//指定盐(getRemark1获取新增用户时保存在remark1字段中的盐)
authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(employees.get(0).getRemark1()));
return authenticationInfo;
}
return null;
}
}
5)dao层
查询数据而已,省略
Ⅲ、新增用户,密码加密示例
/*新增员工,并加密*/
@Override
@Transactional
public BaseResult addCurrentEmployee(Employee employee) {
logger.info("开始新增,参数:"+employee);
BaseResult result = new BaseResult();
try{
//添加之前校验工号唯一
EmployeeExample example = new EmployeeExample();
EmployeeExample.Criteria criteria = example.createCriteria();
criteria.andJobNumberEqualTo(employee.getJobNumber());
List<Employee> employeeList = employeeMapper.selectByExample(example);
if(employeeList!=null && employeeList.size()>0){
result.setResult(false);
result.setMessage("工号重复");
return result;
}else{
//获取随机数(别称:盐)
//二进制转为十六进制
String salt = new SecureRandomNumberGenerator().nextBytes().toHex();
//使用shiro加密(原密码、随机数、迭代三次)
//使用MD5算法进行加密
String newepwd = new Md5Hash(employee.getEpwd(),salt,3).toString();
employee.setEpwd(newepwd);
//把盐也保存下来(登录、修改时使用)
employee.setRemark1(salt);
employeeMapper.insert(employee);
result.setResult(true);
result.setMessage("添加成功");
}
}catch (Exception ex){
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
ex.printStackTrace();
result.setResult(false);
result.setMessage("添加失败");
}
return result;
}
Ⅳ、修改加密的密码示例
/**
*
* @param epwd 输入的旧密码
* @param newepwd 输入的新密码
* @return
*/
public BaseResult changePassword(String epwd,String newepwd) {
BaseResult result = new BaseResult();
Session session = SecurityUtils.getSubject().getSession();
//获取当前用户的id
Employee employee1 = (Employee)session.getAttribute("emplyoee");
int eid = employee1.getEid();
//查询输入的旧密码是否正确(加密比较)
Employee employee = employeeMapper.selectByPrimaryKey(eid);
String saltold = employee.getRemark1();
String epwdold = new Md5Hash(epwd,saltold,3).toString();
if(epwdold.equals(employee.getEpwd())){
//加密修改
String salt = new SecureRandomNumberGenerator().nextBytes().toHex();
String newepwd1 = new Md5Hash(newepwd,salt,3).toString();
employee1.setEpwd(newepwd1);
employee1.setRemark1(salt);
employeeMapper.updateByPrimaryKeySelective(employee1);
result.setResult(true);
result.setMessage("密码修改成功");
}else{
result.setMessage("旧密码不正确!!!");
result.setResult(false);
return result;
}
return result;
}