1.Shiro核心架构
三个常用过滤器
anno:不需要任何权限即可访问资源
authc:需要登录的权限才可以访问资源
perms:需要指定的权限才能访问目标资源
2.shiro登录认证
使用认证过滤器
1.在shiro的xml文件中添加认证过滤器,创建shiroFilterFactoryBean,创建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
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--该文件的作用spring整合shiro-->
<!--1. 创建ShiroFilterFactoryBean对象-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!--配置SecurityManager的属性-->
<property name="securityManager" ref="securityManager"/>
<!--配置shiro过滤器-->
<property name="filterChainDefinitions">
<value>
<!-- /* : 匹配一级目录
/** :匹配多级或者一级目录
静态资源是需要无条件放行,登陆的方法也是需要无条件放行,
shiro默认是对登录页面无条件放行的,而且默认等于页面的名字为login.jsp/
-->
/css/**=anon
/img/**=anon
/make/**=anon
/plugins/**=anon
/login.do=anon
/**=authc
</value>
</property>
<!--配置默认的登录页面-->
<property name="loginUrl" value="/login2.jsp"/>
</bean>
<!--2.创建securityManager对象-->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<!--securityManager本质上是会调用realm的方法-->
<property name="realm" ref="realm"/>
</bean>
<!--3. 创建realm,需要自定义的-->
<bean id="realm" class="cn.itcast.web.shiro.AuthRealm"/>
</beans>
控制层的登录认证
1.得到subject对象,将账号(邮箱)密码封装到token,发出登录请求登录成功得到一个登录成功对象
例:
@Controller
public class LoginController extends BaseController {
@Autowired
private UserService userService;
@Autowired
private ModuleService moduleService;
/*
url: /login.do
作用:校验登陆
参数:email(邮箱) ,password(密码)
返回值: 登陆成功 home/main(后台首页) || 登陆失败: login.jsp()
*/
@RequestMapping("/login")
public String login(String email,String password){
//1. 判断用户名与密码是否为空,如果两者有一者为空都是回到登陆页面
if(StringUtils.isEmpty(email)||StringUtils.isEmpty(password)){
request.setAttribute("error","用户名或者密码不能为空");
//注意: login.jsp不在pages目录中,所以不要经过视图解析器,所以前面带上forward或者redirect
return "forward:/login.jsp";
}
try {
//2. 得到subject的对象
Subject subject = SecurityUtils.getSubject();
//3. 把邮箱与密码封装到Token里面
UsernamePasswordToken token = new UsernamePasswordToken(email,password);
//4. 使用subject发出登陆的请求,携带token过去, 这里的登录方法本质上是会调用realm里面的登陆方法
subject.login(token);
//5. 登陆成功得到一个登陆成功的对象, shiro登陆成功之后其实shiro本质上也会在session中做很多登陆成功标记
User loginUser = (User) subject.getPrincipal();
//这里做的不是登录成功标记,而且我们页面上需要使用登陆者的信息,所以我们保存登陆者的信息
session.setAttribute("loginUser",loginUser);
//6. 查看该登陆成功的用户的权限
List<Module> moduleList = moduleService.findModuleByUser(loginUser);
session.setAttribute("modules",moduleList);
//返回到后台的首页
return "home/main";
} catch (UnknownAccountException e) { //如果一旦出现了该异常,代表不存在用户名
request.setAttribute("error","用户名或者密码错误");
return "forward:/login.jsp";
}catch (IncorrectCredentialsException e){ //密码错误的异常
request.setAttribute("error","用户名或者密码错误");
return "forward:/login.jsp";
}
}
}
编写AuthRealm,实现登录认证
根据token转为用户名、密码,根据用户名查找用户,存在用户则对比密码
例:
public class AuthRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
//登录认证方法
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//1. 先把token强制UsernamePasswordToken
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
//2. 得到邮箱与密码
String email = usernamePasswordToken.getUsername();
// String password = new String(usernamePasswordToken.getPassword()); //用户输入的密码
//3. 根据邮箱去数据库查找一个用户
User dbUser = userService.findByEmail(email);
//4. 如果查找到的用户等于null,代表用户名不存在,直接返回null即可。当你返回null的时候,subject.login方法会接收到一个异常
if(dbUser==null){
return null;
}
//5. 如果用户存在,那么则通知shiro去对比密码
/*
SimpleAuthenticationInfo(Object principal, Object credentials, String realmName)
principal: 登陆成功返回给controller登录成功对象
credentials : 该用户在数据库中的密码
realmName: 不需要管理
*/
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(dbUser,dbUser.getPassword(),"");
return simpleAuthenticationInfo;
}
}
对密码加盐加密,编写自定义加盐加密匹配器,增加用户时进行加盐加密,注销用户需销毁session在shiro中的标记。
3.shiro授权
如何实现授权?分为以下两个步骤==
- 登陆认证成功后,获取用户的权限 (给该用户分配对应的权限)
- 访问资源时候,进行授权校验:用访问资源需要的权限去用户权限列表查找,如果存在,则有权限访问资源。(权限拦截)
shiro提供了四种方式实现权限校验:
1) 硬编码方式(拦截方法)(非Web应用,Web应用)
Subject subject = SecurityUtils.getSubject();
subject.checkPermission("部门管理");
2) 过滤器配置方式(拦截url)(Web应用)
/system/user/list.do = perms["用户管理"]
3) 注解方式(拦截方法)(Web应用)
@RequiresPermissions(“”)
4) shiro提供的标签((拦截页面元素:按钮,表格等))(Web应用)
<shiro:hasPermission name="用户管理">
<a href="#">用户管理</a>
</shiro:hasPermission>
实现自定义realm的doGetAuthorizationInfo()方法,返回用户已经具有的权限。
例:
public class AuthRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Autowired
private ModuleService moduleService;
//授权方法
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//1. 获取到登陆成功对象
User loginUser = (User) principals.getPrimaryPrincipal();
//2. 查询登陆用户拥有的权限
List<Module> moduleList = moduleService.findModuleByUser(loginUser);
//3.把权限添加到AuthorizationInfo对象中,这个对象拥有的权限则代表了当前用户拥有权限
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
//4. 遍历用户的权限,把权限唯一标识添加到authorizationInfo对象里面
for (Module module : moduleList) {
authorizationInfo.addStringPermission(module.getName());//权限标记一般我们都添加
}
return authorizationInfo;
}
}